summaryrefslogtreecommitdiffstats
path: root/docshell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docshell/base/BaseHistory.cpp249
-rw-r--r--docshell/base/BaseHistory.h80
-rw-r--r--docshell/base/BrowsingContext.cpp3301
-rw-r--r--docshell/base/BrowsingContext.h1166
-rw-r--r--docshell/base/BrowsingContextGroup.cpp460
-rw-r--r--docshell/base/BrowsingContextGroup.h232
-rw-r--r--docshell/base/BrowsingContextWebProgress.cpp217
-rw-r--r--docshell/base/BrowsingContextWebProgress.h68
-rw-r--r--docshell/base/CanonicalBrowsingContext.cpp1694
-rw-r--r--docshell/base/CanonicalBrowsingContext.h383
-rw-r--r--docshell/base/ChildProcessChannelListener.cpp61
-rw-r--r--docshell/base/ChildProcessChannelListener.h56
-rw-r--r--docshell/base/IHistory.h150
-rw-r--r--docshell/base/LoadContext.cpp236
-rw-r--r--docshell/base/LoadContext.h68
-rw-r--r--docshell/base/SerializedLoadContext.cpp87
-rw-r--r--docshell/base/SerializedLoadContext.h97
-rw-r--r--docshell/base/SyncedContext.h333
-rw-r--r--docshell/base/SyncedContextInlines.h213
-rw-r--r--docshell/base/URIFixup.jsm1054
-rw-r--r--docshell/base/WindowContext.cpp569
-rw-r--r--docshell/base/WindowContext.h328
-rw-r--r--docshell/base/crashtests/1257730-1.html25
-rw-r--r--docshell/base/crashtests/1331295.html25
-rw-r--r--docshell/base/crashtests/1341657.html18
-rw-r--r--docshell/base/crashtests/1584467.html12
-rw-r--r--docshell/base/crashtests/1614211-1.html15
-rw-r--r--docshell/base/crashtests/1617315-1.html8
-rw-r--r--docshell/base/crashtests/1667491.html16
-rw-r--r--docshell/base/crashtests/1667491_1.html21
-rw-r--r--docshell/base/crashtests/369126-1.html16
-rw-r--r--docshell/base/crashtests/40929-1-inner.html14
-rw-r--r--docshell/base/crashtests/40929-1.html6
-rw-r--r--docshell/base/crashtests/430124-1.html5
-rw-r--r--docshell/base/crashtests/430628-1.html8
-rw-r--r--docshell/base/crashtests/432114-1.html8
-rw-r--r--docshell/base/crashtests/432114-2.html21
-rw-r--r--docshell/base/crashtests/436900-1-inner.html21
-rw-r--r--docshell/base/crashtests/436900-1.html8
-rw-r--r--docshell/base/crashtests/436900-2-inner.html21
-rw-r--r--docshell/base/crashtests/436900-2.html8
-rw-r--r--docshell/base/crashtests/443655.html15
-rw-r--r--docshell/base/crashtests/500328-1.html17
-rw-r--r--docshell/base/crashtests/514779-1.xhtml9
-rw-r--r--docshell/base/crashtests/614499-1.html20
-rw-r--r--docshell/base/crashtests/678872-1.html36
-rw-r--r--docshell/base/crashtests/914521.html32
-rw-r--r--docshell/base/crashtests/crashtests.list21
-rw-r--r--docshell/base/crashtests/file_432114-2.xhtml1
-rw-r--r--docshell/base/moz.build130
-rw-r--r--docshell/base/nsAboutRedirector.cpp266
-rw-r--r--docshell/base/nsAboutRedirector.h26
-rw-r--r--docshell/base/nsCTooltipTextProvider.h15
-rw-r--r--docshell/base/nsDSURIContentListener.cpp307
-rw-r--r--docshell/base/nsDSURIContentListener.h99
-rw-r--r--docshell/base/nsDocShell.cpp13439
-rw-r--r--docshell/base/nsDocShell.h1281
-rw-r--r--docshell/base/nsDocShellEditorData.cpp139
-rw-r--r--docshell/base/nsDocShellEditorData.h66
-rw-r--r--docshell/base/nsDocShellEnumerator.cpp85
-rw-r--r--docshell/base/nsDocShellEnumerator.h39
-rw-r--r--docshell/base/nsDocShellLoadState.cpp957
-rw-r--r--docshell/base/nsDocShellLoadState.h492
-rw-r--r--docshell/base/nsDocShellLoadTypes.h211
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.cpp210
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.h22
-rw-r--r--docshell/base/nsDocShellTreeOwner.cpp1304
-rw-r--r--docshell/base/nsDocShellTreeOwner.h194
-rw-r--r--docshell/base/nsIContentViewer.idl348
-rw-r--r--docshell/base/nsIContentViewerEdit.idl36
-rw-r--r--docshell/base/nsIDocShell.idl900
-rw-r--r--docshell/base/nsIDocShellTreeItem.idl171
-rw-r--r--docshell/base/nsIDocShellTreeOwner.idl106
-rw-r--r--docshell/base/nsIDocumentLoaderFactory.idl39
-rw-r--r--docshell/base/nsILoadContext.idl148
-rw-r--r--docshell/base/nsILoadURIDelegate.idl49
-rw-r--r--docshell/base/nsIPrivacyTransitionObserver.idl11
-rw-r--r--docshell/base/nsIReflowObserver.idl31
-rw-r--r--docshell/base/nsIRefreshURI.idl88
-rw-r--r--docshell/base/nsIScrollObserver.h45
-rw-r--r--docshell/base/nsITooltipListener.idl44
-rw-r--r--docshell/base/nsITooltipTextProvider.idl44
-rw-r--r--docshell/base/nsIURIFixup.idl156
-rw-r--r--docshell/base/nsIWebNavigation.idl375
-rw-r--r--docshell/base/nsIWebNavigationInfo.idl63
-rw-r--r--docshell/base/nsIWebPageDescriptor.idl30
-rw-r--r--docshell/base/nsPingListener.cpp345
-rw-r--r--docshell/base/nsPingListener.h48
-rw-r--r--docshell/base/nsRefreshTimer.cpp53
-rw-r--r--docshell/base/nsRefreshTimer.h41
-rw-r--r--docshell/base/nsWebNavigationInfo.cpp119
-rw-r--r--docshell/base/nsWebNavigationInfo.h37
-rw-r--r--docshell/base/timeline/AbstractTimelineMarker.cpp72
-rw-r--r--docshell/base/timeline/AbstractTimelineMarker.h71
-rw-r--r--docshell/base/timeline/AutoGlobalTimelineMarker.cpp41
-rw-r--r--docshell/base/timeline/AutoGlobalTimelineMarker.h48
-rw-r--r--docshell/base/timeline/AutoRestyleTimelineMarker.cpp53
-rw-r--r--docshell/base/timeline/AutoRestyleTimelineMarker.h30
-rw-r--r--docshell/base/timeline/AutoTimelineMarker.cpp48
-rw-r--r--docshell/base/timeline/AutoTimelineMarker.h46
-rw-r--r--docshell/base/timeline/CompositeTimelineMarker.h31
-rw-r--r--docshell/base/timeline/ConsoleTimelineMarker.h53
-rw-r--r--docshell/base/timeline/DocLoadingTimelineMarker.h38
-rw-r--r--docshell/base/timeline/EventTimelineMarker.h40
-rw-r--r--docshell/base/timeline/JavascriptTimelineMarker.h96
-rw-r--r--docshell/base/timeline/LayerTimelineMarker.h47
-rw-r--r--docshell/base/timeline/MarkersStorage.cpp20
-rw-r--r--docshell/base/timeline/MarkersStorage.h48
-rw-r--r--docshell/base/timeline/MessagePortTimelineMarker.h46
-rw-r--r--docshell/base/timeline/ObservedDocShell.cpp169
-rw-r--r--docshell/base/timeline/ObservedDocShell.h52
-rw-r--r--docshell/base/timeline/RestyleTimelineMarker.h37
-rw-r--r--docshell/base/timeline/TimelineConsumers.cpp287
-rw-r--r--docshell/base/timeline/TimelineConsumers.h131
-rw-r--r--docshell/base/timeline/TimelineMarker.cpp65
-rw-r--r--docshell/base/timeline/TimelineMarker.h47
-rw-r--r--docshell/base/timeline/TimelineMarkerEnums.h18
-rw-r--r--docshell/base/timeline/TimestampTimelineMarker.h36
-rw-r--r--docshell/base/timeline/WorkerTimelineMarker.h44
-rw-r--r--docshell/base/timeline/moz.build45
-rw-r--r--docshell/base/timeline/readme.md97
-rw-r--r--docshell/build/components.conf190
-rw-r--r--docshell/build/moz.build25
-rw-r--r--docshell/build/nsDocShellCID.h68
-rw-r--r--docshell/build/nsDocShellModule.cpp25
-rw-r--r--docshell/build/nsDocShellModule.h20
-rw-r--r--docshell/moz.build56
-rw-r--r--docshell/resources/content/jar.mn7
-rw-r--r--docshell/resources/content/moz.build7
-rw-r--r--docshell/resources/content/netError.js286
-rw-r--r--docshell/resources/content/netError.xhtml132
-rw-r--r--docshell/shistory/ChildSHistory.cpp298
-rw-r--r--docshell/shistory/ChildSHistory.h152
-rw-r--r--docshell/shistory/SessionHistoryEntry.cpp1588
-rw-r--r--docshell/shistory/SessionHistoryEntry.h337
-rw-r--r--docshell/shistory/moz.build45
-rw-r--r--docshell/shistory/nsIBFCacheEntry.idl16
-rw-r--r--docshell/shistory/nsISHEntry.idl450
-rw-r--r--docshell/shistory/nsISHistory.idl284
-rw-r--r--docshell/shistory/nsISHistoryListener.idl78
-rw-r--r--docshell/shistory/nsSHEntry.cpp1076
-rw-r--r--docshell/shistory/nsSHEntry.h70
-rw-r--r--docshell/shistory/nsSHEntryShared.cpp311
-rw-r--r--docshell/shistory/nsSHEntryShared.h212
-rw-r--r--docshell/shistory/nsSHistory.cpp1977
-rw-r--r--docshell/shistory/nsSHistory.h321
-rw-r--r--docshell/test/browser/Bug1622420Child.jsm11
-rw-r--r--docshell/test/browser/Bug422543Child.jsm100
-rw-r--r--docshell/test/browser/browser.ini183
-rw-r--r--docshell/test/browser/browser_backforward_userinteraction.js380
-rw-r--r--docshell/test/browser/browser_backforward_userinteraction_about.js67
-rw-r--r--docshell/test/browser/browser_badCertDomainFixup.js92
-rw-r--r--docshell/test/browser/browser_browsingContext-01.js205
-rw-r--r--docshell/test/browser/browser_browsingContext-02.js231
-rw-r--r--docshell/test/browser/browser_browsingContext-embedder.js156
-rw-r--r--docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js51
-rw-r--r--docshell/test/browser/browser_browsingContext-getWindowByName.js35
-rw-r--r--docshell/test/browser/browser_browsing_context_attached.js139
-rw-r--r--docshell/test/browser/browser_browsing_context_discarded.js65
-rw-r--r--docshell/test/browser/browser_bug1206879.js49
-rw-r--r--docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js54
-rw-r--r--docshell/test/browser/browser_bug1328501.js58
-rw-r--r--docshell/test/browser/browser_bug1347823.js77
-rw-r--r--docshell/test/browser/browser_bug134911.js57
-rw-r--r--docshell/test/browser/browser_bug1415918_beforeunload_options.js151
-rw-r--r--docshell/test/browser/browser_bug1543077-1.js47
-rw-r--r--docshell/test/browser/browser_bug1543077-2.js47
-rw-r--r--docshell/test/browser/browser_bug1543077-3.js47
-rw-r--r--docshell/test/browser/browser_bug1543077-4.js47
-rw-r--r--docshell/test/browser/browser_bug1594938.js99
-rw-r--r--docshell/test/browser/browser_bug1622420.js30
-rw-r--r--docshell/test/browser/browser_bug1648464-1.js47
-rw-r--r--docshell/test/browser/browser_bug1673702.js24
-rw-r--r--docshell/test/browser/browser_bug1688368-1.js25
-rw-r--r--docshell/test/browser/browser_bug234628-1.js47
-rw-r--r--docshell/test/browser/browser_bug234628-10.js47
-rw-r--r--docshell/test/browser/browser_bug234628-11.js47
-rw-r--r--docshell/test/browser/browser_bug234628-2.js51
-rw-r--r--docshell/test/browser/browser_bug234628-3.js49
-rw-r--r--docshell/test/browser/browser_bug234628-4.js47
-rw-r--r--docshell/test/browser/browser_bug234628-5.js47
-rw-r--r--docshell/test/browser/browser_bug234628-6.js47
-rw-r--r--docshell/test/browser/browser_bug234628-7.js49
-rw-r--r--docshell/test/browser/browser_bug234628-8.js18
-rw-r--r--docshell/test/browser/browser_bug234628-9.js18
-rw-r--r--docshell/test/browser/browser_bug349769.js72
-rw-r--r--docshell/test/browser/browser_bug388121-1.js22
-rw-r--r--docshell/test/browser/browser_bug388121-2.js73
-rw-r--r--docshell/test/browser/browser_bug420605.js133
-rw-r--r--docshell/test/browser/browser_bug422543.js251
-rw-r--r--docshell/test/browser/browser_bug441169.js44
-rw-r--r--docshell/test/browser/browser_bug503832.js76
-rw-r--r--docshell/test/browser/browser_bug554155.js32
-rw-r--r--docshell/test/browser/browser_bug655270.js64
-rw-r--r--docshell/test/browser/browser_bug655273.js54
-rw-r--r--docshell/test/browser/browser_bug670318.js144
-rw-r--r--docshell/test/browser/browser_bug673467.js62
-rw-r--r--docshell/test/browser/browser_bug852909.js35
-rw-r--r--docshell/test/browser/browser_bug92473.js70
-rw-r--r--docshell/test/browser/browser_click_link_within_view_source.js78
-rw-r--r--docshell/test/browser/browser_cross_process_csp_inheritance.js124
-rw-r--r--docshell/test/browser/browser_csp_uir.js87
-rw-r--r--docshell/test/browser/browser_dataURI_unique_opaque_origin.js29
-rw-r--r--docshell/test/browser/browser_data_load_inherit_csp.js109
-rw-r--r--docshell/test/browser/browser_fall_back_to_https.js72
-rw-r--r--docshell/test/browser/browser_fission_maxOrigins.js209
-rw-r--r--docshell/test/browser/browser_history_triggeringprincipal_viewsource.js92
-rw-r--r--docshell/test/browser/browser_loadURI_postdata.js42
-rw-r--r--docshell/test/browser/browser_multiple_pushState.js23
-rw-r--r--docshell/test/browser/browser_onbeforeunload.js326
-rw-r--r--docshell/test/browser/browser_onbeforeunload_navigation.js174
-rw-r--r--docshell/test/browser/browser_onunload_stop.js22
-rw-r--r--docshell/test/browser/browser_overlink.js27
-rw-r--r--docshell/test/browser/browser_platform_emulation.js69
-rw-r--r--docshell/test/browser/browser_search_notification.js55
-rw-r--r--docshell/test/browser/browser_tab_replace_while_loading.js83
-rw-r--r--docshell/test/browser/browser_tab_touch_events.js70
-rw-r--r--docshell/test/browser/browser_timelineMarkers-01.js45
-rw-r--r--docshell/test/browser/browser_timelineMarkers-02.js16
-rw-r--r--docshell/test/browser/browser_timelineMarkers-03.js8
-rw-r--r--docshell/test/browser/browser_timelineMarkers-04.js9
-rw-r--r--docshell/test/browser/browser_timelineMarkers-05.js16
-rw-r--r--docshell/test/browser/browser_timelineMarkers-frame-02.js183
-rw-r--r--docshell/test/browser/browser_timelineMarkers-frame-03.js106
-rw-r--r--docshell/test/browser/browser_timelineMarkers-frame-04.js123
-rw-r--r--docshell/test/browser/browser_timelineMarkers-frame-05.js150
-rw-r--r--docshell/test/browser/browser_ua_emulation.js70
-rw-r--r--docshell/test/browser/browser_uriFixupAlternateRedirects.js65
-rw-r--r--docshell/test/browser/browser_uriFixupIntegration.js114
-rw-r--r--docshell/test/browser/browser_viewsource_chrome_to_content.js20
-rw-r--r--docshell/test/browser/browser_viewsource_multipart.js44
-rw-r--r--docshell/test/browser/dummy_iframe_page.html8
-rw-r--r--docshell/test/browser/dummy_page.html6
-rw-r--r--docshell/test/browser/favicon_bug655270.icobin0 -> 1406 bytes
-rw-r--r--docshell/test/browser/file_basic_multipart.sjs24
-rw-r--r--docshell/test/browser/file_bug1046022.html54
-rw-r--r--docshell/test/browser/file_bug1206879.html9
-rw-r--r--docshell/test/browser/file_bug1328501.html27
-rw-r--r--docshell/test/browser/file_bug1328501_frame.html4
-rw-r--r--docshell/test/browser/file_bug1328501_framescript.js38
-rw-r--r--docshell/test/browser/file_bug1543077-1-child.html11
-rw-r--r--docshell/test/browser/file_bug1543077-1.html16
-rw-r--r--docshell/test/browser/file_bug1543077-2-child.html11
-rw-r--r--docshell/test/browser/file_bug1543077-2.html16
-rw-r--r--docshell/test/browser/file_bug1543077-3-child.html11
-rw-r--r--docshell/test/browser/file_bug1543077-3.html16
-rw-r--r--docshell/test/browser/file_bug1543077-4-child.html11
-rw-r--r--docshell/test/browser/file_bug1543077-4.html16
-rw-r--r--docshell/test/browser/file_bug1622420.html1
-rw-r--r--docshell/test/browser/file_bug1648464-1-child.html13
-rw-r--r--docshell/test/browser/file_bug1648464-1.html18
-rw-r--r--docshell/test/browser/file_bug1673702.json1
-rw-r--r--docshell/test/browser/file_bug1673702.json^headers^1
-rw-r--r--docshell/test/browser/file_bug1688368-1.sjs32
-rw-r--r--docshell/test/browser/file_bug234628-1-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-1.html17
-rw-r--r--docshell/test/browser/file_bug234628-10-child.xhtml4
-rw-r--r--docshell/test/browser/file_bug234628-10.html17
-rw-r--r--docshell/test/browser/file_bug234628-11-child.xhtml4
-rw-r--r--docshell/test/browser/file_bug234628-11-child.xhtml^headers^1
-rw-r--r--docshell/test/browser/file_bug234628-11.html17
-rw-r--r--docshell/test/browser/file_bug234628-2-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-2.html17
-rw-r--r--docshell/test/browser/file_bug234628-3-child.html13
-rw-r--r--docshell/test/browser/file_bug234628-3.html18
-rw-r--r--docshell/test/browser/file_bug234628-4-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-4.html18
-rw-r--r--docshell/test/browser/file_bug234628-5-child.htmlbin0 -> 498 bytes
-rw-r--r--docshell/test/browser/file_bug234628-5.html18
-rw-r--r--docshell/test/browser/file_bug234628-6-child.htmlbin0 -> 540 bytes
-rw-r--r--docshell/test/browser/file_bug234628-6-child.html^headers^1
-rw-r--r--docshell/test/browser/file_bug234628-6.html18
-rw-r--r--docshell/test/browser/file_bug234628-7-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-7-child.html^headers^1
-rw-r--r--docshell/test/browser/file_bug234628-7.html18
-rw-r--r--docshell/test/browser/file_bug234628-8-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-8.html17
-rw-r--r--docshell/test/browser/file_bug234628-9-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-9.htmlbin0 -> 740 bytes
-rw-r--r--docshell/test/browser/file_bug420605.html31
-rw-r--r--docshell/test/browser/file_bug503832.html35
-rw-r--r--docshell/test/browser/file_bug655270.html11
-rw-r--r--docshell/test/browser/file_bug670318.html23
-rw-r--r--docshell/test/browser/file_bug852909.pdfbin0 -> 1568 bytes
-rw-r--r--docshell/test/browser/file_bug852909.pngbin0 -> 94 bytes
-rw-r--r--docshell/test/browser/file_click_link_within_view_source.html6
-rw-r--r--docshell/test/browser/file_cross_process_csp_inheritance.html11
-rw-r--r--docshell/test/browser/file_csp_uir.html11
-rw-r--r--docshell/test/browser/file_csp_uir_dummy.html1
-rw-r--r--docshell/test/browser/file_data_load_inherit_csp.html11
-rw-r--r--docshell/test/browser/file_multiple_pushState.html20
-rw-r--r--docshell/test/browser/file_onbeforeunload_0.html9
-rw-r--r--docshell/test/browser/file_onbeforeunload_1.html9
-rw-r--r--docshell/test/browser/file_onbeforeunload_2.html10
-rw-r--r--docshell/test/browser/file_onbeforeunload_3.html9
-rw-r--r--docshell/test/browser/file_open_about_blank.html2
-rw-r--r--docshell/test/browser/file_slow_load.sjs8
-rw-r--r--docshell/test/browser/frame-head.js109
-rw-r--r--docshell/test/browser/head.js253
-rw-r--r--docshell/test/browser/onload_message.html15
-rw-r--r--docshell/test/browser/onpageshow_message.html13
-rw-r--r--docshell/test/browser/overlink_test.html7
-rw-r--r--docshell/test/browser/print_postdata.sjs22
-rw-r--r--docshell/test/browser/redirect_to_example.sjs4
-rw-r--r--docshell/test/browser/test-form_sjis.html24
-rw-r--r--docshell/test/browser/timelineMarkers-04.html56
-rw-r--r--docshell/test/chrome/112564_nocache.html10
-rw-r--r--docshell/test/chrome/112564_nocache.html^headers^1
-rw-r--r--docshell/test/chrome/215405_nocache.html14
-rw-r--r--docshell/test/chrome/215405_nocache.html^headers^1
-rw-r--r--docshell/test/chrome/215405_nostore.html14
-rw-r--r--docshell/test/chrome/215405_nostore.html^headers^1
-rw-r--r--docshell/test/chrome/582176_dummy.html1
-rw-r--r--docshell/test/chrome/582176_xml.xml2
-rw-r--r--docshell/test/chrome/582176_xslt.xsl8
-rw-r--r--docshell/test/chrome/662200a.html8
-rw-r--r--docshell/test/chrome/662200b.html8
-rw-r--r--docshell/test/chrome/662200c.html7
-rw-r--r--docshell/test/chrome/89419.html7
-rw-r--r--docshell/test/chrome/92598_nostore.html10
-rw-r--r--docshell/test/chrome/92598_nostore.html^headers^1
-rw-r--r--docshell/test/chrome/allowContentRetargeting.sjs7
-rw-r--r--docshell/test/chrome/blue.pngbin0 -> 2745 bytes
-rw-r--r--docshell/test/chrome/bug112564_window.xhtml121
-rw-r--r--docshell/test/chrome/bug113934_window.xhtml164
-rw-r--r--docshell/test/chrome/bug215405_window.xhtml170
-rw-r--r--docshell/test/chrome/bug293235.html13
-rw-r--r--docshell/test/chrome/bug293235_p2.html8
-rw-r--r--docshell/test/chrome/bug293235_window.xhtml119
-rw-r--r--docshell/test/chrome/bug294258_testcase.html43
-rw-r--r--docshell/test/chrome/bug294258_window.xhtml74
-rw-r--r--docshell/test/chrome/bug298622_window.xhtml146
-rw-r--r--docshell/test/chrome/bug301397_1.html9
-rw-r--r--docshell/test/chrome/bug301397_2.html10
-rw-r--r--docshell/test/chrome/bug301397_3.html10
-rw-r--r--docshell/test/chrome/bug301397_4.html9
-rw-r--r--docshell/test/chrome/bug301397_window.xhtml250
-rw-r--r--docshell/test/chrome/bug303267.html23
-rw-r--r--docshell/test/chrome/bug303267_window.xhtml99
-rw-r--r--docshell/test/chrome/bug311007_window.xhtml204
-rw-r--r--docshell/test/chrome/bug321671_window.xhtml133
-rw-r--r--docshell/test/chrome/bug360511_case1.html15
-rw-r--r--docshell/test/chrome/bug360511_case2.html15
-rw-r--r--docshell/test/chrome/bug360511_window.xhtml134
-rw-r--r--docshell/test/chrome/bug364461_window.xhtml272
-rw-r--r--docshell/test/chrome/bug396519_window.xhtml172
-rw-r--r--docshell/test/chrome/bug396649_window.xhtml116
-rw-r--r--docshell/test/chrome/bug449778_window.xhtml106
-rw-r--r--docshell/test/chrome/bug449780_window.xhtml76
-rw-r--r--docshell/test/chrome/bug454235-subframe.xhtml7
-rw-r--r--docshell/test/chrome/bug582176_window.xhtml85
-rw-r--r--docshell/test/chrome/bug608669.xhtml14
-rw-r--r--docshell/test/chrome/bug662200_window.xhtml129
-rw-r--r--docshell/test/chrome/bug690056_window.xhtml174
-rw-r--r--docshell/test/chrome/bug846906.html10
-rw-r--r--docshell/test/chrome/bug89419.sjs13
-rw-r--r--docshell/test/chrome/bug89419_window.xhtml69
-rw-r--r--docshell/test/chrome/bug909218.html11
-rw-r--r--docshell/test/chrome/bug909218.js2
-rw-r--r--docshell/test/chrome/bug92598_window.xhtml122
-rw-r--r--docshell/test/chrome/chrome.ini89
-rw-r--r--docshell/test/chrome/docshell_helpers.js614
-rw-r--r--docshell/test/chrome/file_viewsource_forbidden_in_iframe.html11
-rw-r--r--docshell/test/chrome/gen_template.pl39
-rw-r--r--docshell/test/chrome/generic.html12
-rw-r--r--docshell/test/chrome/mozFrameType_window.xhtml52
-rw-r--r--docshell/test/chrome/red.pngbin0 -> 82 bytes
-rw-r--r--docshell/test/chrome/test.template.txt41
-rw-r--r--docshell/test/chrome/test_allowContentRetargeting.html76
-rw-r--r--docshell/test/chrome/test_bug112564.xhtml37
-rw-r--r--docshell/test/chrome/test_bug113934.xhtml29
-rw-r--r--docshell/test/chrome/test_bug215405.xhtml37
-rw-r--r--docshell/test/chrome/test_bug293235.xhtml38
-rw-r--r--docshell/test/chrome/test_bug294258.xhtml38
-rw-r--r--docshell/test/chrome/test_bug298622.xhtml38
-rw-r--r--docshell/test/chrome/test_bug301397.xhtml38
-rw-r--r--docshell/test/chrome/test_bug303267.xhtml39
-rw-r--r--docshell/test/chrome/test_bug311007.xhtml42
-rw-r--r--docshell/test/chrome/test_bug321671.xhtml38
-rw-r--r--docshell/test/chrome/test_bug360511.xhtml39
-rw-r--r--docshell/test/chrome/test_bug364461.xhtml43
-rw-r--r--docshell/test/chrome/test_bug396519.xhtml28
-rw-r--r--docshell/test/chrome/test_bug396649.xhtml41
-rw-r--r--docshell/test/chrome/test_bug428288.html37
-rw-r--r--docshell/test/chrome/test_bug449778.xhtml29
-rw-r--r--docshell/test/chrome/test_bug449780.xhtml29
-rw-r--r--docshell/test/chrome/test_bug453650.xhtml121
-rw-r--r--docshell/test/chrome/test_bug454235.xhtml55
-rw-r--r--docshell/test/chrome/test_bug456980.xhtml29
-rw-r--r--docshell/test/chrome/test_bug565388.xhtml83
-rw-r--r--docshell/test/chrome/test_bug582176.xhtml38
-rw-r--r--docshell/test/chrome/test_bug608669.xhtml81
-rw-r--r--docshell/test/chrome/test_bug662200.xhtml38
-rw-r--r--docshell/test/chrome/test_bug690056.xhtml26
-rw-r--r--docshell/test/chrome/test_bug789773.xhtml69
-rw-r--r--docshell/test/chrome/test_bug846906.xhtml95
-rw-r--r--docshell/test/chrome/test_bug89419.xhtml38
-rw-r--r--docshell/test/chrome/test_bug909218.html117
-rw-r--r--docshell/test/chrome/test_bug92598.xhtml37
-rw-r--r--docshell/test/chrome/test_docRedirect.sjs5
-rw-r--r--docshell/test/chrome/test_docRedirect.xhtml93
-rw-r--r--docshell/test/chrome/test_mozFrameType.xhtml42
-rw-r--r--docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml159
-rw-r--r--docshell/test/chrome/window.template.txt44
-rw-r--r--docshell/test/iframesandbox/.eslintrc.js5
-rw-r--r--docshell/test/iframesandbox/file_child_navigation_by_location.html1
-rw-r--r--docshell/test/iframesandbox/file_marquee_event_handlers.html17
-rw-r--r--docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_parent_navigation_by_location.html18
-rw-r--r--docshell/test/iframesandbox/file_sibling_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_location.html20
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html27
-rw-r--r--docshell/test/iframesandbox/mochitest.ini24
-rw-r--r--docshell/test/iframesandbox/test_child_navigation_by_location.html91
-rw-r--r--docshell/test/iframesandbox/test_marquee_event_handlers.html95
-rw-r--r--docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html80
-rw-r--r--docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html84
-rw-r--r--docshell/test/iframesandbox/test_parent_navigation_by_location.html75
-rw-r--r--docshell/test/iframesandbox/test_sibling_navigation_by_location.html78
-rw-r--r--docshell/test/iframesandbox/test_top_navigation_by_location.html167
-rw-r--r--docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html204
-rw-r--r--docshell/test/mochitest/bug1422334_redirect.html3
-rw-r--r--docshell/test/mochitest/bug1422334_redirect.html^headers^2
-rw-r--r--docshell/test/mochitest/bug404548-subframe.html7
-rw-r--r--docshell/test/mochitest/bug404548-subframe_window.html1
-rw-r--r--docshell/test/mochitest/bug413310-post.sjs7
-rw-r--r--docshell/test/mochitest/bug413310-subframe.html7
-rw-r--r--docshell/test/mochitest/bug529119-window.html7
-rw-r--r--docshell/test/mochitest/bug530396-noref.sjs20
-rw-r--r--docshell/test/mochitest/bug530396-subframe.html7
-rw-r--r--docshell/test/mochitest/bug570341_recordevents.html21
-rw-r--r--docshell/test/mochitest/bug668513_redirect.html1
-rw-r--r--docshell/test/mochitest/bug668513_redirect.html^headers^2
-rw-r--r--docshell/test/mochitest/bug691547_frame.html12
-rw-r--r--docshell/test/mochitest/clicker.html7
-rw-r--r--docshell/test/mochitest/double_submit.sjs122
-rw-r--r--docshell/test/mochitest/dummy_page.html6
-rw-r--r--docshell/test/mochitest/file_anchor_scroll_after_document_open.html15
-rw-r--r--docshell/test/mochitest/file_bfcache_plus_hash_1.html24
-rw-r--r--docshell/test/mochitest/file_bfcache_plus_hash_2.html17
-rw-r--r--docshell/test/mochitest/file_bug1121701_1.html1
-rw-r--r--docshell/test/mochitest/file_bug1121701_2.html1
-rw-r--r--docshell/test/mochitest/file_bug1151421.html19
-rw-r--r--docshell/test/mochitest/file_bug1186774.html1
-rw-r--r--docshell/test/mochitest/file_bug1450164.html16
-rw-r--r--docshell/test/mochitest/file_bug385434_1.html29
-rw-r--r--docshell/test/mochitest/file_bug385434_2.html26
-rw-r--r--docshell/test/mochitest/file_bug385434_3.html22
-rw-r--r--docshell/test/mochitest/file_bug475636.sjs90
-rw-r--r--docshell/test/mochitest/file_bug509055.html9
-rw-r--r--docshell/test/mochitest/file_bug511449.html6
-rw-r--r--docshell/test/mochitest/file_bug540462.html25
-rw-r--r--docshell/test/mochitest/file_bug580069_1.html8
-rw-r--r--docshell/test/mochitest/file_bug580069_2.sjs5
-rw-r--r--docshell/test/mochitest/file_bug590573_1.html8
-rw-r--r--docshell/test/mochitest/file_bug590573_2.html8
-rw-r--r--docshell/test/mochitest/file_bug598895_1.html1
-rw-r--r--docshell/test/mochitest/file_bug598895_2.html1
-rw-r--r--docshell/test/mochitest/file_bug634834.html5
-rw-r--r--docshell/test/mochitest/file_bug637644_1.html1
-rw-r--r--docshell/test/mochitest/file_bug637644_2.html1
-rw-r--r--docshell/test/mochitest/file_bug640387.html26
-rw-r--r--docshell/test/mochitest/file_bug653741.html13
-rw-r--r--docshell/test/mochitest/file_bug6604045
-rw-r--r--docshell/test/mochitest/file_bug660404-1.html1
-rw-r--r--docshell/test/mochitest/file_bug660404^headers^1
-rw-r--r--docshell/test/mochitest/file_bug662170.html13
-rw-r--r--docshell/test/mochitest/file_bug668513.html101
-rw-r--r--docshell/test/mochitest/file_bug669671.sjs14
-rw-r--r--docshell/test/mochitest/file_bug675587.html1
-rw-r--r--docshell/test/mochitest/file_bug680257.html16
-rw-r--r--docshell/test/mochitest/file_bug703855.html2
-rw-r--r--docshell/test/mochitest/file_bug728939.html3
-rw-r--r--docshell/test/mochitest/file_close_onpagehide1.html5
-rw-r--r--docshell/test/mochitest/file_close_onpagehide2.html5
-rw-r--r--docshell/test/mochitest/file_compressed_multipartbin0 -> 111 bytes
-rw-r--r--docshell/test/mochitest/file_compressed_multipart^headers^2
-rw-r--r--docshell/test/mochitest/file_framedhistoryframes.html16
-rw-r--r--docshell/test/mochitest/file_history_length_during_pageload.html17
-rw-r--r--docshell/test/mochitest/file_history_length_during_pageload_2.html20
-rw-r--r--docshell/test/mochitest/file_pushState_after_document_open.html11
-rw-r--r--docshell/test/mochitest/file_redirect_history.html18
-rw-r--r--docshell/test/mochitest/form_submit.sjs40
-rw-r--r--docshell/test/mochitest/form_submit_redirect.sjs10
-rw-r--r--docshell/test/mochitest/historyframes.html176
-rw-r--r--docshell/test/mochitest/mochitest.ini145
-rw-r--r--docshell/test/mochitest/ping.html6
-rw-r--r--docshell/test/mochitest/start_historyframe.html1
-rw-r--r--docshell/test/mochitest/test_anchor_scroll_after_document_open.html55
-rw-r--r--docshell/test/mochitest/test_bfcache_plus_hash.html125
-rw-r--r--docshell/test/mochitest/test_bug1045096.html29
-rw-r--r--docshell/test/mochitest/test_bug1121701.html71
-rw-r--r--docshell/test/mochitest/test_bug1151421.html61
-rw-r--r--docshell/test/mochitest/test_bug1186774.html51
-rw-r--r--docshell/test/mochitest/test_bug1422334.html40
-rw-r--r--docshell/test/mochitest/test_bug1450164.html31
-rw-r--r--docshell/test/mochitest/test_bug1507702.html57
-rw-r--r--docshell/test/mochitest/test_bug1645781.html89
-rw-r--r--docshell/test/mochitest/test_bug385434.html211
-rw-r--r--docshell/test/mochitest/test_bug387979.html52
-rw-r--r--docshell/test/mochitest/test_bug402210.html50
-rw-r--r--docshell/test/mochitest/test_bug404548.html39
-rw-r--r--docshell/test/mochitest/test_bug413310.html106
-rw-r--r--docshell/test/mochitest/test_bug475636.html52
-rw-r--r--docshell/test/mochitest/test_bug509055.html116
-rw-r--r--docshell/test/mochitest/test_bug511449.html56
-rw-r--r--docshell/test/mochitest/test_bug529119-1.html110
-rw-r--r--docshell/test/mochitest/test_bug529119-2.html115
-rw-r--r--docshell/test/mochitest/test_bug530396.html56
-rw-r--r--docshell/test/mochitest/test_bug540462.html44
-rw-r--r--docshell/test/mochitest/test_bug551225.html32
-rw-r--r--docshell/test/mochitest/test_bug570341.html142
-rw-r--r--docshell/test/mochitest/test_bug580069.html58
-rw-r--r--docshell/test/mochitest/test_bug590573.html206
-rw-r--r--docshell/test/mochitest/test_bug598895.html52
-rw-r--r--docshell/test/mochitest/test_bug634834.html51
-rw-r--r--docshell/test/mochitest/test_bug637644.html52
-rw-r--r--docshell/test/mochitest/test_bug640387_1.html107
-rw-r--r--docshell/test/mochitest/test_bug640387_2.html89
-rw-r--r--docshell/test/mochitest/test_bug653741.html49
-rw-r--r--docshell/test/mochitest/test_bug660404.html49
-rw-r--r--docshell/test/mochitest/test_bug662170.html51
-rw-r--r--docshell/test/mochitest/test_bug668513.html28
-rw-r--r--docshell/test/mochitest/test_bug669671.html145
-rw-r--r--docshell/test/mochitest/test_bug675587.html33
-rw-r--r--docshell/test/mochitest/test_bug680257.html76
-rw-r--r--docshell/test/mochitest/test_bug691547.html59
-rw-r--r--docshell/test/mochitest/test_bug694612.html34
-rw-r--r--docshell/test/mochitest/test_bug703855.html79
-rw-r--r--docshell/test/mochitest/test_bug728939.html37
-rw-r--r--docshell/test/mochitest/test_bug797909.html66
-rw-r--r--docshell/test/mochitest/test_close_onpagehide_by_history_back.html24
-rw-r--r--docshell/test/mochitest/test_close_onpagehide_by_window_close.html20
-rw-r--r--docshell/test/mochitest/test_compressed_multipart.html41
-rw-r--r--docshell/test/mochitest/test_double_submit.html108
-rw-r--r--docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html57
-rw-r--r--docshell/test/mochitest/test_framedhistoryframes.html32
-rw-r--r--docshell/test/mochitest/test_history_length_during_pageload.html43
-rw-r--r--docshell/test/mochitest/test_iframe_srcdoc_to_remote.html43
-rw-r--r--docshell/test/mochitest/test_navigate_after_pagehide.html33
-rw-r--r--docshell/test/mochitest/test_pushState_after_document_open.html39
-rw-r--r--docshell/test/mochitest/test_redirect_history.html57
-rw-r--r--docshell/test/mochitest/test_triggeringprincipal_location_seturi.html104
-rw-r--r--docshell/test/mochitest/test_windowedhistoryframes.html32
-rw-r--r--docshell/test/mochitest/url1_historyframe.html1
-rw-r--r--docshell/test/mochitest/url2_historyframe.html1
-rw-r--r--docshell/test/moz.build137
-rw-r--r--docshell/test/navigation/.eslintrc.js11
-rw-r--r--docshell/test/navigation/NavigationUtils.js203
-rw-r--r--docshell/test/navigation/blank.html1
-rw-r--r--docshell/test/navigation/bluebox_bug430723.html6
-rw-r--r--docshell/test/navigation/browser.ini15
-rw-r--r--docshell/test/navigation/browser_bug343515.js267
-rw-r--r--docshell/test/navigation/browser_ghistorymaxsize_is_0.js81
-rw-r--r--docshell/test/navigation/browser_test-content-chromeflags.js57
-rw-r--r--docshell/test/navigation/browser_test_bfcache_eviction.js96
-rw-r--r--docshell/test/navigation/bug343515_pg1.html5
-rw-r--r--docshell/test/navigation/bug343515_pg2.html7
-rw-r--r--docshell/test/navigation/bug343515_pg3.html7
-rw-r--r--docshell/test/navigation/bug343515_pg3_1.html6
-rw-r--r--docshell/test/navigation/bug343515_pg3_1_1.html1
-rw-r--r--docshell/test/navigation/bug343515_pg3_2.html1
-rw-r--r--docshell/test/navigation/file_bug1300461.html61
-rw-r--r--docshell/test/navigation/file_bug1300461_back.html37
-rw-r--r--docshell/test/navigation/file_bug1300461_redirect.html10
-rw-r--r--docshell/test/navigation/file_bug1300461_redirect.html^headers^2
-rw-r--r--docshell/test/navigation/file_bug1326251.html190
-rw-r--r--docshell/test/navigation/file_bug1326251_evict_cache.html27
-rw-r--r--docshell/test/navigation/file_bug1364364-1.html33
-rw-r--r--docshell/test/navigation/file_bug1364364-2.html14
-rw-r--r--docshell/test/navigation/file_bug1375833-frame1.html8
-rw-r--r--docshell/test/navigation/file_bug1375833-frame2.html8
-rw-r--r--docshell/test/navigation/file_bug1375833.html22
-rw-r--r--docshell/test/navigation/file_bug1379762-1.html35
-rw-r--r--docshell/test/navigation/file_bug1536471.html8
-rw-r--r--docshell/test/navigation/file_bug1609475.html51
-rw-r--r--docshell/test/navigation/file_bug386782_contenteditable.html1
-rw-r--r--docshell/test/navigation/file_bug386782_designmode.html1
-rw-r--r--docshell/test/navigation/file_bug462076_1.html55
-rw-r--r--docshell/test/navigation/file_bug462076_2.html52
-rw-r--r--docshell/test/navigation/file_bug462076_3.html52
-rw-r--r--docshell/test/navigation/file_bug508537_1.html33
-rw-r--r--docshell/test/navigation/file_bug534178.html30
-rw-r--r--docshell/test/navigation/file_contentpolicy_block_window.html5
-rw-r--r--docshell/test/navigation/file_docshell_gotoindex.html42
-rw-r--r--docshell/test/navigation/file_document_write_1.html18
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load.html27
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load_frame1.html6
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs19
-rw-r--r--docshell/test/navigation/file_load_history_entry_page_with_one_link.html7
-rw-r--r--docshell/test/navigation/file_load_history_entry_page_with_two_links.html9
-rw-r--r--docshell/test/navigation/file_nested_frames.html27
-rw-r--r--docshell/test/navigation/file_nested_frames_innerframe.html1
-rw-r--r--docshell/test/navigation/file_scrollRestoration.html153
-rw-r--r--docshell/test/navigation/file_shiftReload_and_pushState.html28
-rw-r--r--docshell/test/navigation/file_static_and_dynamic_1.html31
-rw-r--r--docshell/test/navigation/file_tell_opener.html8
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_frame_1.html27
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_frame_2.html8
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html15
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe.html15
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe_nav.html21
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html20
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_window_open.html6
-rw-r--r--docshell/test/navigation/frame0.html3
-rw-r--r--docshell/test/navigation/frame1.html3
-rw-r--r--docshell/test/navigation/frame2.html3
-rw-r--r--docshell/test/navigation/frame3.html3
-rw-r--r--docshell/test/navigation/goback.html5
-rw-r--r--docshell/test/navigation/iframe.html8
-rw-r--r--docshell/test/navigation/iframe_static.html8
-rw-r--r--docshell/test/navigation/mochitest.ini122
-rw-r--r--docshell/test/navigation/navigate.html37
-rw-r--r--docshell/test/navigation/navigation_target_popup_url.html1
-rw-r--r--docshell/test/navigation/navigation_target_url.html1
-rw-r--r--docshell/test/navigation/open.html9
-rw-r--r--docshell/test/navigation/parent.html14
-rw-r--r--docshell/test/navigation/redbox_bug430723.html6
-rw-r--r--docshell/test/navigation/test_aboutblank_change_process.html46
-rw-r--r--docshell/test/navigation/test_bug1300461.html75
-rw-r--r--docshell/test/navigation/test_bug1326251.html74
-rw-r--r--docshell/test/navigation/test_bug1364364.html56
-rw-r--r--docshell/test/navigation/test_bug1375833.html136
-rw-r--r--docshell/test/navigation/test_bug1379762.html64
-rw-r--r--docshell/test/navigation/test_bug13871.html81
-rw-r--r--docshell/test/navigation/test_bug145971.html29
-rw-r--r--docshell/test/navigation/test_bug1536471.html75
-rw-r--r--docshell/test/navigation/test_bug1609475.html35
-rw-r--r--docshell/test/navigation/test_bug270414.html95
-rw-r--r--docshell/test/navigation/test_bug278916.html37
-rw-r--r--docshell/test/navigation/test_bug279495.html44
-rw-r--r--docshell/test/navigation/test_bug344861.html35
-rw-r--r--docshell/test/navigation/test_bug386782.html132
-rw-r--r--docshell/test/navigation/test_bug430624.html57
-rw-r--r--docshell/test/navigation/test_bug430723.html124
-rw-r--r--docshell/test/navigation/test_child.html47
-rw-r--r--docshell/test/navigation/test_contentpolicy_block_window.html102
-rw-r--r--docshell/test/navigation/test_docshell_gotoindex.html29
-rw-r--r--docshell/test/navigation/test_dynamic_frame_forward_back.html35
-rw-r--r--docshell/test/navigation/test_fragment_handling_during_load.html35
-rw-r--r--docshell/test/navigation/test_grandchild.html47
-rw-r--r--docshell/test/navigation/test_load_history_entry.html196
-rw-r--r--docshell/test/navigation/test_nested_frames.html35
-rw-r--r--docshell/test/navigation/test_not-opener.html53
-rw-r--r--docshell/test/navigation/test_opener.html52
-rw-r--r--docshell/test/navigation/test_performance_navigation.html38
-rw-r--r--docshell/test/navigation/test_popup-navigates-children.html69
-rw-r--r--docshell/test/navigation/test_rate_limit_location_change.html96
-rw-r--r--docshell/test/navigation/test_reserved.html92
-rw-r--r--docshell/test/navigation/test_scrollRestoration.html44
-rw-r--r--docshell/test/navigation/test_session_history_entry_cleanup.html35
-rw-r--r--docshell/test/navigation/test_sessionhistory.html48
-rw-r--r--docshell/test/navigation/test_sessionhistory_document_write.html34
-rw-r--r--docshell/test/navigation/test_shiftReload_and_pushState.html35
-rw-r--r--docshell/test/navigation/test_sibling-matching-parent.html46
-rw-r--r--docshell/test/navigation/test_sibling-off-domain.html46
-rw-r--r--docshell/test/navigation/test_static_and_dynamic.html36
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_frame_nav.html69
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html62
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html87
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html70
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_window_open.html79
-rw-r--r--docshell/test/unit/head_docshell.js95
-rw-r--r--docshell/test/unit/test_URIFixup.js123
-rw-r--r--docshell/test/unit/test_URIFixup_info.js932
-rw-r--r--docshell/test/unit/test_URIFixup_search.js142
-rw-r--r--docshell/test/unit/test_browsing_context_structured_clone.js68
-rw-r--r--docshell/test/unit/test_bug442584.js35
-rw-r--r--docshell/test/unit/test_pb_notification.js18
-rw-r--r--docshell/test/unit/test_privacy_transition.js21
-rw-r--r--docshell/test/unit/xpcshell.ini16
-rw-r--r--docshell/test/unit_ipc/test_pb_notification_ipc.js17
-rw-r--r--docshell/test/unit_ipc/xpcshell.ini7
678 files changed, 70827 insertions, 0 deletions
diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp
new file mode 100644
index 0000000000..b39c33bbae
--- /dev/null
+++ b/docshell/base/BaseHistory.cpp
@@ -0,0 +1,249 @@
+/* -*- 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) {
+ mPendingQueries.PutEntry(aURI);
+ if (mStartPendingVisitedQueriesScheduled) {
+ return;
+ }
+ mStartPendingVisitedQueriesScheduled =
+ NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ "BaseHistory::StartPendingVisitedQueries",
+ [self = RefPtr<BaseHistory>(this)] {
+ self->mStartPendingVisitedQueriesScheduled = false;
+ auto queries = std::move(self->mPendingQueries);
+ self->StartPendingVisitedQueries(queries);
+ MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty());
+ }),
+ EventQueuePriority::Idle));
+}
+
+void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) {
+ mPendingQueries.RemoveEntry(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!");
+ if (XRE_IsContentProcess()) {
+ MOZ_ASSERT(aLink, "Must pass a non-null Link!");
+ }
+
+ if (!CanStore(aURI)) {
+ if (aLink) {
+ aLink->VisitedQueryFinished(/* visited = */ false);
+ }
+ return;
+ }
+
+ // Obtain our array of observers for this URI.
+ auto entry = mTrackedURIs.LookupForAdd(aURI);
+ MOZ_DIAGNOSTIC_ASSERT(!entry || !entry.Data().mLinks.IsEmpty(),
+ "An empty key was kept around in our hashtable!");
+ if (!entry) {
+ ScheduleVisitedQuery(aURI);
+ }
+
+ if (!aLink) {
+ // In IPC builds, we are passed a nullptr Link from
+ // ContentParent::RecvStartVisitedQuery. All of our code after this point
+ // assumes aLink is non-nullptr, so we have to return now.
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
+ "We should only ever get a null Link "
+ "in the parent process!");
+ // We don't want to remove if we're tracking other links.
+ if (!entry) {
+ entry.OrRemove();
+ }
+ return;
+ }
+
+ ObservingLinks& links = entry.OrInsert([] { return ObservingLinks{}; });
+
+ // 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!");
+ // FIXME(emilio): We should consider changing this (see the entry.Remove()
+ // call in NotifyVisitedInThisProcess).
+ MOZ_DIAGNOSTIC_ASSERT(links.mStatus != VisitedStatus::Visited,
+ "We don't keep tracking known-visited links");
+
+ 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:
+ if (!StaticPrefs::layout_css_notify_of_unvisited()) {
+ break;
+ }
+ [[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.Data().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) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
+
+ if (aStatus == VisitedStatus::Unvisited &&
+ !StaticPrefs::layout_css_notify_of_unvisited()) {
+ return;
+ }
+
+ NotifyVisitedInThisProcess(aURI, aStatus);
+ if (XRE_IsParentProcess()) {
+ NotifyVisitedFromParent(aURI, aStatus);
+ }
+}
+
+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);
+ }
+
+ // We never go from visited -> unvisited.
+ //
+ // FIXME(emilio): It seems unfortunate to remove a link to a visited uri and
+ // then re-add it to the document to trigger a new visited query. It shouldn't
+ // if we keep track of mStatus.
+ if (visited) {
+ entry.Remove();
+ }
+}
+
+void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
+ MOZ_ASSERT(!mPendingResults.IsEmpty());
+
+ mStartPendingResultsScheduled = false;
+
+ auto results = std::move(mPendingResults);
+ MOZ_ASSERT(mPendingResults.IsEmpty());
+
+ nsTArray<ContentParent*> cplist;
+ ContentParent::GetAll(cplist);
+ for (ContentParent* cp : cplist) {
+ Unused << NS_WARN_IF(!cp->SendNotifyVisited(results));
+ }
+}
+
+void BaseHistory::NotifyVisitedFromParent(nsIURI* aURI, VisitedStatus aStatus) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto& result = *mPendingResults.AppendElement();
+ result.visited() = aStatus == VisitedStatus::Visited;
+ result.uri() = aURI;
+
+ 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..941ec29972
--- /dev/null
+++ b/docshell/base/BaseHistory.h
@@ -0,0 +1,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_BaseHistory_h
+#define mozilla_BaseHistory_h
+
+#include "IHistory.h"
+#include "mozilla/dom/ContentParent.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 UnregisterVisitedCallback(nsIURI*, dom::Link*) final;
+ void NotifyVisited(nsIURI*, VisitedStatus) 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);
+ static constexpr const size_t kTrackedUrisInitialSize = 64;
+
+ BaseHistory();
+ ~BaseHistory();
+
+ using ObserverArray = nsTObserverArray<dom::Link*>;
+ struct ObservingLinks {
+ ObserverArray mLinks;
+ VisitedStatus mStatus = VisitedStatus::Unknown;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ };
+
+ using PendingVisitedQueries = nsTHashtable<nsURIHashKey>;
+ using PendingVisitedResults = nsTArray<mozilla::dom::VisitedQueryResult>;
+
+ // Starts all the queries in the pending queries list, potentially at the same
+ // time.
+ virtual void StartPendingVisitedQueries(const PendingVisitedQueries&) = 0;
+
+ private:
+ void ScheduleVisitedQuery(nsIURI*);
+
+ // 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.
+ nsDataHashtable<nsURIHashKey, ObservingLinks> 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..0d9cce338f
--- /dev/null
+++ b/docshell/base/BrowsingContext.cpp
@@ -0,0 +1,3301 @@
+/* -*- 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/ProxyAccessibleBase.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/dom/CanonicalBrowsingContext.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/PopupBlocker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/UserActivationIPCUtils.h"
+#include "mozilla/dom/WindowBinding.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/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_page_load.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIURIFixup.h"
+#include "nsIXULRuntime.h"
+
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindowOuter.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<mozilla::dom::OrientationType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::OrientationType,
+ mozilla::dom::OrientationType::Portrait_primary,
+ mozilla::dom::OrientationType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::DisplayMode>
+ : public ContiguousEnumSerializer<mozilla::dom::DisplayMode,
+ mozilla::dom::DisplayMode::Browser,
+ mozilla::dom::DisplayMode::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::ExplicitActiveStatus,
+ mozilla::dom::ExplicitActiveStatus::None,
+ mozilla::dom::ExplicitActiveStatus::EndGuard_> {};
+
+// Allow serialization and deserialization of TouchEventsOverride over IPC
+template <>
+struct ParamTraits<mozilla::dom::TouchEventsOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::TouchEventsOverride,
+ mozilla::dom::TouchEventsOverride::Disabled,
+ mozilla::dom::TouchEventsOverride::EndGuard_> {};
+} // 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<BrowsingContext>;
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+static LazyLogModule gBrowsingContextLog("BrowsingContext");
+
+typedef nsDataHashtable<nsUint64HashKey, BrowsingContext*> BrowsingContextMap;
+
+// All BrowsingContexts indexed by Id
+static StaticAutoPtr<BrowsingContextMap> sBrowsingContexts;
+// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id
+static StaticAutoPtr<BrowsingContextMap> 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->Put(aBrowsingContext->Id(), aBrowsingContext);
+ if (aBrowsingContext->IsTopContent()) {
+ sCurrentTopByBrowserId->Put(aBrowsingContext->BrowserId(),
+ aBrowsingContext);
+ }
+
+ aBrowsingContext->Group()->Register(aBrowsingContext);
+}
+
+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;
+}
+
+int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) {
+ int32_t index = -1;
+ for (BrowsingContext* child : Children()) {
+ ++index;
+ if (child == aChild) {
+ break;
+ }
+ }
+ return index;
+}
+
+WindowContext* BrowsingContext::GetTopWindowContext() {
+ 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 */
+already_AddRefed<BrowsingContext> BrowsingContext::Get(uint64_t aId) {
+ return do_AddRef(sBrowsingContexts->Get(aId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::GetCurrentTopByBrowserId(
+ uint64_t aBrowserId) {
+ return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> 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> BrowsingContext::CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ 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<BrowsingContext> parentBC =
+ aParent ? aParent->GetBrowsingContext() : nullptr;
+ RefPtr<WindowContext> parentWC =
+ aParent ? aParent->GetWindowContext() : nullptr;
+ BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener;
+
+ // Determine which BrowsingContextGroup this context should be created in.
+ RefPtr<BrowsingContextGroup> 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.mName = 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.mOpenerId = aOpener->Id();
+ fields.mHadOriginalOpener = true;
+ }
+
+ if (aParent) {
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group);
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType);
+ fields.mEmbedderInnerWindowId = aParent->WindowID();
+
+ // 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.mAncestorLoading =
+ parentBC->GetAncestorLoading() ||
+ readystate == Document::ReadyState::READYSTATE_LOADING ||
+ readystate == Document::ReadyState::READYSTATE_INTERACTIVE;
+ }
+
+ fields.mBrowserId =
+ parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId();
+
+ fields.mOpenerPolicy = 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.mOpenerPolicy = aOpener->Top()->GetOpenerPolicy();
+ } 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);
+ }
+
+ nsContentUtils::GenerateUUIDInPlace(fields.mHistoryID);
+ fields.mExplicitActive = [&] {
+ 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.mFullZoom = parentBC ? parentBC->FullZoom() : 1.0f;
+ fields.mTextZoom = parentBC ? parentBC->TextZoom() : 1.0f;
+
+ bool allowContentRetargeting =
+ inherit ? inherit->GetAllowContentRetargetingOnChildren() : true;
+ fields.mAllowContentRetargeting = allowContentRetargeting;
+ fields.mAllowContentRetargetingOnChildren = allowContentRetargeting;
+
+ // Assume top allows fullscreen for its children unless otherwise stated.
+ // Subframes start with it false unless otherwise noted in SetEmbedderElement.
+ fields.mFullscreenAllowedByOwner = !aParent;
+
+ fields.mAllowPlugins = inherit ? inherit->GetAllowPlugins() : true;
+
+ fields.mDefaultLoadFlags =
+ inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL;
+
+ fields.mOrientationLock = mozilla::hal::eScreenOrientation_None;
+
+ fields.mUseGlobalHistory = inherit ? inherit->GetUseGlobalHistory() : false;
+
+ fields.mUseErrorPages = true;
+
+ fields.mTouchEventsOverrideInternal = TouchEventsOverride::None;
+
+ RefPtr<BrowsingContext> 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<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ nsCOMPtr<nsIRequestContext> requestContext;
+ nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext));
+ if (NS_SUCCEEDED(rv) && requestContext) {
+ context->mRequestContextId = requestContext->GetID();
+ }
+ }
+
+ return context.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
+ Type aType) {
+ RefPtr<BrowsingContext> bc(
+ CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType));
+ 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 */
+void 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<WindowContext> parent = aInit.GetParent();
+
+ RefPtr<BrowsingContext> 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;
+ 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);
+
+ 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) {
+ 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);
+ }
+}
+
+// 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<JSObject*> 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 (current->IsCached()) {
+ 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<CanonicalBrowsingContext> 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()));
+ if (nsCOMPtr<nsPIDOMWindowInner> 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(
+ kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
+ txn.SetUseGlobalHistory(useGlobalHistory);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(txn.Commit(this));
+ }
+
+ mEmbedderElement = aEmbedder;
+}
+
+void BrowsingContext::Embed() {
+ if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) {
+ frame->BindToBrowsingContext(this);
+ }
+}
+
+void BrowsingContext::Attach(bool aFromIPC, ContentParent* aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEverAttached);
+ 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);
+
+ AssertCoherentLoadContext();
+
+ // Add ourselves either to our parent or BrowsingContextGroup's child list.
+ 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");
+ }
+
+ mParentWindow->AppendChildBrowsingContext(this);
+ } else {
+ mGroup->Toplevels().AppendElement(this);
+ }
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+
+ if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) {
+ CreateChildSHistory();
+ }
+
+ if (XRE_IsContentProcess() && !aFromIPC) {
+ // Send attach to our parent if we need to.
+ ContentChild::GetSingleton()->SendCreateBrowsingContext(
+ mGroup->Id(), GetIPCInitializer());
+ } else if (XRE_IsParentProcess()) {
+ 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 (IsTopContent() && !Canonical()->GetWebProgress()) {
+ Canonical()->mWebProgress = new BrowsingContextWebProgress();
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached",
+ nullptr);
+ }
+}
+
+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);
+
+ nsCOMPtr<nsIRequestContextService> 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)) {
+ return;
+ }
+
+ if (mParentWindow) {
+ mParentWindow->RemoveChildBrowsingContext(this);
+ } else {
+ mGroup->Toplevels().RemoveElement(this);
+ }
+
+ auto callSendDiscard = [&](auto* aActor) {
+ // 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();
+ auto callback = [self = RefPtr{this}](auto) {
+ self->mGroup->RemoveKeepAlive();
+ };
+
+ aActor->SendDiscardBrowsingContext(this, callback, callback);
+ };
+
+ if (XRE_IsParentProcess()) {
+ 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.
+ if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID()) &&
+ !Canonical()->IsOwnedByProcess(aParent->ChildID())) {
+ callSendDiscard(aParent);
+ }
+ });
+ } else if (!aFromIPC) {
+ callSendDiscard(ContentChild::GetSingleton());
+ }
+
+ mGroup->Unregister(this);
+ UnregisterBrowserId(this);
+ mIsDiscarded = true;
+
+ if (XRE_IsParentProcess()) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->BrowsingContextDetached(this);
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyObservers(ToSupports(this), "browsing-context-discarded",
+ nullptr);
+ }
+
+ // NOTE: Doesn't use SetClosed, as it will be set in all processes
+ // automatically by calls to Detach()
+ mFields.SetWithoutSyncing<IDX_Closed>(true);
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::UnregisterOpenPopupSpam();
+ // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
+ // automatically.
+ mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
+ }
+
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->CanonicalDiscard();
+ }
+}
+
+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::IsCached() const {
+ return mParentWindow && mParentWindow->IsCached();
+}
+
+bool BrowsingContext::IsTargetable() const {
+ return !GetClosed() && !mIsDiscarded && !IsCached();
+}
+
+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->IsCached() || wc->IsDiscarded()) {
+ return false;
+ }
+
+ bc = wc->GetBrowsingContext();
+ } else {
+ return true;
+ }
+ }
+}
+
+Span<RefPtr<BrowsingContext>> BrowsingContext::Children() const {
+ if (WindowContext* current = mCurrentWindowContext) {
+ return current->Children();
+ }
+ return Span<RefPtr<BrowsingContext>>();
+}
+
+void BrowsingContext::GetChildren(
+ nsTArray<RefPtr<BrowsingContext>>& aChildren) {
+ aChildren.AppendElements(Children());
+}
+
+void BrowsingContext::GetWindowContexts(
+ nsTArray<RefPtr<WindowContext>>& 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<IDX_CurrentInnerWindowId>());
+ 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<IDX_CurrentInnerWindowId>());
+ MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr);
+ }
+}
+
+void BrowsingContext::PreOrderWalk(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ aCallback(this);
+
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PreOrderWalk(aCallback);
+ }
+}
+
+void BrowsingContext::PostOrderWalk(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PostOrderWalk(aCallback);
+ }
+
+ aCallback(this);
+}
+
+void BrowsingContext::GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ aBrowsingContexts.AppendElement(aContext);
+ });
+}
+
+// FindWithName follows the rules for choosing a browsing context,
+// with the exception of sandboxing for iframes. The implementation
+// for arbitrarily choosing between two browsing contexts with the
+// same name is as follows:
+//
+// 1) The start browsing context, i.e. 'this'
+// 2) Descendants in insertion order
+// 3) The parent
+// 4) Siblings and their children, both in insertion order
+// 5) After this we iteratively follow the parent chain, repeating 3
+// and 4 until
+// 6) If there is no parent, consider all other top level browsing
+// contexts and their children, both in insertion order
+//
+// See
+// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
+BrowsingContext* BrowsingContext::FindWithName(
+ const nsAString& aName, bool aUseEntryGlobalForAccessCheck) {
+ RefPtr<BrowsingContext> requestingContext = this;
+ if (aUseEntryGlobalForAccessCheck) {
+ if (nsCOMPtr<nsIDocShell> caller = do_GetInterface(GetEntryGlobal())) {
+ if (caller->GetBrowsingContext()) {
+ requestingContext = caller->GetBrowsingContext();
+ }
+ }
+ }
+
+ BrowsingContext* found = nullptr;
+ if (aName.IsEmpty()) {
+ // You can't find a browsing context with an empty name.
+ found = nullptr;
+ } else if (aName.LowerCaseEqualsLiteral("_blank")) {
+ // Just return null. Caller must handle creating a new window with
+ // a blank name.
+ found = nullptr;
+ } else if (nsContentUtils::IsSpecialName(aName)) {
+ found = FindWithSpecialName(aName, *requestingContext);
+ } else if (BrowsingContext* child =
+ FindWithNameInSubtree(aName, *requestingContext)) {
+ found = child;
+ } else {
+ BrowsingContext* current = this;
+
+ do {
+ Span<RefPtr<BrowsingContext>> siblings;
+ BrowsingContext* parent = current->GetParent();
+
+ if (!parent) {
+ // We've reached the root of the tree, consider browsing
+ // contexts in the same browsing context group.
+ siblings = mGroup->Toplevels();
+ } else if (parent->NameEquals(aName) &&
+ requestingContext->CanAccess(parent) &&
+ parent->IsTargetable()) {
+ found = parent;
+ break;
+ } else {
+ siblings = parent->Children();
+ }
+
+ for (BrowsingContext* sibling : siblings) {
+ if (sibling == current) {
+ continue;
+ }
+
+ if (BrowsingContext* relative =
+ sibling->FindWithNameInSubtree(aName, *requestingContext)) {
+ found = relative;
+ // Breaks the outer loop
+ parent = nullptr;
+ break;
+ }
+ }
+
+ current = parent;
+ } while (current);
+ }
+
+ // Helpers should perform access control checks, which means that we
+ // only need to assert that we can access found.
+ MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanAccess(found));
+
+ return found;
+}
+
+BrowsingContext* BrowsingContext::FindChildWithName(
+ const nsAString& aName, BrowsingContext& aRequestingContext) {
+ if (aName.IsEmpty()) {
+ // You can't find a browsing context with the empty name.
+ return nullptr;
+ }
+
+ for (BrowsingContext* child : Children()) {
+ if (child->NameEquals(aName) && aRequestingContext.CanAccess(child) &&
+ child->IsTargetable()) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithSpecialName(
+ const nsAString& aName, BrowsingContext& aRequestingContext) {
+ // 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 aRequestingContext.CanAccess(parent) ? parent : nullptr;
+ }
+ return this;
+ }
+
+ if (aName.LowerCaseEqualsLiteral("_top")) {
+ BrowsingContext* top = Top();
+
+ return aRequestingContext.CanAccess(top) ? top : nullptr;
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithNameInSubtree(
+ const nsAString& aName, BrowsingContext& aRequestingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty());
+
+ if (NameEquals(aName) && aRequestingContext.CanAccess(this) &&
+ IsTargetable()) {
+ return this;
+ }
+
+ for (BrowsingContext* child : Children()) {
+ if (BrowsingContext* found =
+ child->FindWithNameInSubtree(aName, aRequestingContext)) {
+ return found;
+ }
+ }
+
+ return nullptr;
+}
+
+// For historical context, see:
+//
+// Bug 13871: Prevent frameset spoofing
+// Bug 103638: Targets with same name in different windows open in wrong
+// window with javascript
+// Bug 408052: Adopt "ancestor" frame navigation policy
+// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce
+// origin attribute isolation.
+bool BrowsingContext::CanAccess(BrowsingContext* aTarget,
+ bool aConsiderOpener) {
+ MOZ_ASSERT(
+ mDocShell,
+ "CanAccess() may only be called in the process of the accessing window");
+ MOZ_ASSERT(aTarget, "Must have a target");
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ Group() == aTarget->Group(),
+ "A BrowsingContext should never see a context from a different group");
+
+ // A frame can navigate itself and its own root.
+ if (aTarget == this || aTarget == Top()) {
+ return true;
+ }
+
+ // A frame can navigate any frame with a same-origin ancestor.
+ for (BrowsingContext* bc = aTarget; bc; bc = bc->GetParent()) {
+ if (bc->mDocShell && nsDocShell::ValidateOrigin(this, bc)) {
+ return true;
+ }
+ }
+
+ // If the target is a top-level document, a frame can navigate it if it can
+ // navigate its opener.
+ if (aConsiderOpener && !aTarget->GetParent()) {
+ if (RefPtr<BrowsingContext> opener = aTarget->GetOpener()) {
+ return CanAccess(opener, false);
+ }
+ }
+
+ return false;
+}
+
+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<Document> 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<BrowsingContext> 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.
+ if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) &&
+ mCurrentWindowContext &&
+ mCurrentWindowContext->HasValidTransientUserGestureActivation() &&
+ aTarget == Top()) {
+ return false;
+ }
+
+ // Otherwise, we are sandboxed from aTarget.
+ return true;
+}
+
+RefPtr<SessionStorageManager> BrowsingContext::GetSessionStorageManager() {
+ RefPtr<SessionStorageManager>& 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));
+ }
+}
+
+Tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ GetSavedPrincipal(mTriggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ GetSavedPrincipal(mPrincipalToInherit);
+ return MakeTuple(triggeringPrincipal, principalToInherit);
+}
+
+nsIPrincipal* BrowsingContext::GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple) {
+ if (aPrincipalTuple) {
+ nsCOMPtr<nsIPrincipal> principal;
+ uint64_t loadIdentifier;
+ 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<RefPtr<BrowsingContext>, 8> toDiscard;
+ for (const auto& entry : *sBrowsingContexts) {
+ auto* bc = entry.GetData()->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<JSObject*> 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::RootedValue 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<BrowsingContext> 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<WindowProxyHolder> 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<WindowProxyHolder> 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<JS::Value> aVal,
+ ErrorResult& aError) {
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (!ToJSValue(aCx, mOriginAttributes, aVal)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+NS_IMETHODIMP BrowsingContext::GetAssociatedWindow(
+ mozIDOMWindowProxy** aAssociatedWindow) {
+ nsCOMPtr<mozIDOMWindowProxy> 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<Element> 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);
+ }
+ }
+ 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<JS::Value> 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<BrowsingContext> 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<BrowsingContext> 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<IDX_IsPopupSpam>(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
+
+class RemoteLocationProxy
+ : public RemoteObjectProxy<BrowsingContext::LocationProxy,
+ Location_Binding::sCrossOriginProperties> {
+ public:
+ typedef RemoteObjectProxy Base;
+
+ constexpr RemoteLocationProxy()
+ : RemoteObjectProxy(prototypes::id::Location) {}
+
+ void NoteChildren(JSObject* aProxy,
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ auto location =
+ static_cast<BrowsingContext::LocationProxy*>(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<JSObject*> 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<Document> doc = wc->TopWindowContext()->GetDocument()) {
+ return doc->RemoveFromBFCacheSync();
+ }
+ }
+ return false;
+}
+
+nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) {
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (sourceBC.IsDiscarded() || (sourceBC && sourceBC->IsSandboxedFrom(this))) {
+ return NS_ERROR_DOM_INVALID_ACCESS_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) {
+ return mDocShell->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();
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group());
+ if (sourceBC && sourceBC->IsInProcess()) {
+ if (!sourceBC->CanAccess(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ if (WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild()) {
+ wgc->SendLoadURI(this, 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(),
+ aLoadState->TriggeringPrincipal());
+
+ // 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, 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) {
+ return nsDocShell::Cast(mDocShell)->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 (XRE_IsParentProcess()) {
+ ContentParent* cp = Canonical()->GetContentParent();
+ if (!cp || !cp->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
+ Unused << cp->SendInternalLoad(aLoadState);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group());
+
+ if (!sourceBC->CanAccess(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild();
+ if (!wgc || !wgc->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
+ wgc->SendInternalLoad(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;
+ mDocShell->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 (IsFrame()) {
+ // .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;
+ }
+
+ 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()) {
+ // 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 (RefPtr<Document> doc = GetExtantDocument()) {
+ // 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;
+}
+
+std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return {false, false};
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
+ BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr;
+ RefPtr<BrowsingContext> 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 = (RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
+ PopupBlocker::openBlocked);
+ }
+
+ bool isActive = false;
+ if (XRE_IsParentProcess()) {
+ RefPtr<CanonicalBrowsingContext> chromeTop =
+ Canonical()->TopCrossChromeBoundary();
+ nsCOMPtr<nsPIDOMWindowOuter> 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);
+ }
+}
+
+void BrowsingContext::Blur(ErrorResult& aError) {
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendWindowBlur(this);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendWindowBlur(this);
+ }
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetWindow() {
+ if (XRE_IsParentProcess() && !IsInProcess()) {
+ return nullptr;
+ }
+ return WindowProxyHolder(this);
+}
+
+Nullable<WindowProxyHolder> 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<JS::Value> aOpener,
+ ErrorResult& aError) const {
+ RefPtr<BrowsingContext> 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<WindowProxyHolder> BrowsingContext::GetParent(ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return nullptr;
+ }
+
+ if (GetParent()) {
+ return WindowProxyHolder(GetParent());
+ }
+ return WindowProxyHolder(this);
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> sourceBc;
+ PostMessageData data;
+ data.targetOrigin() = aTargetOrigin;
+ data.subjectPrincipal() = &aSubjectPrincipal;
+ RefPtr<nsGlobalWindowInner> 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<JS::Value> 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 contins a shared memory object, then CloneScope would
+ // return SameProcess. Otherwise, it returns DifferentProcess.
+ if (message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::DifferentProcess) {
+ ClonedMessageData clonedMessageData;
+ if (!message.BuildClonedMessageDataForChild(cc, 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.BuildClonedMessageDataForParent(cp, 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<JS::Value> 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.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<WindowContext> BrowsingContext::IPCInitializer::GetParent() {
+ RefPtr<WindowContext> parent;
+ if (mParentId != 0) {
+ parent = WindowContext::GetById(mParentId);
+ MOZ_RELEASE_ASSERT(parent);
+ }
+ return parent.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetOpener() {
+ RefPtr<BrowsingContext> 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);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>,
+ 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 defined(XP_WIN) && defined(ACCESSIBILITY)
+ if (XRE_IsParentProcess() && a11y::Compatibility::IsDolphin()) {
+ // update active accessible documents on windows
+ if (BrowserParent* bp = Canonical()->GetBrowserParent()) {
+ 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<nsIDocShell> ds = aContext->GetDocShell()) {
+ nsDocShell::Cast(ds)->ActivenessMaybeChanged();
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_HasMainMediaController>,
+ bool aNewValue, ContentParent* aSource) {
+ return IsTop() && CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_HasMainMediaController>,
+ bool aOldValue) {
+ if (!IsTop() || aOldValue == GetHasMainMediaController()) {
+ return;
+ }
+ Group()->UpdateToplevelsSuspendedIfNeeded();
+}
+
+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 a main
+ // media controller. Having a main controller in context means it might be
+ // playing media, or waiting media keys to control media (could be not playing
+ // anything currently)
+ return !IsActive() && !GetHasMainMediaController();
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_TouchEventsOverrideInternal>,
+ const enum TouchEventsOverride& aTouchEventsOverride,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_DisplayMode>,
+ const enum DisplayMode& aDisplayMOde,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>,
+ enum DisplayMode aOldValue) {
+ MOZ_ASSERT(IsTop());
+
+ if (GetDisplayMode() == aOldValue) {
+ return;
+ }
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsIDocShell* shell = aContext->GetDocShell()) {
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->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<IDX_Muted>) {
+ 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();
+ }
+ });
+}
+
+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<IDX_UserAgentOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedUserAgent();
+ }
+ });
+}
+
+void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform,
+ ErrorResult& aRv) {
+ Top()->SetPlatformOverride(aPlatform, aRv);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PlatformOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedPlatform();
+ }
+ });
+}
+
+bool BrowsingContext::CheckOnlyOwningProcessCanSet(
+ ContentParent* aSource) {
+ if (aSource) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Double-check ownership if we aren't the setter.
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) &&
+ aSource->ChildID() != Canonical()->GetInFlightProcessId()) {
+ return false;
+ }
+ } else if (!IsInProcess() && !XRE_IsParentProcess()) {
+ // Don't allow this to be set from content processes that
+ // don't own the BrowsingContext.
+ return false;
+ }
+
+ return true;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ const bool& aValue, ContentParent* aSource) {
+ // Should only be set in the parent process.
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ 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<Document> doc = aContext->GetExtantDocument()) {
+ doc->UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true);
+
+ 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, doc->GetWindow()->GetCurrentInnerWindow(),
+ isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
+ CanBubble::eYes, Cancelable::eYes, nullptr);
+ }
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_AllowPlugins>,
+ const bool& aAllowPlugins,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>,
+ const bool& aAllowed, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_UseErrorPages>,
+ const bool& aUseErrorPages,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+mozilla::dom::TouchEventsOverride BrowsingContext::TouchEventsOverride() const {
+ const BrowsingContext* bc = this;
+ while (bc) {
+ mozilla::dom::TouchEventsOverride tev =
+ bc->GetTouchEventsOverrideInternal();
+ if (tev != mozilla::dom::TouchEventsOverride::None) {
+ return tev;
+ }
+
+ bc = bc->GetParent();
+ }
+
+ return mozilla::dom::TouchEventsOverride::None;
+}
+
+void BrowsingContext::SetTouchEventsOverride(
+ const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv) {
+ SetTouchEventsOverrideInternal(aTouchEventsOverride, aRv);
+}
+
+nsresult BrowsingContext::SetTouchEventsOverride(
+ const enum TouchEventsOverride aTouchEventsOverride) {
+ ErrorResult rv;
+ SetTouchEventsOverride(aTouchEventsOverride, rv);
+ return rv.StealNSResult();
+}
+
+// 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<IDX_WatchedByDevToolsInternal>,
+ 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);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags,
+ ContentParent* aSource) {
+ // 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 CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DefaultLoadFlags>) {
+ 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<IDX_UseGlobalHistory>,
+ const bool& aUseGlobalHistory,
+ ContentParent* aSource) {
+ // Should only be set in the parent process.
+ // return XRE_IsParentProcess() && !aSource;
+ return true;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>,
+ const nsString& aUserAgent,
+ ContentParent* aSource) {
+ if (!IsTop()) {
+ return false;
+ }
+
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatform,
+ ContentParent* aSource) {
+ if (!IsTop()) {
+ return false;
+ }
+
+ return CheckOnlyOwningProcessCanSet(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<IDX_EmbedderInnerWindowId>,
+ 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<IDX_EmbedderElementType>,
+ const Maybe<nsString>&, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource) {
+ // Generally allow clearing this. We may want to be more precise about this
+ // check in the future.
+ if (aValue == 0) {
+ return true;
+ }
+
+ if (aSource) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // If in the parent process, double-check ownership and WindowGlobalParent
+ // as well.
+ RefPtr<WindowGlobalParent> wgp =
+ WindowGlobalParent::GetByInnerWindowId(aValue);
+ if (NS_WARN_IF(!wgp) || NS_WARN_IF(wgp->BrowsingContext() != this)) {
+ return false;
+ }
+
+ // Double-check ownership if we aren't the setter.
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) &&
+ aSource->ChildID() != Canonical()->GetInFlightProcessId()) {
+ return false;
+ }
+ } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) {
+ return false;
+ }
+
+ // We must have access to the specified context.
+ RefPtr<WindowContext> window = WindowContext::GetById(aValue);
+ return window && window->GetBrowsingContext() == this;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_CurrentInnerWindowId>) {
+ RefPtr<WindowContext> 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) {
+ mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true);
+ }
+ }
+ BrowserParent::UpdateFocusFromBrowsingContext();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsPopupSpam>, 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<IDX_IsPopupSpam>) {
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup,
+ ContentParent* aSource) {
+ // Should only be set in the parent process on toplevel.
+ return XRE_IsParentProcess() && !aSource && IsTopContent();
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_OrientationLock>,
+ 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<IDX_Loading>) {
+ if (mFields.Get<IDX_Loading>()) {
+ return;
+ }
+
+ while (!mDeprioritizedLoadRunner.isEmpty()) {
+ nsCOMPtr<nsIRunnable> 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<IDX_AncestorLoading>) {
+ 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<IDX_AuthorStyleDisabledDefault>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set AuthorStyleDisabledDefault in the top "
+ "browsing context");
+
+ // We don't need to handle changes to this field, since PageStyleChild.jsm
+ // 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<IDX_TextZoom>, 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()) {
+ auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
+ element, u"TextZoomChange"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ dispatcher->RunDOMEventWhenSafe();
+ }
+ }
+}
+
+// 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<IDX_FullZoom>, 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()) {
+ auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
+ element, u"FullZoomChange"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ dispatcher->RunDOMEventWhenSafe();
+ }
+ }
+}
+
+void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) {
+ MOZ_ASSERT(IsLoading());
+ MOZ_ASSERT(Top() == this);
+
+ RefPtr<DeprioritizedLoadRunner> runner = new DeprioritizedLoadRunner(aRunner);
+ mDeprioritizedLoadRunner.insertBack(runner);
+ NS_DispatchToCurrentThreadQueue(
+ runner.forget(), StaticPrefs::page_load_deprioritization_period(),
+ EventQueuePriority::Idle);
+}
+
+void BrowsingContext::GetHistoryID(JSContext* aCx,
+ JS::MutableHandle<JS::Value> 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<IDX_HasSessionHistory>,
+ bool aOldValue) {
+ MOZ_ASSERT(GetHasSessionHistory() || !aOldValue,
+ "We don't support turning off session history.");
+
+ if (GetHasSessionHistory() && !aOldValue) {
+ CreateChildSHistory();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_BrowserId>, 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<IDX_PendingInitialization>,
+ 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;
+}
+
+void BrowsingContext::SessionHistoryChanged(int32_t aIndexDelta,
+ int32_t aLengthDelta) {
+ if (XRE_IsParentProcess() || mozilla::SessionHistoryInParent()) {
+ // This method is used to test index and length for the session history
+ // in child process only.
+ return;
+ }
+
+ if (!IsTop()) {
+ // Some tests have unexpected setup while Fission shistory is being
+ // implemented.
+ return;
+ }
+
+ RefPtr<ChildSHistory> shistory = GetChildSessionHistory();
+ if (!shistory || !shistory->AsyncHistoryLength()) {
+ return;
+ }
+
+ nsID changeID = shistory->AddPendingHistoryChange(aIndexDelta, aLengthDelta);
+ uint32_t index = shistory->Index();
+ uint32_t length = shistory->Count();
+
+ // Do artificial history update through parent process to test asynchronous
+ // history.length handling.
+ ContentChild::GetSingleton()->SendSessionHistoryUpdate(this, index, length,
+ changeID);
+}
+
+bool BrowsingContext::IsPopupAllowed() {
+ for (auto* context = GetCurrentWindowContext(); context;
+ context = context->GetParentWindowContext()) {
+ if (context->CanShowPopup()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void BrowsingContext::SessionHistoryCommit(
+ const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType,
+ bool aHadActiveEntry, bool aPersist, bool aCloneEntryChildren) {
+ nsID changeID = {};
+ if (XRE_IsContentProcess()) {
+ RefPtr<ChildSHistory> 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,
+ // and ShouldUpdateSessionHistory(loadType) returns true.
+ // It is possible that this leads to wrong length temporarily, but
+ // so would not having the check for replace.
+ if (!LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) &&
+ (IsTop() || aHadActiveEntry) &&
+ ShouldUpdateSessionHistory(aLoadType)) {
+ changeID = rootSH->AddPendingHistoryChange();
+ }
+ } else {
+ // This is a load from session history, so we can update
+ // index and length immediately.
+ rootSH->SetIndexAndLength(aInfo.mRequestedIndex,
+ aInfo.mSessionHistoryLength, changeID);
+ }
+ }
+ ContentChild* cc = ContentChild::GetSingleton();
+ mozilla::Unused << cc->SendHistoryCommit(this, aInfo.mLoadId, changeID,
+ aLoadType, aPersist,
+ aCloneEntryChildren);
+ } else {
+ Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType,
+ aPersist, aCloneEntryChildren);
+ }
+}
+
+void BrowsingContext::SetActiveSessionHistoryEntry(
+ const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey) {
+ if (XRE_IsContentProcess()) {
+ // XXX Why we update cache key only in content process case?
+ if (aUpdatedCacheKey != 0) {
+ aInfo->SetCacheKey(aUpdatedCacheKey);
+ }
+
+ nsID changeID = {};
+ RefPtr<ChildSHistory> 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() {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this);
+ } else {
+ Canonical()->RemoveFromSessionHistory();
+ }
+}
+
+void BrowsingContext::HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction,
+ std::function<void(int32_t&&)>&& aResolver) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendHistoryGo(
+ this, aOffset, aHistoryEpoch, aRequireUserInteraction,
+ std::move(aResolver),
+ [](mozilla::ipc::
+ ResponseRejectReason) { /* FIXME Is ignoring this fine? */ });
+ } else {
+ Canonical()->HistoryGo(
+ aOffset, aHistoryEpoch, aRequireUserInteraction,
+ Canonical()->GetContentParent()
+ ? Some(Canonical()->GetContentParent()->ChildID())
+ : Nothing(),
+ std::move(aResolver));
+ }
+}
+
+void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) {
+ mChildSessionHistory = aChildSHistory;
+ mChildSessionHistory->SetBrowsingContext(this);
+ mFields.SetWithoutSyncing<IDX_HasSessionHistory>(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) && IsFrame()));
+}
+
+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<dom::MaybeDiscarded<dom::BrowsingContext>>::Write(
+ IPC::Message* aMsg, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) {
+ MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() ||
+ aParam.GetMaybeDiscarded()->EverAttached());
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aMsg, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Read(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::BrowsingContext> bc = dom::BrowsingContext::Get(id)) {
+ *aResult = std::move(bc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Write(
+ IPC::Message* aMessage, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aMessage, aActor, aInit.mId);
+ WriteIPDLParam(aMessage, aActor, aInit.mParentId);
+ WriteIPDLParam(aMessage, aActor, aInit.mWindowless);
+ WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteTabs);
+ WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteSubframes);
+ WriteIPDLParam(aMessage, aActor, aInit.mCreatedDynamically);
+ WriteIPDLParam(aMessage, aActor, aInit.mOriginAttributes);
+ WriteIPDLParam(aMessage, aActor, aInit.mRequestContextId);
+ WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryIndex);
+ WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryCount);
+ WriteIPDLParam(aMessage, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read(
+ const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor,
+ dom::BrowsingContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mId) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mParentId) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mWindowless) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mUseRemoteTabs) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor,
+ &aInit->mUseRemoteSubframes) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor,
+ &aInit->mCreatedDynamically) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOriginAttributes) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mRequestContextId) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor,
+ &aInit->mSessionHistoryIndex) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor,
+ &aInit->mSessionHistoryCount) ||
+ !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields)) {
+ return false;
+ }
+ return true;
+}
+
+template struct IPDLParamTraits<dom::BrowsingContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
new file mode 100644
index 0000000000..4b09798e01
--- /dev/null
+++ b/docshell/base/BrowsingContext.h
@@ -0,0 +1,1166 @@
+/* -*- 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 <tuple>
+#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/Tuple.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;
+} // namespace IPC
+
+namespace mozilla {
+
+class ErrorResult;
+class LogModule;
+
+namespace ipc {
+class IProtocol;
+
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class BrowsingContent;
+class BrowsingContextGroup;
+class CanonicalBrowsingContext;
+class ChildSHistory;
+class ContentParent;
+class Element;
+struct LoadingSessionHistoryInfo;
+template <typename>
+struct Nullable;
+template <typename T>
+class Sequence;
+class SessionHistoryInfo;
+class SessionStorageManager;
+class StructuredCloneHolder;
+class WindowContext;
+struct WindowPostMessageOptions;
+class WindowProxyHolder;
+
+enum class ExplicitActiveStatus : uint8_t {
+ None,
+ Active,
+ Inactive,
+ EndGuard_,
+};
+
+// 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) \
+ /* See nsSandboxFlags.h for the possible flags. */ \
+ FIELD(SandboxFlags, uint32_t) \
+ 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(FullscreenAllowedByOwner, 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<nsString>) \
+ FIELD(MessageManagerGroup, nsString) \
+ FIELD(MaxTouchPointsOverride, uint8_t) \
+ FIELD(FullZoom, float) \
+ FIELD(WatchedByDevToolsInternal, bool) \
+ FIELD(TextZoom, float) \
+ /* The current in-progress load. */ \
+ FIELD(CurrentLoadIdentifier, Maybe<uint64_t>) \
+ /* 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) \
+ FIELD(HasLoadedNonInitialDocument, bool) \
+ FIELD(CreatedDynamically, 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(DisplayMode, mozilla::dom::DisplayMode) \
+ /* True if the top level browsing context owns a main media controller */ \
+ FIELD(HasMainMediaController, 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();
+
+ // Look up a BrowsingContext in the current process by ID.
+ static already_AddRefed<BrowsingContext> Get(uint64_t aId);
+ static already_AddRefed<BrowsingContext> Get(GlobalObject&, uint64_t aId) {
+ return Get(aId);
+ }
+ // Look up the top-level BrowsingContext by BrowserID.
+ static already_AddRefed<BrowsingContext> GetCurrentTopByBrowserId(
+ uint64_t aBrowserId);
+
+ static already_AddRefed<BrowsingContext> GetFromWindow(
+ WindowProxyHolder& aProxy);
+ static already_AddRefed<BrowsingContext> 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 <browser> 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<BrowsingContext> 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<BrowsingContext> CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ 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<JSObject*> 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);
+
+ // 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);
+
+ // Determine if the current BrowsingContext is in the BFCache.
+ bool IsCached() const;
+
+ // 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 IsFrame() const { return !IsTop(); }
+
+ bool IsTopContent() const { return IsContent() && IsTop(); }
+
+ bool IsInSubtreeOf(BrowsingContext* aContext);
+
+ bool IsContentSubframe() const { return IsContent() && IsFrame(); }
+ // non-zero
+ uint64_t Id() const { return mBrowsingContextId; }
+
+ BrowsingContext* GetParent() const;
+ BrowsingContext* Top();
+ int32_t IndexOf(BrowsingContext* aChild);
+
+ // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not
+ // cross toplevel content browser boundaries.
+ WindowContext* GetParentWindowContext() const { return mParentWindow; }
+ WindowContext* GetTopWindowContext();
+
+ already_AddRefed<BrowsingContext> GetOpener() const {
+ RefPtr<BrowsingContext> 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<BrowsingContext> 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() { return GetSandboxFlags(); }
+
+ Span<RefPtr<BrowsingContext>> Children() const;
+ void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren);
+
+ const nsTArray<RefPtr<WindowContext>>& GetWindowContexts() {
+ return mWindowContexts;
+ }
+ void GetWindowContexts(nsTArray<RefPtr<WindowContext>>& 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.
+ void PreOrderWalk(const std::function<void(BrowsingContext*)>& aCallback);
+ void PostOrderWalk(const std::function<void(BrowsingContext*)>& aCallback);
+ void GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
+
+ BrowsingContextGroup* Group() { return mGroup; }
+
+ // WebIDL bindings for nsILoadContext
+ Nullable<WindowProxyHolder> GetAssociatedWindow();
+ Nullable<WindowProxyHolder> 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<JS::Value> aVal,
+ ErrorResult& aError);
+
+ bool InRDMPane() const { return GetInRDMPane(); }
+
+ bool WatchedByDevTools();
+ void SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv);
+
+ mozilla::dom::TouchEventsOverride TouchEventsOverride() const;
+ void SetTouchEventsOverride(
+ const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv);
+ MOZ_MUST_USE nsresult
+ SetTouchEventsOverride(const enum TouchEventsOverride aTouchEventsOverride);
+
+ bool FullscreenAllowed() const;
+
+ float FullZoom() const { return GetFullZoom(); }
+ float TextZoom() const { return GetTextZoom(); }
+
+ bool SuspendMediaWhenInactive() const {
+ return GetSuspendMediaWhenInactive();
+ }
+
+ bool IsActive() const;
+ void SetIsActive(bool aIsActive, mozilla::ErrorResult& aRv) {
+ SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active
+ : ExplicitActiveStatus::Inactive,
+ aRv);
+ }
+
+ 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);
+ }
+ }
+
+ // Using the rules for choosing a browsing context we try to find
+ // the browsing context with the given name in the set of
+ // transitively reachable browsing contexts. Performs access control
+ // checks with regard to this.
+ // See
+ // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name.
+ //
+ // BrowsingContext::FindWithName(const nsAString&) is equivalent to
+ // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr,
+ // nullptr, false, <return value>).
+ BrowsingContext* FindWithName(const nsAString& aName,
+ bool aUseEntryGlobalForAccessCheck = true);
+
+ // 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,
+ BrowsingContext& aRequestingContext);
+
+ // Find a browsing context in the subtree rooted at 'this' Doesn't
+ // consider the special names, '_self', '_parent', '_top', or
+ // '_blank'. Performs access control checks with regard to
+ // 'aRequestingContext'.
+ BrowsingContext* FindWithNameInSubtree(const nsAString& aName,
+ BrowsingContext& aRequestingContext);
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<JSObject*> aWindowProxy) {
+ mWindowProxy = aWindowProxy;
+ }
+
+ Nullable<WindowProxyHolder> GetWindow();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_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<JSObject*> aLocation,
+ ErrorResult& aError);
+ void Close(CallerType aCallerType, ErrorResult& aError);
+ bool GetClosed(ErrorResult&) { return GetClosed(); }
+ void Focus(CallerType aCallerType, ErrorResult& aError);
+ void Blur(ErrorResult& aError);
+ WindowProxyHolder GetFrames(ErrorResult& aError);
+ int32_t Length() const { return Children().Length(); }
+ Nullable<WindowProxyHolder> GetTop(ErrorResult& aError);
+ void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aOpener,
+ ErrorResult& aError) const;
+ Nullable<WindowProxyHolder> GetParent(ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> 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<WindowContext> GetParent();
+ already_AddRefed<BrowsingContext> GetOpener();
+
+ uint64_t GetOpenerId() const { return mFields.mOpenerId; }
+
+ bool mWindowless = false;
+ bool mUseRemoteTabs = false;
+ bool mUseRemoteSubframes = false;
+ bool mCreatedDynamically = false;
+ 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 void CreateFromIPC(IPCInitializer&& aInitializer,
+ BrowsingContextGroup* aGroup,
+ ContentParent* aOriginProcess);
+
+ // Performs access control to check that 'this' can access 'aTarget'.
+ bool CanAccess(BrowsingContext* aTarget, bool aConsiderOpener = true);
+
+ 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<SessionStorageManager> GetSessionStorageManager();
+
+ // Set PendingInitialization on this BrowsingContext before the context has
+ // been attached.
+ void InitPendingInitialization(bool aPendingInitialization) {
+ MOZ_ASSERT(!EverAttached());
+ mFields.SetWithoutSyncing<IDX_PendingInitialization>(
+ aPendingInitialization);
+ }
+
+ bool CreatedDynamically() const { return mCreatedDynamically; }
+
+ const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
+ nsresult SetOriginAttributes(const OriginAttributes& aAttrs);
+
+ void GetHistoryID(JSContext* aCx, JS::MutableHandle<JS::Value> 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();
+
+ void SessionHistoryChanged(int32_t aIndexDelta, int32_t aLengthDelta);
+
+ // Check if it is allowed to open a popup from the current browsing
+ // context or any of its ancestors.
+ bool IsPopupAllowed();
+
+ void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo,
+ uint32_t aLoadType, bool aHadActiveEntry,
+ bool aPersist, bool aCloneEntryChildren);
+
+ // 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<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey);
+
+ // 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();
+
+ 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.
+ Tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+ GetTriggeringAndInheritPrincipalsForCurrentLoad();
+
+ void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction,
+ std::function<void(int32_t&&)>&& 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<bool, bool> CanFocusCheck(CallerType aCallerType);
+
+ PopupBlocker::PopupControlState RevisePopupAbuseLevel(
+ PopupBlocker::PopupControlState aControl);
+
+ protected:
+ virtual ~BrowsingContext();
+ BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit);
+
+ void SetChildSHistory(ChildSHistory* aChildSHistory);
+ already_AddRefed<ChildSHistory> ForgetChildSHistory() {
+ // FIXME Do we need to unset mHasSessionHistory?
+ return mChildSessionHistory.forget();
+ }
+
+ private:
+ void Attach(bool aFromIPC, ContentParent* aOriginProcess);
+
+ // 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,
+ BrowsingContext& aRequestingContext);
+
+ // 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<BrowsingContext*>(
+ uintptr_t(this) - offsetof(BrowsingContext, mLocation));
+ }
+
+ already_AddRefed<nsIDocShell> 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);
+
+ // Ensure that opener is in the same BrowsingContextGroup.
+ bool CanSet(FieldIndex<IDX_OpenerId>, const uint64_t& aValue,
+ ContentParent* aSource) {
+ if (aValue != 0) {
+ RefPtr<BrowsingContext> opener = Get(aValue);
+ return opener && opener->Group() == Group();
+ }
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ const enum TouchEventsOverride& aTouchEventsOverride,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_DisplayMode>, const enum DisplayMode& aDisplayMode,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_DisplayMode>, enum DisplayMode aOldValue);
+
+ void DidSet(FieldIndex<IDX_ExplicitActive>, ExplicitActiveStatus aOldValue);
+
+ bool CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, const bool& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, 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<IDX_Muted>);
+
+ bool CanSet(FieldIndex<IDX_EmbedderInnerWindowId>, const uint64_t& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_CurrentInnerWindowId>, const uint64_t& aValue,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_CurrentInnerWindowId>);
+
+ bool CanSet(FieldIndex<IDX_IsPopupSpam>, const bool& aValue,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_IsPopupSpam>);
+
+ void DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>);
+ void DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>);
+
+ void DidSet(FieldIndex<IDX_Loading>);
+
+ void DidSet(FieldIndex<IDX_AncestorLoading>);
+
+ void DidSet(FieldIndex<IDX_PlatformOverride>);
+ bool CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatformOverride, ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_UserAgentOverride>);
+ bool CanSet(FieldIndex<IDX_UserAgentOverride>, const nsString& aUserAgent,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_OrientationLock>,
+ const mozilla::hal::ScreenOrientation& aOrientationLock,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderElementType>,
+ const Maybe<nsString>& aInitiatorType, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AllowPlugins>, const bool& aAllowPlugins,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>, const bool&,
+ ContentParent*);
+ bool CanSet(FieldIndex<IDX_WatchedByDevToolsInternal>,
+ const bool& aWatchedByDevToolsInternal, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags, ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_DefaultLoadFlags>);
+
+ bool CanSet(FieldIndex<IDX_UseGlobalHistory>, const bool& aUseGlobalHistory,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_HasSessionHistory>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_UseErrorPages>, const bool& aUseErrorPages,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_PendingInitialization>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasMainMediaController>, bool aNewValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_HasMainMediaController>, bool aOldValue);
+
+ template <size_t I, typename T>
+ bool CanSet(FieldIndex<I>, 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 <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+
+ void DidSet(FieldIndex<IDX_FullZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_TextZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_AuthorStyleDisabledDefault>);
+
+ // True if the process attemping to set field is the same as the owning
+ // process.
+ bool CheckOnlyOwningProcessCanSet(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 =
+ Tuple<nsCOMPtr<nsIPrincipal>, uint64_t>;
+
+ nsIPrincipal* GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple);
+
+ // Type of BrowsingContent
+ const Type mType;
+
+ // Unique id identifying BrowsingContext
+ const uint64_t mBrowsingContextId;
+
+ RefPtr<BrowsingContextGroup> mGroup;
+ RefPtr<WindowContext> mParentWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ RefPtr<Element> mEmbedderElement;
+
+ nsTArray<RefPtr<WindowContext>> mWindowContexts;
+ RefPtr<WindowContext> 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<JSObject*> 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;
+
+ // 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<PrincipalWithLoadIdentifierTuple> mTriggeringPrincipal;
+ Maybe<PrincipalWithLoadIdentifierTuple> mPrincipalToInherit;
+
+ class DeprioritizedLoadRunner
+ : public mozilla::Runnable,
+ public mozilla::LinkedListElement<DeprioritizedLoadRunner> {
+ public:
+ explicit DeprioritizedLoadRunner(nsIRunnable* aInner)
+ : Runnable("DeprioritizedLoadRunner"), mInner(aInner) {}
+
+ NS_IMETHOD Run() override {
+ if (mInner) {
+ RefPtr<nsIRunnable> inner = std::move(mInner);
+ inner->Run();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsIRunnable> mInner;
+ };
+
+ mozilla::LinkedList<DeprioritizedLoadRunner> mDeprioritizedLoadRunner;
+
+ RefPtr<SessionStorageManager> mSessionStorageManager;
+ RefPtr<ChildSHistory> mChildSessionHistory;
+
+ // 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<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aRetVal);
+
+using BrowsingContextTransaction = BrowsingContext::BaseTransaction;
+using BrowsingContextInitializer = BrowsingContext::IPCInitializer;
+using MaybeDiscardedBrowsingContext = MaybeDiscarded<BrowsingContext>;
+
+// Specialize the transaction object for every translation unit it's used in.
+extern template class syncedcontext::Transaction<BrowsingContext>;
+
+} // namespace dom
+
+// Allow sending BrowsingContext objects over IPC.
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::BrowsingContext::IPCInitializer> {
+ static void Write(IPC::Message* aMessage, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInitializer);
+
+ static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator,
+ 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..cf3bd9504d
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -0,0 +1,460 @@
+/* -*- 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/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"
+
+namespace mozilla {
+namespace dom {
+
+static StaticRefPtr<BrowsingContextGroup> sChromeGroup;
+
+static StaticAutoPtr<
+ nsDataHashtable<nsUint64HashKey, RefPtr<BrowsingContextGroup>>>
+ sBrowsingContextGroups;
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetOrCreate(
+ uint64_t aId) {
+ if (!sBrowsingContextGroups) {
+ sBrowsingContextGroups =
+ new nsDataHashtable<nsUint64HashKey, RefPtr<BrowsingContextGroup>>();
+ ClearOnShutdown(&sBrowsingContextGroups);
+ }
+
+ auto entry = sBrowsingContextGroups->LookupForAdd(aId);
+ RefPtr<BrowsingContextGroup> group =
+ entry.OrInsert([&] { return do_AddRef(new BrowsingContextGroup(aId)); });
+ return group.forget();
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Create() {
+ return GetOrCreate(nsContentUtils::GenerateBrowsingContextId());
+}
+
+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_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.PutEntry(aContext);
+}
+
+void BrowsingContextGroup::Unregister(nsISupports* aContext) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.RemoveEntry(aContext);
+
+ MaybeDestroy();
+}
+
+void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) {
+ MOZ_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");
+
+ if (!aProcess->IsDead()) {
+ auto entry = mHosts.LookupForAdd(aProcess->GetRemoteType());
+ if (entry) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ entry.Data() == aProcess,
+ "There's already another host process for this remote type");
+ return;
+ }
+
+ // This process wasn't already marked as our host, so insert it, and begin
+ // subscribing, unless the process is still launching.
+ entry.OrInsert([&] { return do_AddRef(aProcess); });
+ }
+
+ 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<RefPtr<BrowsingContext>> aContexts,
+ nsTArray<SyncedContextInitializer>& 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 (auto& window : context->GetWindowContexts()) {
+ aInits.AppendElement(window->GetIPCInitializer());
+ CollectContextInitializers(window->Children(), aInits);
+ }
+ }
+}
+
+void BrowsingContextGroup::Subscribe(ContentParent* aProcess) {
+ MOZ_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<SyncedContextInitializer> 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, active);
+ }
+ }
+}
+
+void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(aProcess);
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
+ mSubscribers.RemoveEntry(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<nsPIDOMWindowInner> 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 DEBUG
+ if (mDestroyed) {
+ MOZ_ASSERT(mHosts.Count() == 0);
+ MOZ_ASSERT(mSubscribers.Count() == 0);
+ MOZ_ASSERT_IF(sBrowsingContextGroups,
+ sBrowsingContextGroups->Get(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 (auto& entry : mHosts) {
+ entry.GetData()->RemoveBrowsingContextGroup(this);
+ }
+ for (auto& entry : mSubscribers) {
+ entry.GetKey()->RemoveBrowsingContextGroup(this);
+ }
+ mHosts.Clear();
+ mSubscribers.Clear();
+
+ if (sBrowsingContextGroups) {
+ sBrowsingContextGroups->Remove(Id());
+ }
+}
+
+void BrowsingContextGroup::AddKeepAlive() {
+ MOZ_ASSERT(!mDestroyed);
+ mKeepAliveCount++;
+}
+
+void BrowsingContextGroup::RemoveKeepAlive() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0);
+ mKeepAliveCount--;
+
+ MaybeDestroy();
+}
+
+void BrowsingContextGroup::MaybeDestroy() {
+ if (mContexts.IsEmpty() && mKeepAliveCount == 0 && this != sChromeGroup) {
+ // There are no synced contexts still referencing this group. We can clear
+ // all subscribers, and destroy ourselves.
+ Destroy();
+
+ // We may have been deleted here as the ContentChild/Parent may
+ // have held the last references to `this`.
+ // Do not access any members at this point.
+ }
+}
+
+nsISupports* BrowsingContextGroup::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult BrowsingContextGroup::QueuePostMessageEvent(
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
+ if (!mPostMessageEventQueue) {
+ nsCOMPtr<nsISerialEventTarget> 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<nsIRunnable> 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<DocGroup*>& aDocGroups) {
+ MOZ_ASSERT(NS_IsMainThread());
+ for (auto iter = mDocGroups.ConstIter(); !iter.Done(); iter.Next()) {
+ aDocGroups.AppendElement(iter.Data());
+ }
+}
+
+already_AddRefed<DocGroup> BrowsingContextGroup::AddDocument(
+ const nsACString& aKey, Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<DocGroup>& docGroup = mDocGroups.GetOrInsert(aKey);
+ if (!docGroup) {
+ docGroup = DocGroup::Create(this, aKey);
+ }
+
+ docGroup->AddDocument(aDocument);
+ return do_AddRef(docGroup);
+}
+
+void BrowsingContextGroup::RemoveDocument(const nsACString& aKey,
+ Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<DocGroup> docGroup = aDocument->GetDocGroup();
+ // Removing the last document in DocGroup might decrement the
+ // DocGroup BrowsingContextGroup's refcount to 0.
+ RefPtr<BrowsingContextGroup> kungFuDeathGrip(this);
+ docGroup->RemoveDocument(aDocument);
+
+ if (docGroup->IsEmpty()) {
+ mDocGroups.Remove(aKey);
+ }
+}
+
+already_AddRefed<BrowsingContextGroup> 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<RefPtr<BrowsingContextGroup>>& aGroups) {
+ aGroups.Clear();
+ if (!sBrowsingContextGroups) {
+ return;
+ }
+
+ aGroups.SetCapacity(sBrowsingContextGroups->Count());
+ for (auto& group : *sBrowsingContextGroups) {
+ aGroups.AppendElement(group.GetData());
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts,
+ mToplevels, mHosts, mSubscribers,
+ mTimerEventQueue, mWorkerEventQueue)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContextGroup, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContextGroup, Release)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h
new file mode 100644
index 0000000000..39fc6d24f8
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.h
@@ -0,0 +1,232 @@
+/* -*- 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 "nsTHashtable.h"
+#include "nsWrapperCache.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+class ThrottledEventQueue;
+
+namespace dom {
+
+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_SCRIPT_HOLDER_NATIVE_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 directy, as it's automatically called by
+ // |EnsureHostProcess| as needed.
+ void Subscribe(ContentParent* aProcess);
+
+ // Stop synchromizing 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 the BrowsingContext has been discarded. A `KeepAlive` will be
+ // added to the `BrowsingContextGroup`, delaying destruction.
+ void AddKeepAlive();
+ void RemoveKeepAlive();
+
+ // 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<RefPtr<BrowsingContext>>& Toplevels() { return mToplevels; }
+ void GetToplevels(nsTArray<RefPtr<BrowsingContext>>& aToplevels) {
+ aToplevels.AppendElements(mToplevels);
+ }
+
+ uint64_t Id() { return mId; }
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Get or create a BrowsingContextGroup with the given ID.
+ static already_AddRefed<BrowsingContextGroup> GetOrCreate(uint64_t aId);
+ static already_AddRefed<BrowsingContextGroup> Create();
+ static already_AddRefed<BrowsingContextGroup> Select(
+ WindowContext* aParent, BrowsingContext* aOpener);
+
+ // For each 'ContentParent', except for 'aExcludedParent',
+ // associated with this group call 'aCallback'.
+ template <typename Func>
+ void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Get()->GetKey() != aExcludedParent) {
+ aCallback(iter.Get()->GetKey());
+ }
+ }
+ }
+
+ // For each 'ContentParent' associated with
+ // this group call 'aCallback'.
+ template <typename Func>
+ void EachParent(Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) {
+ aCallback(iter.Get()->GetKey());
+ }
+ }
+
+ nsresult QueuePostMessageEvent(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void FlushPostMessageEvents();
+
+ // Increase or decrease the suspension level in InputTaskManager
+ void UpdateInputTaskManagerIfNeeded(bool aIsActive);
+
+ static BrowsingContextGroup* GetChromeGroup();
+
+ void GetDocGroups(nsTArray<DocGroup*>& aDocGroups);
+
+ // Called by Document when a Document needs to be added to a DocGroup.
+ already_AddRefed<DocGroup> AddDocument(const nsACString& aKey,
+ Document* aDocument);
+
+ // Called by Document when a Document needs to be removed to a DocGroup.
+ void RemoveDocument(const nsACString& aKey, Document* aDocument);
+
+ auto DocGroups() const { return mDocGroups.ConstIter(); }
+
+ mozilla::ThrottledEventQueue* GetTimerEventQueue() const {
+ return mTimerEventQueue;
+ }
+
+ mozilla::ThrottledEventQueue* GetWorkerEventQueue() const {
+ return mWorkerEventQueue;
+ }
+
+ static void GetAllGroups(nsTArray<RefPtr<BrowsingContextGroup>>& aGroups);
+
+ void IncInputEventSuspensionLevel();
+ void DecInputEventSuspensionLevel();
+
+ 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 DEBUG
+ 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`?
+ nsTHashtable<nsRefPtrHashKey<nsISupports>> mContexts;
+
+ // The set of toplevel browsing contexts in the current BrowsingContextGroup.
+ nsTArray<RefPtr<BrowsingContext>> 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<nsCStringHashKey, DocGroup> 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<nsCStringHashKey, ContentParent> mHosts;
+
+ nsTHashtable<nsRefPtrHashKey<ContentParent>> mSubscribers;
+
+ // A queue to store postMessage events during page load, the queue will be
+ // flushed once the page is loaded
+ RefPtr<mozilla::ThrottledEventQueue> mPostMessageEventQueue;
+
+ RefPtr<mozilla::ThrottledEventQueue> mTimerEventQueue;
+ RefPtr<mozilla::ThrottledEventQueue> 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;
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_BrowsingContextGroup_h)
diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp
new file mode 100644
index 0000000000..0dfe9d0eb8
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.cpp
@@ -0,0 +1,217 @@
+/* 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"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ADDREF(BrowsingContextWebProgress)
+NS_IMPL_RELEASE(BrowsingContextWebProgress)
+
+NS_INTERFACE_MAP_BEGIN(BrowsingContextWebProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END
+
+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::GetDOMWindow(
+ mozIDOMWindowProxy** aDOMWindow) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
+ *aIsTopLevel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
+ bool* aIsLoadingDocument) {
+ *aIsLoadingDocument = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = 0;
+ 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<void(nsIWebProgressListener*)>& aCallback) {
+ RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
+
+ ListenerArray::ForwardIterator iter(mListenerInfoList);
+ while (iter.HasMore()) {
+ ListenerInfo& info = iter.GetNext();
+ if (!(info.mNotifyMask & aFlag)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ do_QueryReferent(info.mWeakListener);
+ if (!listener) {
+ mListenerInfoList.RemoveElement(info);
+ continue;
+ }
+
+ aCallback(listener);
+ }
+
+ mListenerInfoList.Compact();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus) {
+ const uint32_t startDocumentFlags =
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK;
+ bool isTopLevel = false;
+ nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isTopLevelStartDocumentEvent =
+ (aStateFlags & startDocumentFlags) == startDocumentFlags && isTopLevel;
+ // If we receive a matching STATE_START for a top-level document event,
+ // and we are currently not suspending this event, start suspending all
+ // further matching STATE_START events after this one.
+ if (isTopLevelStartDocumentEvent && !mSuspendOnStateStartChangeEvents) {
+ mSuspendOnStateStartChangeEvents = true;
+ } else if (mSuspendOnStateStartChangeEvents) {
+ // If we are currently suspending matching STATE_START events, check if this
+ // is a corresponding STATE_STOP event.
+ const uint32_t stopWindowFlags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW;
+ bool isTopLevelStopWindowEvent =
+ (aStateFlags & stopWindowFlags) == stopWindowFlags && isTopLevel;
+ if (isTopLevelStopWindowEvent) {
+ // If this is a STATE_STOP event corresponding to the initial STATE_START
+ // event, stop suspending matching STATE_START events.
+ mSuspendOnStateStartChangeEvents = false;
+ } else if (isTopLevelStartDocumentEvent) {
+ // We have received a matching STATE_START event at least twice, but
+ // haven't received the corresponding STATE_STOP event for the first one.
+ // Don't let this event through. This is probably a duplicate event from
+ // the new BrowserParent due to a process switch.
+ return NS_OK;
+ }
+ // Allow all other progress events that don't match top-level start document
+ // flags.
+ }
+
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
+ [&](nsIWebProgressListener* listener) {
+ listener->OnContentBlockingEvent(aWebProgress,
+ aRequest, aEvent);
+ });
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h
new file mode 100644
index 0000000000..8085da715b
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.h
@@ -0,0 +1,68 @@
+/* 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"
+
+namespace mozilla {
+namespace dom {
+
+class BrowsingContextWebProgress final : public nsIWebProgress,
+ public nsIWebProgressListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ BrowsingContextWebProgress() = default;
+
+ 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;
+ };
+
+ private:
+ virtual ~BrowsingContextWebProgress() = default;
+
+ void UpdateAndNotifyListeners(
+ uint32_t aFlag,
+ const std::function<void(nsIWebProgressListener*)>& aCallback);
+
+ using ListenerArray = nsAutoTObserverArray<ListenerInfo, 4>;
+ ListenerArray mListenerInfoList;
+
+ // This indicates whether we are currently suspending onStateChange top level
+ // STATE_START events for a document. We start suspending whenever we receive
+ // the first STATE_START event with the matching flags (see
+ // ::RecvOnStateChange for details), until we get a corresponding STATE_STOP
+ // event. In the meantime, if there other onStateChange events, this flag does
+ // not affect them. We do this to avoid duplicate onStateChange STATE_START
+ // events that happen during process switch. With this flag, we allow
+ // onStateChange STATE_START event from the old BrowserParent, but not the
+ // same event from the new BrowserParent during a process switch.
+ bool mSuspendOnStateStartChangeEvents = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BrowsingContextWebProgress_h
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
new file mode 100644
index 0000000000..7f01ff6cf8
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -0,0 +1,1694 @@
+/* -*- 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 "mozilla/CheckedInt.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/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/SessionHistoryEntry.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/NullPrincipal.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"
+
+using namespace mozilla::ipc;
+
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+extern mozilla::LazyLogModule gSHLog;
+
+#define AUTOPLAY_LOG(msg, ...) \
+ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+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) {
+ // You are only ever allowed to create CanonicalBrowsingContexts in the
+ // parent process.
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+}
+
+/* static */
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
+ uint64_t aId) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
+}
+
+/* static */
+CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<CanonicalBrowsingContext*>(aContext);
+}
+
+/* static */
+const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ const BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<const CanonicalBrowsingContext*>(aContext);
+}
+
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
+ already_AddRefed<BrowsingContext>&& aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return aContext.downcast<CanonicalBrowsingContext>();
+}
+
+ContentParent* CanonicalBrowsingContext::GetContentParent() const {
+ if (mProcessId == 0) {
+ return nullptr;
+ }
+
+ ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+ 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;
+}
+
+void CanonicalBrowsingContext::MaybeAddAsProgressListener(
+ nsIWebProgress* aWebProgress) {
+ if (!GetWebProgress()) {
+ return;
+ }
+ if (!mStatusFilter) {
+ mStatusFilter = new nsBrowserStatusFilter();
+ mStatusFilter->AddProgressListener(GetWebProgress(),
+ nsIWebProgress::NOTIFY_ALL);
+ }
+ aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
+}
+
+void CanonicalBrowsingContext::ReplacedBy(
+ CanonicalBrowsingContext* aNewContext) {
+ MOZ_ASSERT(!aNewContext->EverAttached());
+ MOZ_ASSERT(IsTop() && aNewContext->IsTop());
+ if (mStatusFilter) {
+ mStatusFilter->RemoveProgressListener(mWebProgress);
+ mStatusFilter = nullptr;
+ }
+ aNewContext->mWebProgress = std::move(mWebProgress);
+ aNewContext->mFields.SetWithoutSyncing<IDX_BrowserId>(GetBrowserId());
+ aNewContext->mFields.SetWithoutSyncing<IDX_HistoryID>(GetHistoryID());
+ aNewContext->mFields.SetWithoutSyncing<IDX_ExplicitActive>(
+ GetExplicitActive());
+
+ if (mSessionHistory) {
+ mSessionHistory->SetBrowsingContext(aNewContext);
+ mSessionHistory.swap(aNewContext->mSessionHistory);
+ RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
+ aNewContext->SetChildSHistory(childSHistory);
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
+ }
+
+ MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
+ mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
+ MOZ_ASSERT(!aNewContext->mActiveEntry);
+ mActiveEntry.swap(aNewContext->mActiveEntry);
+}
+
+void CanonicalBrowsingContext::UpdateSecurityState() {
+ if (mSecureBrowserUI) {
+ mSecureBrowserUI->RecomputeSecurityFlags();
+ }
+}
+
+void CanonicalBrowsingContext::SetInFlightProcessId(uint64_t aProcessId) {
+ MOZ_ASSERT(aProcessId);
+ mInFlightProcessId = aProcessId;
+}
+
+void CanonicalBrowsingContext::ClearInFlightProcessId(uint64_t aProcessId) {
+ MOZ_ASSERT(aProcessId);
+ if (mInFlightProcessId == aProcessId) {
+ mInFlightProcessId = 0;
+ }
+}
+
+void CanonicalBrowsingContext::GetWindowGlobals(
+ nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
+ aWindows.SetCapacity(GetWindowContexts().Length());
+ for (auto& window : GetWindowContexts()) {
+ aWindows.AppendElement(static_cast<WindowGlobalParent*>(window.get()));
+ }
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const {
+ return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetParentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetTopWindowContext());
+}
+
+already_AddRefed<nsIWidget>
+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<nsIWidget> 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<WindowGlobalParent>
+CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
+ uint64_t windowId = GetEmbedderInnerWindowId();
+ if (windowId == 0) {
+ return nullptr;
+ }
+
+ return WindowGlobalParent::GetByInnerWindowId(windowId);
+}
+
+already_AddRefed<CanonicalBrowsingContext>
+CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
+ if (GetParent()) {
+ return do_AddRef(Cast(GetParent()));
+ }
+ if (GetEmbedderElement()) {
+ return do_AddRef(
+ Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext()));
+ }
+ return nullptr;
+}
+
+already_AddRefed<CanonicalBrowsingContext>
+CanonicalBrowsingContext::TopCrossChromeBoundary() {
+ RefPtr<CanonicalBrowsingContext> bc(this);
+ while (RefPtr<CanonicalBrowsingContext> parent =
+ bc->GetParentCrossChromeBoundary()) {
+ bc = parent.forget();
+ }
+ return bc.forget();
+}
+
+Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
+ RefPtr<CanonicalBrowsingContext> 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;
+}
+
+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<SessionHistoryEntry> 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<LoadingSessionHistoryInfo>& aLoadingInfo, int32_t* aRequestedIndex,
+ int32_t* aLength) {
+ *aRequestedIndex = -1;
+ *aLength = 0;
+
+ 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<nsISHEntry> shEntry;
+ parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ index, getter_AddRefs(shEntry));
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
+ if (she) {
+ aLoadingInfo.emplace(she);
+ mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
+ aLoadingInfo.value().mLoadId, she.get()});
+ *aRequestedIndex = shistory->GetRequestedIndex();
+ *aLength = shistory->GetCount();
+ Unused << SetHistoryID(she->DocshellID());
+ }
+ break;
+ }
+ }
+ }
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, nsIChannel* aChannel) {
+ RefPtr<SessionHistoryEntry> entry;
+ const LoadingSessionHistoryInfo* existingLoadingInfo =
+ aLoadState->GetLoadingSessionHistoryInfo();
+ if (existingLoadingInfo) {
+ entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
+ existingLoadingInfo->mLoadId, entry.get()));
+ if (!entry) {
+ return nullptr;
+ }
+ } 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<LoadingSessionHistoryInfo> loadingInfo;
+ if (existingLoadingInfo) {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
+ } else {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
+ }
+
+ MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == entry);
+
+ return loadingInfo;
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel) {
+ MOZ_ASSERT(aInfo);
+ MOZ_ASSERT(aChannel);
+
+ UniquePtr<SessionHistoryInfo> newInfo = MakeUnique<SessionHistoryInfo>(
+ aChannel, aInfo->mInfo.LoadType(),
+ aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
+
+ RefPtr<SessionHistoryEntry> newEntry = new SessionHistoryEntry(newInfo.get());
+ if (IsTop()) {
+ // Only top level pages care about Get/SetPersist.
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ newEntry->SetPersist(nsDocShell::ShouldAddToSessionHistory(uri, aChannel));
+ } else {
+ newEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
+ }
+ newEntry->SetDocshellID(GetHistoryID());
+ newEntry->SetIsDynamicallyAdded(CreatedDynamically());
+ newEntry->SetForInitialLoad(true);
+
+ // Replacing the old entry.
+ SessionHistoryEntry::SetByLoadId(aInfo->mLoadId, newEntry);
+
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
+ mLoadingEntries[i].mEntry = newEntry;
+ break;
+ }
+ }
+
+ return MakeUnique<LoadingSessionHistoryInfo>(newEntry, aInfo->mLoadId);
+}
+
+void CanonicalBrowsingContext::CallOnAllTopDescendants(
+ const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
+ aCallback) {
+#ifdef DEBUG
+ RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary();
+ MOZ_ASSERT(!parent, "Should only call on top chrome BC");
+#endif
+
+ nsTArray<RefPtr<BrowsingContextGroup>> 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<CanonicalBrowsingContext> 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) {
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this,
+ aLoadId));
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aLoadId) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+ return;
+ }
+
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
+
+ bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
+ newActiveEntry->SetForInitialLoad(false);
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+
+ // 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()) {
+ mActiveEntry = newActiveEntry;
+ if (loadFromSessionHistory) {
+ // XXX Synchronize browsing context tree and session history tree?
+ shistory->UpdateIndex();
+ } else {
+ 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, mActiveEntry);
+ }
+ }
+
+ if (addEntry) {
+ shistory->AddEntry(mActiveEntry, aPersist);
+ }
+ }
+ } 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;
+ // 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)) {
+ // 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());
+ }
+ }
+ }
+ }
+
+ 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?
+}
+
+static already_AddRefed<nsDocShellLoadState> CreateLoadInfo(
+ SessionHistoryEntry* aEntry) {
+ const SessionHistoryInfo& info = aEntry->Info();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(info.GetURI()));
+ info.FillLoadInfo(*loadState);
+ UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
+ loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
+
+ return loadState.forget();
+}
+
+void CanonicalBrowsingContext::NotifyOnHistoryReload(
+ bool aForceReload, bool& aCanReload,
+ Maybe<RefPtr<nsDocShellLoadState>>& aLoadState,
+ Maybe<bool>& 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(CreateLoadInfo(mActiveEntry));
+ aReloadActiveEntry.emplace(true);
+ if (aForceReload) {
+ shistory->RemoveFrameEntries(mActiveEntry);
+ }
+ } else if (!mLoadingEntries.IsEmpty()) {
+ const LoadingSessionHistoryEntry& loadingEntry =
+ mLoadingEntries.LastElement();
+ aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry));
+ aReloadActiveEntry.emplace(false);
+ if (aForceReload) {
+ SessionHistoryEntry* entry =
+ SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId);
+ if (entry) {
+ shistory->RemoveFrameEntries(entry);
+ }
+ }
+ }
+
+ if (aLoadState) {
+ int32_t index = 0;
+ int32_t requestedIndex = -1;
+ int32_t length = 0;
+ shistory->GetIndex(&index);
+ shistory->GetRequestedIndex(&requestedIndex);
+ shistory->GetCount(&length);
+ aLoadState.ref()->SetLoadIsFromSessionHistory(
+ requestedIndex >= 0 ? requestedIndex : index, length,
+ 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<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory) {
+ return;
+ }
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ RefPtr<SessionHistoryEntry> 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<int32_t> 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();
+
+ // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
+ HistoryCommitIndexAndLength(aChangeID, caller);
+
+ static_cast<nsSHistory*>(shistory)->LogHistory();
+}
+
+void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
+ SessionHistoryInfo* aInfo) {
+ if (!mActiveEntry) {
+ return;
+ }
+
+ mActiveEntry->SetInfo(aInfo);
+ // Notify children of the update
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ shistory->NotifyOnHistoryReplaceEntry();
+ shistory->UpdateRootBrowsingContextState();
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ // 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<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
+}
+
+void CanonicalBrowsingContext::RemoveFromSessionHistory() {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ bool didRemove;
+ AutoTArray<nsID, 16> ids({GetHistoryID()});
+ shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
+ if (didRemove) {
+ BrowsingContext* rootBC = shistory->GetBrowsingContext();
+ if (rootBC) {
+ if (!rootBC->IsInProcess()) {
+ Unused << rootBC->Canonical()
+ ->GetContentParent()
+ ->SendDispatchLocationChangeEvent(rootBC);
+ } else if (rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+ }
+ }
+ }
+}
+
+void CanonicalBrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ Maybe<ContentParentId> aContentId,
+ std::function<void(int32_t&&)>&& aResolver) {
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ return;
+ }
+
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ return;
+ }
+
+ CheckedInt<int32_t> 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;
+ }
+
+ // 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<ContentParentId> 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<nsSHistory::LoadEntryResult> loadResults;
+ nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
+ return;
+ }
+ if (epoch < aHistoryEpoch || aContentId != id) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
+ shistory->SetEpoch(aHistoryEpoch, aContentId);
+ }
+ aResolver(shistory->GetRequestedIndex());
+ nsSHistory::LoadURIs(loadResults);
+}
+
+JSObject* CanonicalBrowsingContext::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> 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<AsyncEventDispatcher>(
+ element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ dispatcher->PostDOMEvent();
+}
+
+void CanonicalBrowsingContext::CanonicalDiscard() {
+ if (mTabMediaController) {
+ mTabMediaController->Shutdown();
+ mTabMediaController = nullptr;
+ }
+
+ if (IsTop()) {
+ BackgroundSessionStorageManager::RemoveManager(Id());
+ }
+}
+
+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<OwningNonNull<BrowsingContext>>& aRoots) {
+ nsTHashtable<nsCStringHashKey> 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.PutEntry(siteOrigin);
+ }
+ }
+ });
+ }
+
+ return uniqueSiteOrigins.Count();
+}
+
+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(const nsAString& aURI,
+ const LoadURIOptions& aOptions,
+ ErrorResult& aError) {
+ RefPtr<nsDocShellLoadState> 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<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED);
+ }
+
+ if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoBack(aRequireUserInteraction);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoBack(this, cancelContentJSEpoch,
+ aRequireUserInteraction);
+ }
+}
+void CanonicalBrowsingContext::GoForward(
+ const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED);
+ }
+
+ if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoForward(aRequireUserInteraction);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoForward(this, cancelContentJSEpoch,
+ aRequireUserInteraction);
+ }
+}
+void CanonicalBrowsingContext::GoToIndex(
+ int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED);
+ }
+
+ if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GotoIndex(aIndex);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch);
+ }
+}
+void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED);
+ }
+
+ if (auto* 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);
+ }
+
+ // 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::ProcessReady() {
+ if (!mPromise) {
+ return;
+ }
+
+ // Wait for our blocker promise to resolve, if present.
+ if (mPrepareToChangePromise) {
+ mPrepareToChangePromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}](bool) { self->Finish(); },
+ [self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); });
+ return;
+ }
+
+ Finish();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::Finish() {
+ if (!mPromise) {
+ return;
+ }
+
+ RefPtr<CanonicalBrowsingContext> target(mTarget);
+ if (target->IsDiscarded()) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // 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.
+ if (!target->AncestorsAreCurrent()) {
+ NS_WARNING("Ancestor context is no longer current");
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // If this BrowsingContext is embedded within the parent process, perform the
+ // process switch directly.
+ if (Element* browserElement = target->GetEmbedderElement()) {
+ MOZ_DIAGNOSTIC_ASSERT(target->IsTop(),
+ "We shouldn't be trying to change the remoteness of "
+ "non-remote iframes");
+
+ nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
+ if (!browser) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryObject(browserElement);
+ MOZ_RELEASE_ASSERT(frameLoaderOwner,
+ "embedder browser must be nsFrameLoaderOwner");
+
+ // Tell frontend code that this browser element is about to change process.
+ nsresult rv = browser->BeforeChangeRemoteness();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ // 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, mReplaceBrowsingContext, mSpecificGroup, error);
+ if (error.Failed()) {
+ Cancel(error.StealNSResult());
+ return;
+ }
+
+ // Tell frontend the load is done.
+ bool loadResumed = false;
+ rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ return;
+ }
+
+ // We did it! The process switch is complete.
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
+ if (!newBrowser) {
+ if (mContentParent) {
+ // Failed to create the BrowserParent somehow! Abort the process switch
+ // attempt.
+ Cancel(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (!loadResumed) {
+ RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
+ if (error.Failed()) {
+ Cancel(error.StealNSResult());
+ return;
+ }
+
+ rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
+ /* aHistoryIndex */ -1);
+ if (NS_FAILED(rv)) {
+ Cancel(error.StealNSResult());
+ return;
+ }
+ }
+ } else if (!loadResumed) {
+ newBrowser->ResumeLoad(mPendingSwitchId);
+ }
+
+ mPromise->Resolve(newBrowser, __func__);
+ Clear();
+ return;
+ }
+
+ if (NS_WARN_IF(!mContentParent)) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<WindowGlobalParent> embedderWindow = target->GetEmbedderWindowGlobal();
+ if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
+ if (NS_WARN_IF(!embedderBrowser)) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Pull load flags from our embedder browser.
+ nsCOMPtr<nsILoadContext> loadContext = embedderBrowser->GetLoadContext();
+ MOZ_DIAGNOSTIC_ASSERT(
+ loadContext->UseRemoteTabs() && loadContext->UseRemoteSubframes(),
+ "Not supported without fission");
+
+ // NOTE: These are the only flags we actually care about
+ uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
+ nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
+ if (loadContext->UsePrivateBrowsing()) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+ }
+
+ RefPtr<WindowGlobalParent> oldWindow = target->GetCurrentWindowGlobal();
+ RefPtr<BrowserParent> oldBrowser =
+ oldWindow ? oldWindow->GetBrowserParent() : nullptr;
+ bool wasRemote = oldWindow && oldWindow->IsProcessRoot();
+
+ // Update which process is considered the current owner
+ uint64_t inFlightProcessId = target->OwnerProcessId();
+ target->SetInFlightProcessId(inFlightProcessId);
+ target->SetOwnerProcessId(mContentParent->ChildID());
+
+ auto resetInFlightId = [target, inFlightProcessId] {
+ target->ClearInFlightProcessId(inFlightProcessId);
+ };
+
+ // If we were in a remote frame, trigger unloading of the remote window. When
+ // the original remote window acknowledges, we can clear the in-flight ID.
+ if (wasRemote) {
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser);
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser->GetBrowserBridgeParent());
+
+ auto callback = [resetInFlightId](auto) { resetInFlightId(); };
+ oldBrowser->SendWillChangeProcess(callback, callback);
+ oldBrowser->Destroy();
+ }
+
+ MOZ_ASSERT(!mReplaceBrowsingContext, "Cannot replace BC for subframe");
+ nsCOMPtr<nsIPrincipal> initialPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(
+ target->OriginAttributesRef(),
+ /* isFirstParty */ false);
+ WindowGlobalInit windowInit =
+ WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
+
+ // Create and initialize our new BrowserBridgeParent.
+ TabId tabId(nsContentUtils::GenerateTabId());
+ RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
+ nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
+ windowInit, chromeFlags, tabId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ return;
+ }
+
+ // 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<BrowserParent> newBrowser = bridge->GetBrowserParent();
+ {
+ auto callback = [wasRemote, resetInFlightId](auto) {
+ if (!wasRemote) {
+ resetInFlightId();
+ }
+ };
+
+ ManagedEndpoint<PBrowserBridgeChild> endpoint =
+ embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
+ if (NS_WARN_IF(!endpoint.IsValid())) {
+ Cancel(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ 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__);
+ Clear();
+}
+
+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<PendingRemotenessChange> 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;
+ mPrepareToChangePromise = nullptr;
+}
+
+CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
+ CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId, bool aReplaceBrowsingContext)
+ : mTarget(aTarget),
+ mPromise(aPromise),
+ mPendingSwitchId(aPendingSwitchId),
+ mReplaceBrowsingContext(aReplaceBrowsingContext) {}
+
+CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
+ MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup &&
+ !mPrepareToChangePromise,
+ "should've already been Cancel() or Complete()-ed");
+}
+
+BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
+ if (auto* wg = GetCurrentWindowGlobal()) {
+ return wg->GetBrowserParent();
+ }
+ return nullptr;
+}
+
+RefPtr<CanonicalBrowsingContext::RemotenessPromise>
+CanonicalBrowsingContext::ChangeRemoteness(const nsACString& aRemoteType,
+ uint64_t aPendingSwitchId,
+ bool aReplaceBrowsingContext,
+ uint64_t aSpecificGroupId) {
+ 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(!aReplaceBrowsingContext || IsTop(),
+ "Cannot replace BrowsingContext for subframes");
+ MOZ_DIAGNOSTIC_ASSERT(aSpecificGroupId == 0 || aReplaceBrowsingContext,
+ "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 already.
+ RefPtr<WindowGlobalParent> 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__);
+ }
+
+ if (aRemoteType.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");
+ }
+
+ RefPtr<BrowserParent> embedderBrowser =
+ embedderWindowGlobal->GetBrowserParent();
+ // Switching to local. No new process, so perform switch sync.
+ if (embedderBrowser &&
+ aRemoteType == 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");
+ if (GetCurrentWindowGlobal()) {
+ MOZ_DIAGNOSTIC_ASSERT(GetCurrentWindowGlobal()->IsProcessRoot());
+ RefPtr<BrowserParent> oldBrowser =
+ GetCurrentWindowGlobal()->GetBrowserParent();
+
+ uint64_t targetProcessId = OwnerProcessId();
+ SetInFlightProcessId(targetProcessId);
+ auto callback = [target = RefPtr{this}, targetProcessId](auto) {
+ target->ClearInFlightProcessId(targetProcessId);
+ };
+ oldBrowser->SendWillChangeProcess(callback, callback);
+ oldBrowser->Destroy();
+ }
+
+ // If the embedder process is remote, tell that remote process to become
+ // the owner.
+ MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext);
+ MOZ_DIAGNOSTIC_ASSERT(!aRemoteType.IsEmpty());
+ SetOwnerProcessId(embedderBrowser->Manager()->ChildID());
+ Unused << embedderWindowGlobal->SendMakeFrameLocal(this, aPendingSwitchId);
+ return RemotenessPromise::CreateAndResolve(embedderBrowser, __func__);
+ }
+
+ // Switching to remote. Wait for new process to launch before switch.
+ auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
+ RefPtr<PendingRemotenessChange> change = new PendingRemotenessChange(
+ this, promise, aPendingSwitchId, aReplaceBrowsingContext);
+ 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 (aSpecificGroupId) {
+ change->mSpecificGroup =
+ BrowsingContextGroup::GetOrCreate(aSpecificGroupId);
+ change->mSpecificGroup->AddKeepAlive();
+ }
+
+ // Call `prepareToChangeRemoteness` in parallel with starting a new process
+ // for <browser> loads.
+ if (IsTop() && GetEmbedderElement()) {
+ nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
+ if (!browser) {
+ change->Cancel(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ RefPtr<Promise> blocker;
+ nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
+ if (NS_FAILED(rv)) {
+ change->Cancel(rv);
+ return promise.forget();
+ }
+ change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker);
+ }
+
+ if (aRemoteType.IsEmpty()) {
+ change->ProcessReady();
+ } else {
+ // 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 =
+ aReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
+
+ change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
+ /* aRemoteType = */ aRemoteType,
+ /* aGroup = */ finalGroup,
+ /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
+ /* aPreferUsed = */ false);
+ 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();
+ change->mContentParent->WaitForLaunchAsync()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [change](ContentParent*) { change->ProcessReady(); },
+ [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); });
+ }
+ return promise.forget();
+}
+
+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;
+ }
+
+ // 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<nsIURI> 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;
+ }
+
+ // 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;
+ }
+
+ // Session-history-in-parent implementation relies currently on getting a
+ // round trip through a child process.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ 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) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED);
+ }
+ mCurrentLoad = aLoad;
+
+ if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
+ mCurrentLoad = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) {
+ mCurrentLoad = nullptr;
+
+ if (!aForProcessSwitch) {
+ // Resetting the current load identifier on a discarded context
+ // has no effect when a document load has finished.
+ Unused << SetCurrentLoadIdentifier(Nothing());
+ }
+}
+
+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);
+
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
+ aChangeID);
+ });
+}
+
+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::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;
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CanonicalBrowsingContext, BrowsingContext,
+ mSessionHistory, mContainerFeaturePolicy)
+
+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..dd4226d7e1
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -0,0 +1,383 @@
+/* -*- 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/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 nsISHistory;
+class nsIWidget;
+class nsSHistory;
+class nsBrowserStatusFilter;
+class nsSecureBrowserUI;
+class CallerWillNotifyHistoryIndexAndLengthChanges;
+
+namespace mozilla {
+enum class CallState;
+
+namespace net {
+class DocumentLoadListener;
+}
+
+namespace dom {
+
+class BrowserParent;
+class FeaturePolicy;
+struct LoadURIOptions;
+class MediaController;
+struct LoadingSessionHistoryInfo;
+class SessionHistoryEntry;
+class WindowGlobalParent;
+
+// 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_CLASS_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+
+ static already_AddRefed<CanonicalBrowsingContext> Get(uint64_t aId);
+ static CanonicalBrowsingContext* Cast(BrowsingContext* aContext);
+ static const CanonicalBrowsingContext* Cast(const BrowsingContext* aContext);
+ static already_AddRefed<CanonicalBrowsingContext> Cast(
+ already_AddRefed<BrowsingContext>&& 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);
+
+ void SetInFlightProcessId(uint64_t aProcessId);
+ void ClearInFlightProcessId(uint64_t aProcessId);
+ uint64_t GetInFlightProcessId() const { return mInFlightProcessId; }
+
+ // 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 GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& 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<nsIWidget> GetParentProcessWidgetContaining();
+
+ // Same as `GetParentWindowContext`, but will also cross <browser> and
+ // content/chrome boundaries.
+ already_AddRefed<WindowGlobalParent> GetEmbedderWindowGlobal() const;
+
+ already_AddRefed<CanonicalBrowsingContext> GetParentCrossChromeBoundary();
+
+ already_AddRefed<CanonicalBrowsingContext> TopCrossChromeBoundary();
+ Nullable<WindowProxyHolder> GetTopChromeWindow();
+
+ nsISHistory* GetSessionHistory();
+ SessionHistoryEntry* GetActiveSessionHistoryEntry();
+
+ UniquePtr<LoadingSessionHistoryInfo> CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, nsIChannel* aChannel);
+
+ UniquePtr<LoadingSessionHistoryInfo> ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel);
+
+ // 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<mozilla::CallState(CanonicalBrowsingContext*)>&
+ aCallback);
+
+ void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID,
+ uint32_t aLoadType, bool aPersist,
+ bool aCloneEntryChildren);
+
+ // 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<RefPtr<nsDocShellLoadState>>& aLoadState,
+ Maybe<bool>& aReloadActiveEntry);
+
+ // See BrowsingContext::SetActiveSessionHistoryEntry.
+ void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey,
+ const nsID& aChangeID);
+
+ void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo);
+
+ void RemoveDynEntriesFromActiveSessionHistoryEntry();
+
+ void RemoveFromSessionHistory();
+
+ void HistoryGo(int32_t aIndex, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction,
+ Maybe<ContentParentId> aContentId,
+ std::function<void(int32_t&&)>&& aResolver);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<mozilla::OwningNonNull<BrowsingContext>>& aRoots);
+
+ // 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 LoadURI(const nsAString& aURI, const LoadURIOptions& aOptions,
+ ErrorResult& aError);
+
+ void GoBack(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction);
+ void GoForward(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction);
+ void GoToIndex(int32_t aIndex,
+ const Optional<int32_t>& aCancelContentJSEpoch);
+ void Reload(uint32_t aReloadFlags);
+ void Stop(uint32_t aStopFlags);
+
+ BrowserParent* GetBrowserParent() const;
+
+ // 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<RefPtr<BrowserParent>, nsresult, false>;
+ RefPtr<RemotenessPromise> ChangeRemoteness(const nsACString& aRemoteType,
+ uint64_t aPendingSwitchId,
+ bool aReplaceBrowsingContext,
+ uint64_t aSpecificGroupId);
+
+ // 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);
+
+ bool HasHistoryEntry(nsISHEntry* aEntry);
+
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ void AddLoadingSessionHistoryEntry(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry);
+
+ void GetLoadingSessionHistoryInfoFromParent(
+ Maybe<LoadingSessionHistoryInfo>& aLoadingInfo, int32_t* aRequestedIndex,
+ int32_t* aLength);
+
+ void HistoryCommitIndexAndLength();
+
+ void ResetScalingZoom();
+
+ void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy);
+ FeaturePolicy* GetContainerFeaturePolicy() const {
+ return mContainerFeaturePolicy;
+ }
+
+ protected:
+ // Called when the browsing context is being discarded.
+ void CanonicalDiscard();
+
+ 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;
+
+ ~CanonicalBrowsingContext() = default;
+
+ class PendingRemotenessChange {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange)
+
+ PendingRemotenessChange(CanonicalBrowsingContext* aTarget,
+ RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId,
+ bool aReplaceBrowsingContext);
+
+ void Cancel(nsresult aRv);
+
+ private:
+ friend class CanonicalBrowsingContext;
+
+ ~PendingRemotenessChange();
+ void ProcessReady();
+ void Finish();
+ void Clear();
+
+ RefPtr<CanonicalBrowsingContext> mTarget;
+ RefPtr<RemotenessPromise::Private> mPromise;
+ RefPtr<GenericPromise> mPrepareToChangePromise;
+ RefPtr<ContentParent> mContentParent;
+ RefPtr<BrowsingContextGroup> mSpecificGroup;
+
+ uint64_t mPendingSwitchId;
+ bool mReplaceBrowsingContext;
+ };
+
+ 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.
+ void EndDocumentLoad(bool aForProcessSwitch);
+
+ bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState,
+ uint64_t* aOuterWindowId);
+
+ void HistoryCommitIndexAndLength(
+ const nsID& aChangeID,
+ const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller);
+
+ // 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;
+
+ // The ID of the former owner process during an ownership change, which may
+ // have in-flight messages that assume it is still the owner.
+ uint64_t mInFlightProcessId = 0;
+
+ 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();
+
+ // The current remoteness change which is in a pending state.
+ RefPtr<PendingRemotenessChange> mPendingRemotenessChange;
+
+ RefPtr<nsSHistory> 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<MediaController> mTabMediaController;
+
+ RefPtr<net::DocumentLoadListener> mCurrentLoad;
+
+ struct LoadingSessionHistoryEntry {
+ uint64_t mLoadId = 0;
+ RefPtr<SessionHistoryEntry> mEntry;
+ };
+ nsTArray<LoadingSessionHistoryEntry> mLoadingEntries;
+ RefPtr<SessionHistoryEntry> mActiveEntry;
+
+ RefPtr<nsSecureBrowserUI> mSecureBrowserUI;
+ RefPtr<BrowsingContextWebProgress> mWebProgress;
+ RefPtr<nsBrowserStatusFilter> mStatusFilter;
+
+ RefPtr<FeaturePolicy> mContainerFeaturePolicy;
+};
+
+} // 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..3580127f6a
--- /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<ChildProcessChannelListener> sCPCLSingleton;
+
+void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier,
+ Callback&& aCallback) {
+ if (auto args = mChannelArgs.GetAndRemove(aIdentifier)) {
+ nsresult rv =
+ aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints),
+ args->mTiming);
+ args->mResolver(rv);
+ } else {
+ mCallbacks.Put(aIdentifier, std::move(aCallback));
+ }
+}
+
+void ChildProcessChannelListener::OnChannelReady(
+ nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming,
+ Resolver&& aResolver) {
+ if (auto callback = mCallbacks.GetAndRemove(aIdentifier)) {
+ nsresult rv =
+ (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming);
+ aResolver(rv);
+ } else {
+ mChannelArgs.Put(aIdentifier,
+ {aLoadState, std::move(aStreamFilterEndpoints), aTiming,
+ std::move(aResolver)});
+ }
+}
+
+ChildProcessChannelListener::~ChildProcessChannelListener() {
+ for (auto& args : mChannelArgs) {
+ args.GetData().mResolver(NS_ERROR_FAILURE);
+ }
+}
+
+already_AddRefed<ChildProcessChannelListener>
+ChildProcessChannelListener::GetSingleton() {
+ if (!sCPCLSingleton) {
+ sCPCLSingleton = new ChildProcessChannelListener();
+ ClearOnShutdown(&sCPCLSingleton);
+ }
+ RefPtr<ChildProcessChannelListener> 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..232ca12dd5
--- /dev/null
+++ b/docshell/base/ChildProcessChannelListener.h
@@ -0,0 +1,56 @@
+/* -*- 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 <functional>
+
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDataHashtable.h"
+#include "nsIChannel.h"
+#include "BackgroundUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+class ChildProcessChannelListener final {
+ NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener)
+
+ using Endpoint = mozilla::ipc::Endpoint<extensions::PStreamFilterParent>;
+ using Resolver = std::function<void(const nsresult&)>;
+ using Callback = std::function<nsresult(
+ nsDocShellLoadState*, nsTArray<Endpoint>&&, nsDOMNavigationTiming*)>;
+
+ void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback);
+
+ void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming, Resolver&& aResolver);
+
+ static already_AddRefed<ChildProcessChannelListener> GetSingleton();
+
+ private:
+ ChildProcessChannelListener() = default;
+ ~ChildProcessChannelListener();
+ struct CallbackArgs {
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsTArray<Endpoint> mStreamFilterEndpoints;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ Resolver mResolver;
+ };
+
+ // TODO Backtrack.
+ nsDataHashtable<nsUint64HashKey, Callback> mCallbacks;
+ nsDataHashtable<nsUint64HashKey, CallbackArgs> mChannelArgs;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_ChildProcessChannelListener_h)
diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h
new file mode 100644
index 0000000000..12609cc110
--- /dev/null
+++ b/docshell/base/IHistory.h
@@ -0,0 +1,150 @@
+/* -*- 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 "nsDataHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsTObserverArray.h"
+
+class nsIURI;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace dom {
+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)
+
+ /**
+ * 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;
+
+ /**
+ * 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.
+ */
+ virtual void NotifyVisited(nsIURI*, VisitedStatus) = 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.
+ */
+ NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
+ uint32_t aFlags) = 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..e90ad77f80
--- /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<dom::Element> 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::MutableHandleValue 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<nsILoadContext*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+static already_AddRefed<nsILoadContext> CreateInstance(bool aPrivate) {
+ OriginAttributes oa;
+ oa.mPrivateBrowsingId = aPrivate ? 1 : 0;
+
+ nsCOMPtr<nsILoadContext> lc = new LoadContext(oa);
+
+ return lc.forget();
+}
+
+already_AddRefed<nsILoadContext> CreateLoadContext() {
+ return CreateInstance(false);
+}
+
+already_AddRefed<nsILoadContext> 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<nsILoadContext> CreateLoadContext();
+already_AddRefed<nsILoadContext> 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<nsILoadContext> 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<nsIPrivateBrowsingChannel> 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<nsILoadContext> 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..689934f714
--- /dev/null
+++ b/docshell/base/SerializedLoadContext.h
@@ -0,0 +1,97 @@
+/* -*- 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<SerializedLoadContext> {
+ typedef SerializedLoadContext paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ nsAutoCString suffix;
+ aParam.mOriginAttributes.CreateSuffix(suffix);
+
+ WriteParam(aMsg, aParam.mIsNotNull);
+ WriteParam(aMsg, aParam.mIsContent);
+ WriteParam(aMsg, aParam.mIsPrivateBitValid);
+ WriteParam(aMsg, aParam.mUseRemoteTabs);
+ WriteParam(aMsg, aParam.mUseRemoteSubframes);
+ WriteParam(aMsg, aParam.mUseTrackingProtection);
+ WriteParam(aMsg, suffix);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ nsAutoCString suffix;
+ if (!ReadParam(aMsg, aIter, &aResult->mIsNotNull) ||
+ !ReadParam(aMsg, aIter, &aResult->mIsContent) ||
+ !ReadParam(aMsg, aIter, &aResult->mIsPrivateBitValid) ||
+ !ReadParam(aMsg, aIter, &aResult->mUseRemoteTabs) ||
+ !ReadParam(aMsg, aIter, &aResult->mUseRemoteSubframes) ||
+ !ReadParam(aMsg, aIter, &aResult->mUseTrackingProtection) ||
+ !ReadParam(aMsg, aIter, &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..e9ad5017cf
--- /dev/null
+++ b/docshell/base/SyncedContext.h
@@ -0,0 +1,333 @@
+/* -*- 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 <array>
+#include <type_traits>
+#include <utility>
+#include "mozilla/Attributes.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;
+} // namespace IPC
+
+namespace mozilla {
+namespace ipc {
+class IProtocol;
+class IPCResult;
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class ContentParent;
+class ContentChild;
+template <typename T>
+class MaybeDiscarded;
+
+namespace syncedcontext {
+
+template <size_t I>
+using Index = typename std::integral_constant<size_t, I>;
+
+using IndexSet = EnumSet<size_t, uint64_t>;
+
+template <typename Context>
+class Transaction {
+ public:
+ // 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 <size_t I, typename U>
+ void Set(U&& aValue) {
+ mValues.Get(Index<I>{}) = std::forward<U>(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<Context>& aOwner,
+ ContentParent* aSource);
+
+ // Called from `ContentChild` in response to a transaction from the parent.
+ mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
+ uint64_t aEpoch, ContentChild* aSource);
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<Transaction<Context>>;
+
+ void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const;
+ bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ 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);
+
+ // Returns the set of fields which failed to validate, or an empty set if
+ // there were no validation errors.
+ IndexSet Validate(Context* aOwner, ContentParent* aSource);
+
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ Context::FieldValues::EachIndex(aCallback);
+ }
+
+ template <size_t I>
+ static uint64_t& FieldEpoch(Index<I>, Context* aContext) {
+ return std::get<I>(aContext->mFields.mEpochs);
+ }
+
+ typename Context::FieldValues mValues;
+ IndexSet mModified;
+};
+
+template <typename Base, size_t Count>
+class FieldValues : public Base {
+ public:
+ // The number of fields stored by this type.
+ static constexpr size_t count = Count;
+ static_assert(count < 64,
+ "At most 64 synced fields are supported. Please file a bug if "
+ "you need additional fields.");
+
+ // 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<I>` for each index less than the
+ // field count.
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ EachIndexInner(std::make_index_sequence<count>(),
+ std::forward<F>(aCallback));
+ }
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<FieldValues<Base, Count>>;
+
+ void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const;
+ bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ mozilla::ipc::IProtocol* aActor);
+
+ template <typename F, size_t... Indexes>
+ static void EachIndexInner(std::index_sequence<Indexes...> aIndexes,
+ F&& aCallback) {
+ (aCallback(Index<Indexes>()), ...);
+ }
+};
+
+// Storage related to synchronized context fields. Contains both a tuple of
+// individual field values, and epoch information for field synchronization.
+template <typename Values>
+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 <size_t I>
+ const auto& Get() const {
+ return RawValues().Get(Index<I>{});
+ }
+
+ // 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 <size_t I, typename U>
+ void SetWithoutSyncing(U&& aValue) {
+ GetNonSyncingReference<I>() = 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 <size_t I>
+ auto& GetNonSyncingReference() {
+ return RawValues().Get(Index<I>{});
+ }
+
+ FieldStorage() = default;
+ explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {}
+
+ private:
+ template <typename Context>
+ friend class Transaction;
+
+ // Data Members
+ std::array<uint64_t, Values::count> mEpochs{};
+ Values mValues;
+};
+
+// 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 <typename T>
+struct GetFieldSetterType {
+ using SetterArg = T;
+};
+template <>
+struct GetFieldSetterType<nsString> {
+ using SetterArg = const nsAString&;
+};
+template <>
+struct GetFieldSetterType<nsCString> {
+ using SetterArg = const nsACString&;
+};
+template <typename T>
+using FieldSetterType = typename GetFieldSetterType<T>::SetterArg;
+
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name,
+#define MOZ_DECL_SYNCED_CONTEXT_FIELDS_DECL(name, type) \
+ /* index based field lookup */ \
+ type& Get(FieldIndex<IDX_##name>) { return m##name; } \
+ const type& Get(FieldIndex<IDX_##name>) const { return m##name; } \
+ \
+ /* storage for the field */ \
+ type m##name{};
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \
+ const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \
+ \
+ [[nodiscard]] nsresult Set##name( \
+ ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \
+ Transaction txn; \
+ txn.template Set<IDX_##name>(std::move(aValue)); \
+ return txn.Commit(this); \
+ } \
+ void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> 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 <typename U> \
+ void Set##name(U&& aValue) { \
+ this->template Set<IDX_##name>(std::forward<U>(aValue)); \
+ }
+#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \
+ case IDX_##name: \
+ return #name;
+
+// 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 <size_t I> \
+ using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \
+ \
+ /* Struct containing the data for all synced fields as members */ \
+ struct BaseFieldValues { \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELDS_DECL) \
+ }; \
+ using FieldValues = \
+ typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \
+ SYNCED_FIELD_COUNT>; \
+ \
+ protected: \
+ friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \
+ \
+ public: \
+ /* Transaction types for bulk mutations */ \
+ using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ 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 "<unknown>"; \
+ } \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET)
+
+} // namespace syncedcontext
+} // namespace dom
+
+namespace ipc {
+
+template <typename Context>
+struct IPDLParamTraits<dom::syncedcontext::Transaction<Context>> {
+ typedef dom::syncedcontext::Transaction<Context> paramType;
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aMsg, aActor);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ return aResult->Read(aMsg, aIter, aActor);
+ }
+};
+
+template <typename Base, size_t Count>
+struct IPDLParamTraits<dom::syncedcontext::FieldValues<Base, Count>> {
+ typedef dom::syncedcontext::FieldValues<Base, Count> paramType;
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aMsg, aActor);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ return aResult->Read(aMsg, aIter, 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..d4fda40162
--- /dev/null
+++ b/docshell/base/SyncedContextInlines.h
@@ -0,0 +1,213 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace dom {
+namespace syncedcontext {
+
+template <typename Context>
+nsCString FormatValidationError(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 <typename Context>
+nsresult Transaction<Context>::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<Context>(
+ failedFields, "CanSet failed for field(s): ");
+ MOZ_CRASH_UNSAFE_PRINTF("%s", error.get());
+ }
+
+ 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);
+ return NS_OK;
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, ContentParent* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetLog(), 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<Context>(
+ failedFields,
+ "Invalid Transaction from Child - CanSet failed for field(s): ");
+ return IPC_FAIL(aSource, error.get());
+ }
+
+ BrowsingContextGroup* group = owner->Group();
+ group->EachOtherParent(aSource, [&](ContentParent* aParent) {
+ owner->SendCommitTransaction(aParent, *this,
+ aParent->GetBrowsingContextFieldEpoch());
+ });
+
+ Apply(owner);
+ return IPC_OK();
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, uint64_t aEpoch,
+ ContentChild* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetLog(), 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) {
+ mModified -= idx;
+ }
+ });
+
+ Apply(owner);
+ return IPC_OK();
+}
+
+template <typename Context>
+void Transaction<Context>::Apply(Context* aOwner) {
+ 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 <typename Context>
+IndexSet Transaction<Context>::Validate(Context* aOwner,
+ ContentParent* aSource) {
+ IndexSet failedFields;
+ // Validate that the set from content is allowed before continuing.
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx) &&
+ NS_WARN_IF(!aOwner->CanSet(idx, mValues.Get(idx), aSource))) {
+ failedFields += idx;
+ }
+ });
+ return failedFields;
+}
+
+template <typename Context>
+void Transaction<Context>::Write(IPC::Message* aMsg,
+ mozilla::ipc::IProtocol* aActor) const {
+ // Record which field indices will be included, and then write those fields
+ // out.
+ uint64_t modified = mModified.serialize();
+ WriteIPDLParam(aMsg, aActor, modified);
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ WriteIPDLParam(aMsg, aActor, mValues.Get(idx));
+ }
+ });
+}
+
+template <typename Context>
+bool Transaction<Context>::Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ mozilla::ipc::IProtocol* aActor) {
+ // Read in which field indices were sent by the remote, followed by the fields
+ // identified by those indices.
+ uint64_t modified = 0;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &modified)) {
+ return false;
+ }
+ mModified.deserialize(modified);
+
+ bool ok = true;
+ EachIndex([&](auto idx) {
+ if (ok && mModified.contains(idx)) {
+ ok = ReadIPDLParam(aMsg, aIter, aActor, &mValues.Get(idx));
+ }
+ });
+ return ok;
+}
+
+template <typename Base, size_t Count>
+void FieldValues<Base, Count>::Write(IPC::Message* aMsg,
+ 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(aMsg, aActor, this->Get(idx)); });
+}
+
+template <typename Base, size_t Count>
+bool FieldValues<Base, Count>::Read(const IPC::Message* aMsg,
+ PickleIterator* aIter,
+ 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(aMsg, aIter, aActor, &this->Get(idx));
+ }
+ });
+ return ok;
+}
+
+} // namespace syncedcontext
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_SyncedContextInlines_h)
diff --git a/docshell/base/URIFixup.jsm b/docshell/base/URIFixup.jsm
new file mode 100644
index 0000000000..797b1ed981
--- /dev/null
+++ b/docshell/base/URIFixup.jsm
@@ -0,0 +1,1054 @@
+/* -*- 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] */
+
+var EXPORTED_SYMBOLS = ["URIFixup", "URIFixupInfo"];
+
+const { ComponentUtils } = ChromeUtils.import(
+ "resource://gre/modules/ComponentUtils.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "externalProtocolService",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "defaultProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=default",
+ "nsIProtocolHandler"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "fileProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=file",
+ "nsIFileProtocolHandler"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "fixupSchemeTypos",
+ "browser.fixup.typo.scheme",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "dnsFirstForSingleWords",
+ "browser.fixup.dns_first_for_single_words",
+ false
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "keywordEnabled",
+ "keyword.enabled",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "alternateEnabled",
+ "browser.fixup.alternate.enabled",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "alternateProtocol",
+ "browser.fixup.alternate.protocol",
+ "https"
+);
+
+const {
+ FIXUP_FLAG_NONE,
+ FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+ FIXUP_FLAG_PRIVATE_CONTEXT,
+ FIXUP_FLAG_FIX_SCHEME_TYPOS,
+} = Ci.nsIURIFixup;
+
+const COMMON_PROTOCOLS = ["http", "https", "ftp", "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(
+ this,
+ "userPasswordRegex",
+ () => /^([a-z+.-]+:\/{0,3})*[^\/@]+@.+/i
+);
+
+// Regex used to identify specific URI characteristics to disallow searching.
+XPCOMUtils.defineLazyGetter(
+ this,
+ "uriLikeRegex",
+ () => /(:\d{1,5}([?#/]|$)|\/.*[?#])/
+);
+
+// Regex used to identify numbers.
+XPCOMUtils.defineLazyGetter(this, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/);
+
+// Regex used to identify tab separated content (having at least 2 tabs).
+XPCOMUtils.defineLazyGetter(this, "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:
+//
+// <hostname>:<port> or
+// <hostname>:<port>/
+//
+// Where <hostname> is a string of alphanumeric characters and dashes
+// separated by dots.
+// and <port> 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
+// <user>:<password>@<host>:<port>/<url-path> 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(
+ this,
+ "possiblyHostPortRegex",
+ () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i
+);
+
+// Regex used to strip newlines.
+XPCOMUtils.defineLazyGetter(this, "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(
+ this,
+ "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(
+ this,
+ "IPv4LikeRegex",
+ () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i
+);
+XPCOMUtils.defineLazyGetter(
+ this,
+ "IPv6LikeRegex",
+ () =>
+ /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i
+);
+
+// Cache of known domains.
+XPCOMUtils.defineLazyGetter(this, "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(this, "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;
+});
+
+function URIFixup() {}
+
+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_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(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) != defaultProtocolHandler ||
+ externalProtocolService.externalProtocolHandlerExists(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.
+ (!possiblyHostPortRegex.test(uriString) &&
+ !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 &&
+ 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;
+ }
+ 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 && 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("/")) {
+ return info;
+ }
+ }
+ }
+
+ // Handle "www.<something>" 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 (
+ keywordEnabled &&
+ fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &&
+ !inputHadDuffProtocol &&
+ !checkSuffix(info).suffix &&
+ keywordURIFixup(uriString, info, isPrivateContext)
+ ) {
+ return info;
+ }
+
+ if (
+ info.fixedURI &&
+ (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix)
+ ) {
+ return info;
+ }
+
+ // If we still haven't been able to construct a valid URI, try to force a
+ // keyword match.
+ if (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
+ );
+ }
+
+ 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();
+
+ // 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;
+ },
+
+ isDomainKnown,
+
+ classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"),
+ _xpcom_factory: ComponentUtils.generateSingletonFactory(URIFixup),
+ QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]),
+};
+
+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 (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 (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 = 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 (
+ 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 || 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;
+ if (
+ !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) ||
+ !alternateEnabled ||
+ // 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.
+ if (oldHost == "localhost") {
+ 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 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;
+ }
+ }
+ if (!newHost) {
+ return false;
+ }
+
+ // Assign the new host string over the old one
+ try {
+ info.fixedURI = uri
+ .mutate()
+ .setScheme(alternateProtocol)
+ .setHost(newHost)
+ .finalize();
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw ex;
+ }
+ return false;
+ }
+ info.fixupCreatedAlternateURI = true;
+ return true;
+}
+
+/**
+ * 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;
+ if (AppConstants.platform == "win") {
+ // Check for "\"" in the url-string or just a drive (e.g. C:).
+ attemptFixup =
+ uriString.includes("\\") ||
+ (uriString.length == 2 && uriString.endsWith(":"));
+ } 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(uriString);
+ return Services.io.newURI(
+ 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 ()
+
+ // 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@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.
+ if (IPv4LikeRegex.test(uriString) || IPv6LikeRegex.test(uriString)) {
+ return false;
+ }
+
+ // Avoid 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;
+ }
+
+ // Even if the host is invalid, avoid lookup if the string has uri-like
+ // characteristics.
+ // Also avoid lookup if there's a valid userPass. We only check for spaces,
+ // the URI parser has encoded any disallowed chars at this point, but if the
+ // user typed spaces before the first @, it's unlikely a valid userPass, plus
+ // some urlbar features use the @ char and we don't want to break them.
+ let userPass = fixupInfo.fixedURI?.userPass;
+ if (
+ !uriLikeRegex.test(uriString) &&
+ !(userPass && /^[^\s@]+@/.test(uriString))
+ ) {
+ 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(possibleProtocolRegex);
+ const hasColon = matches?.[2] === ":";
+ const hasSlash2 = matches?.[3] === "//";
+
+ const isFixupSchemeTypos =
+ 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,
+ };
+}
diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp
new file mode 100644
index 0000000000..4d5b1e1781
--- /dev/null
+++ b/docshell/base/WindowContext.cpp
@@ -0,0 +1,569 @@
+/* -*- 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 "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<WindowContext>;
+
+static LazyLogModule gWindowContextLog("WindowContext");
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+using WindowContextByIdMap = nsDataHashtable<nsUint64HashKey, WindowContext*>;
+static StaticAutoPtr<WindowContextByIdMap> gWindowContexts;
+
+/* static */
+LogModule* WindowContext::GetLog() { return gWindowContextLog; }
+
+/* static */
+already_AddRefed<WindowContext> 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<WindowGlobalParent*>(this);
+}
+
+bool WindowContext::IsCached() const {
+ return mBrowsingContext->mCurrentWindowContext != this;
+}
+
+nsGlobalWindowInner* WindowContext::GetInnerWindow() const {
+ if (mInProcess) {
+ // FIXME: Replace this with something more efficient.
+ return nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowId);
+ }
+ return 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 {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ NS_ENSURE_TRUE(XRE_IsContentProcess(), nullptr);
+ nsGlobalWindowInner* innerWindow = GetInnerWindow();
+ return innerWindow ? innerWindow->GetWindowGlobalChild() : nullptr;
+}
+
+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 we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (!IsCached()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+void WindowContext::RemoveChildBrowsingContext(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
+ "Mismatched groups?");
+
+ mChildren.RemoveElement(aBrowsingContext);
+
+ // If we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (!IsCached()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+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 (mInProcess) {
+ return true;
+ }
+
+ if (XRE_IsParentProcess() && aSource) {
+ return Canonical()->GetContentParent() == aSource;
+ }
+
+ return false;
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AllowMixedContent>,
+ const bool& aAllowMixedContent,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_HasBeforeUnload>,
+ const bool& aHasBeforeUnload,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_CookieBehavior>,
+ const Maybe<uint32_t>& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>,
+ const bool& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecureContext>,
+ const bool& aIsSecureContext,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AutoplayPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ShortcutsPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return IsTop() && CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ 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<IDX_UserActivationState>) {
+ MOZ_ASSERT_IF(!mInProcess, mUserGestureStart.IsNull());
+ USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8
+ " for %s browsing context 0x%08" PRIx64,
+ static_cast<uint8_t>(GetUserActivationState()),
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ if (mInProcess) {
+ USER_ACTIVATION_LOG(
+ "Set user gesture start time for %s browsing context 0x%08" PRIx64,
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ mUserGestureStart =
+ (GetUserActivationState() == UserActivation::State::FullActivated)
+ ? TimeStamp::Now()
+ : TimeStamp();
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>,
+ 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);
+ }
+ }
+ }
+ }
+}
+
+void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
+ "Should be a WindowGlobalParent in the parent");
+
+ RefPtr<BrowsingContext> 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<WindowContext> context =
+ new WindowContext(bc, aInit.mInnerWindowId, aInit.mOuterWindowId,
+ /* aInProcess */ false, 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->GetOrInsert(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;
+}
+
+bool WindowContext::HasValidTransientUserGestureActivation() {
+ MOZ_ASSERT(mInProcess);
+
+ if (GetUserActivationState() != UserActivation::State::FullActivated) {
+ MOZ_ASSERT(mUserGestureStart.IsNull(),
+ "mUserGestureStart should be null if the document hasn't ever "
+ "been activated by user gesture");
+ 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(mInProcess);
+ MOZ_ASSERT(!IsCached());
+
+ 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();
+}
+
+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,
+ bool aInProcess, FieldValues&& aInit)
+ : mFields(std::move(aInit)),
+ mInnerWindowId(aInnerWindowId),
+ mOuterWindowId(aOuterWindowId),
+ mBrowsingContext(aBrowsingContext),
+ mInProcess(aInProcess) {
+ MOZ_ASSERT(mBrowsingContext);
+ MOZ_ASSERT(mInnerWindowId);
+ MOZ_ASSERT(mOuterWindowId);
+}
+
+WindowContext::~WindowContext() {
+ if (gWindowContexts) {
+ gWindowContexts->Remove(InnerWindowId());
+ }
+}
+
+JSObject* WindowContext::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> 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_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_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_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WindowContext)
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Write(
+ IPC::Message* aMsg, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam) {
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aMsg, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Read(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::WindowContext> wc = dom::WindowContext::GetById(id)) {
+ *aResult = std::move(wc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::WindowContext::IPCInitializer>::Write(
+ IPC::Message* aMessage, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aMessage, aActor, aInit.mInnerWindowId);
+ WriteIPDLParam(aMessage, aActor, aInit.mOuterWindowId);
+ WriteIPDLParam(aMessage, aActor, aInit.mBrowsingContextId);
+ WriteIPDLParam(aMessage, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::WindowContext::IPCInitializer>::Read(
+ const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor,
+ dom::WindowContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ return ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mInnerWindowId) &&
+ ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOuterWindowId) &&
+ ReadIPDLParam(aMessage, aIterator, aActor,
+ &aInit->mBrowsingContextId) &&
+ ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields);
+}
+
+template struct IPDLParamTraits<dom::WindowContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h
new file mode 100644
index 0000000000..f0ee15ee43
--- /dev/null
+++ b/docshell/base/WindowContext.h
@@ -0,0 +1,328 @@
+/* -*- 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/Span.h"
+#include "mozilla/dom/MaybeDiscarded.h"
+#include "mozilla/dom/SyncedContext.h"
+#include "mozilla/dom/UserActivation.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<uint32_t>) \
+ 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(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<uint64_t>) \
+ /* 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)
+
+class WindowContext : public nsISupports, public nsWrapperCache {
+ MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WindowContext)
+
+ public:
+ static already_AddRefed<WindowContext> GetById(uint64_t aInnerWindowId);
+ static LogModule* GetLog();
+
+ 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; }
+
+ bool IsCached() const;
+
+ bool IsInProcess() const { return mInProcess; }
+
+ bool HasBeforeUnload() const { return GetHasBeforeUnload(); }
+
+ bool IsLocalIP() const { return GetIsLocalIP(); }
+
+ 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 <browser> boundaries.
+ WindowContext* GetParentWindowContext();
+ WindowContext* TopWindowContext();
+
+ bool SameOriginWithTop() const;
+
+ bool IsTop() const;
+
+ Span<RefPtr<BrowsingContext>> Children() { return mChildren; }
+
+ // Cast this object to it's parent-process canonical form.
+ WindowGlobalParent* Canonical();
+
+ nsIGlobalObject* GetParentObject() const;
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> 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();
+
+ // 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();
+
+ protected:
+ WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId,
+ uint64_t aOuterWindowId, bool aInProcess,
+ 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);
+
+ // 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<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AllowMixedContent>, const bool& aAllowMixedContent,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasBeforeUnload>, const bool& aHasBeforeUnload,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_CookieBehavior>, const Maybe<uint32_t>& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderPolicy>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsSecureContext>, const bool& aIsSecureContext,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AutoplayPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ShortcutsPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ const bool& aSHEntryHasUserInteraction, ContentParent* aSource) {
+ return true;
+ }
+ bool CanSet(FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_UserActivationState>,
+ const UserActivation::State& aUserActivationState,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, bool aOldValue);
+
+ void DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue);
+
+ // 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 <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+ void DidSet(FieldIndex<IDX_UserActivationState>);
+
+ const uint64_t mInnerWindowId;
+ const uint64_t mOuterWindowId;
+ RefPtr<BrowsingContext> mBrowsingContext;
+
+ // --- 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<RefPtr<BrowsingContext>> mChildren;
+
+ bool mIsDiscarded = false;
+ bool mInProcess = false;
+
+ // 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<WindowContext>;
+
+// 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<WindowContext>;
+
+} // namespace dom
+
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::WindowContext::IPCInitializer> {
+ static void Write(IPC::Message* aMessage, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInitializer);
+
+ static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator,
+ 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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("browser.send_pings", true);
+-->
+<script>
+
+function boom() {
+ var aLink = document.createElement('a');
+ document.body.appendChild(aLink);
+ aLink.ping = "ping";
+ aLink.href = "href";
+ aLink.click();
+
+ var baseElement = document.createElement('base');
+ baseElement.setAttribute("href", "javascript:void 0");
+ document.head.appendChild(baseElement);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom() {
+ setTimeout(function(){
+ var o=document.getElementById('b');
+ document.getElementById('a').appendChild(o.parentNode.removeChild(o));
+ },0);
+ var o=document.getElementById('c');
+ var p=document.getElementById('b');
+ p.id=[o.id, o.id=p.id][0];
+ o=document.getElementById('b');
+ o.setAttribute('sandbox', 'disc');
+ window.location.reload(true);
+}
+</script>
+</head>
+<body onload="boom();">
+<header id='a'></header>
+<output id='b'></output>
+<iframe id='c' sandbox='allow-same-origin' src='http://a'></iframe>
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+ <head>
+ <script>
+ function boom() {
+ o1 = document.createElement("script");
+ o2 = document.implementation.createDocument('', '', null);
+ o3 = document.createElement("iframe");
+ document.documentElement.appendChild(o3);
+ o4 = o3.contentWindow;
+ o5 = document.createTextNode('o2.adoptNode(o3); try { o4.location = "" } catch(e) {}');
+ o1.appendChild(o5);
+ document.documentElement.appendChild(o1);
+ document.documentElement.classList.remove("reftest-wait");
+ }
+ </script>
+ </head>
+ <body onload="boom();"></body>
+</html>
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 @@
+<script>
+window.onload = () => {
+ a.addEventListener("DOMSubtreeModified", () => {
+ document.body.appendChild(b)
+ document.body.removeChild(b)
+ window[1]
+ })
+ a.type = ""
+}
+</script>
+<embed id="a">
+<iframe id="b"></iframe>
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 @@
+<script>
+window.onload = () => {
+ b.addEventListener('DOMSubtreeModified', () => {
+ var o = document.getElementById('a')
+ var a = o.attributes
+ for (let j = 0; j < a.length; j++) {
+ o.setAttribute(a[j].name, 'i')
+ o.parentNode.appendChild(o)
+ }
+ })
+ b.setAttribute('a', b)
+}
+</script>
+<iframe id='a' sandbox='' allowfullscreen=''></iframe>
+<dfn id='b'>
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 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ let o = document.getElementById('a')
+ o.setAttribute('id', '')
+ o.setAttribute('sandbox', '')
+})
+</script>
+<iframe id='a' sandbox='s' src='http://%CF'></iframe>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="UTF-8">
+<script>
+ function go() {
+ let win = window.open("1667491_1.html");
+ win.finish = function() {
+ document.documentElement.removeAttribute("class");
+ };
+ }
+</script>
+</head>
+<body onload="go()">
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <script>
+ function go() {
+ document.body.appendChild(a)
+ window.frames[0].onbeforeunload = document.createElement("body").onload;
+ window.requestIdleCallback(() => {
+ window.close();
+ finish();
+ });
+ }
+ </script>
+</head>
+<body onload="go()">
+<iframe id="a"></iframe>
+<iframe></iframe>
+</body>
+</html>
+
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 @@
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("frameset").removeChild(document.getElementById("frame"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<frameset id="frameset" onload="setTimeout(boom, 100)">
+ <frame id="frame" src="data:text/html,<body onUnload=&quot;location = 'http://www.mozilla.org/'&quot;>This frame's onunload tries to load another page.">
+</frameset>
+
+</html>
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 @@
+<html><head><title>Infinite Loop</title></head>
+<body onLoad="initNav(); initNav();">
+
+<script language="JavaScript">
+
+function initNav() {
+ ++parent.i;
+ if (parent.i < 10)
+ window.location.href=window.location.href;
+}
+
+</script>
+
+</body></html>
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 @@
+<html>
+<head><title>Infinite Loop</title><script>var i=0;</script></head>
+<body>
+<iframe src="40929-1-inner.html"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onpagehide="document.getElementById('a').focus();"><div id="a"></div></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onpagehide="document.body.removeChild(document.getElementById('s'));">
+<span id="s" contenteditable="true"></span>
+</body>
+</html>
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 @@
+<html>
+<head>
+<title>Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Cscript%3E%0Awindow.addEventListener%28%27DOMNodeInserted%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3Cframeset%20contenteditable%3D%22true%22%3E"></iframe>
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+<head>
+<title>testcase2 Bug 432114 ďż˝ Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<script>
+ window.addEventListener("DOMNodeRemoved", function() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ });
+ var iframe = document.getElementById("content");
+ iframe.onload=function() {
+ dump("iframe onload\n");
+ console.log("iframe onload");
+ };
+</script>
+<iframe id="content" src="file_432114-2.xhtml" style="width:1000px;height: 200px;"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-1-inner.html#foo"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript" id="foo+++">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-2-inner.html#foo"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+
+<body onload="document.removeChild(document.documentElement)">
+
+<!-- The order of the two iframes matters! -->
+
+<iframe src='data:text/html,<body onload="s = parent.document.getElementById(&apos;s&apos;).contentWindow;" onunload="s.location = s.location;">'></iframe>
+
+<iframe id="s"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="test();">
+<script>
+ function test() {
+ // Test that calling pushState() with a state object which calls
+ // history.back() doesn't crash. We need to make sure that there's at least
+ // one entry in the history before we do anything else.
+ history.pushState(null, "");
+
+ x = {};
+ x.toJSON = { history.back(); return "{a:1}"; };
+ history.pushState(x, "");
+ }
+</script>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+
+<body onunload="document.getElementById('tbody').appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'))">
+ <iframe/>
+ <tbody contenteditable="true" id="tbody">xy</tbody>
+</body>
+
+</html>
diff --git a/docshell/base/crashtests/614499-1.html b/docshell/base/crashtests/614499-1.html
new file mode 100644
index 0000000000..7053a3f52f
--- /dev/null
+++ b/docshell/base/crashtests/614499-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+
+ for (var i = 0; i < 50; ++i) {
+ f.contentWindow.history.pushState({}, "");
+ }
+
+ document.body.removeChild(f);
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html> \ 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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+var f1, f2;
+
+function b1()
+{
+ f1 = document.getElementById("f1");
+ f2 = document.getElementById("f2");
+ f1.contentWindow.document.write("11");
+ f1.contentWindow.history.back();
+ setTimeout(b2, 0);
+}
+
+function b2()
+{
+ f2.contentWindow.history.forward();
+ f2.contentWindow.location.reload();
+ f1.remove();
+}
+
+</script>
+
+
+</script>
+</head>
+
+<body onload="setTimeout(b1, 0);">
+
+<iframe id="f1" src="data:text/html,1"></iframe>
+<iframe id="f2" src="data:text/html,2"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function f()
+{
+ function spin() {
+ for (var i = 0; i < 8; ++i) {
+ var x = new XMLHttpRequest();
+ x.open('GET', 'data:text/html,' + i, false);
+ x.send();
+ }
+ }
+
+ window.addEventListener("popstate", spin);
+ window.close();
+ window.location = "#c";
+ document.documentElement.removeAttribute("class");
+}
+
+function start()
+{
+ var win = window.open("javascript:'<html><body>dummy</body></html>';", null, "width=300,height=300");
+ win.onload = f;
+}
+
+</script>
+</head>
+<body onload="start();"></body>
+</html>
diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list
new file mode 100644
index 0000000000..3912fe5081
--- /dev/null
+++ b/docshell/base/crashtests/crashtests.list
@@ -0,0 +1,21 @@
+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
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 @@
+<html xmlns='http://www.w3.org/1999/xhtml'><frameset contenteditable='true'/><script>function doExecCommand(){dump("doExecCommand\n");document.execCommand('formatBlock', false, 'p');}setTimeout(doExecCommand,100); window.addEventListener('DOMNodeRemoved', function() {window.frameElement.parentNode.removeChild(window.frameElement);}, true);</script></html>
diff --git a/docshell/base/moz.build b/docshell/base/moz.build
new file mode 100644
index 0000000000..5b84c5ec9e
--- /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",
+ "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",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
+
+EXTRA_JS_MODULES += ["URIFixup.jsm"]
diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp
new file mode 100644
index 0000000000..10ac77b519
--- /dev/null
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -0,0 +1,266 @@
+/* -*- 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"
+
+#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: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.
+ */
+static const RedirEntry kRedirMap[] = {
+ {"about", "chrome://global/content/aboutAbout.html", 0},
+ {"addons", "chrome://mozapps/content/extensions/extensions.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"buildconfig", "chrome://global/content/buildconfig.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
+ {"checkerboard", "chrome://global/content/aboutCheckerboard.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT},
+#ifndef MOZ_BUILD_APP_IS_BROWSER
+ {"config", "chrome://global/content/config.xhtml", 0},
+#endif
+#ifdef MOZ_CRASHREPORTER
+ {"crashes", "chrome://global/content/crashes.html", 0},
+#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},
+ {"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},
+ {"mozilla", "chrome://global/content/mozilla.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
+ {"neterror", "chrome://global/content/netError.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},
+ {"plugins", "chrome://global/content/plugins.html",
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD},
+ {"processes", "chrome://global/content/aboutProcesses.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ // about:serviceworkers always wants to load in the parent process because
+ // when dom.serviceWorkers.parent_intercept is set to true (the new default)
+ // then 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},
+#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},
+#ifdef MOZ_GLEAN
+ {"glean", "chrome://global/content/aboutGlean.html",
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::ALLOW_SCRIPT},
+#endif
+ {"telemetry", "chrome://global/content/aboutTelemetry.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"printpreview", "about:blank",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD},
+ {"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}};
+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<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent")) {
+ bool isExternal;
+ aLoadInfo->GetLoadTriggeredFromExternal(&isExternal);
+ if (isExternal) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIChannel> 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<nsIChannel> tempChannel;
+ nsCOMPtr<nsIURI> 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ RefPtr<nsAboutRedirector> 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..08448787e9
--- /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(nsISupports* aOuter, 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..d2f339c9ec
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -0,0 +1,307 @@
+/* -*- 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(nsISupports)
+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<BrowsingContext> 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<BrowsingContext>
+MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) {
+ RefPtr<BrowsingContext> 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;
+}
+
+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);
+
+ *aAbortProcess = false;
+
+ // determine if the channel has just been retargeted to us...
+ nsLoadFlags loadFlags = 0;
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+
+ if (aOpenedChannel) {
+ aOpenedChannel->GetLoadFlags(&loadFlags);
+
+ // block top-level data URI navigations if triggered by the web
+ if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+ aOpenedChannel)) {
+ // logging to console happens within AllowTopLevelNavigationToDataURI
+ aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
+ *aAbortProcess = true;
+ // close the window since the navigation to a data URI was blocked
+ if (mDocShell && mDocShell->GetBrowsingContext()) {
+ RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
+ new MaybeCloseWindowHelper(mDocShell->GetBrowsingContext());
+ maybeCloseWindowHelper->SetShouldCloseWindow(true);
+ Unused << maybeCloseWindowHelper->MaybeCloseWindow();
+ }
+ return NS_OK;
+ }
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ // XXX: Why does this not stop the content too?
+ mDocShell->Stop(nsIWebNavigation::STOP_NETWORK);
+
+ mDocShell->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<nsIChannel> baseChannel;
+ if (nsCOMPtr<nsIMultiPartChannel> mpchan = do_QueryInterface(aRequest)) {
+ mpchan->GetBaseChannel(getter_AddRefs(baseChannel));
+ }
+
+ bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest &&
+ aContentType.EqualsLiteral("image/jpeg");
+
+ if (mExistingJPEGStreamListener && reuseCV) {
+ RefPtr<nsIStreamListener> copy(mExistingJPEGStreamListener);
+ copy.forget(aContentHandler);
+ rv = NS_OK;
+ } else {
+ rv =
+ mDocShell->CreateContentViewer(aContentType, aRequest, aContentHandler);
+ if (NS_SUCCEEDED(rv) && reuseCV) {
+ mExistingJPEGStreamListener = *aContentHandler;
+ } else {
+ mExistingJPEGStreamListener = nullptr;
+ }
+ mExistingJPEGRequest = baseChannel;
+ }
+
+ if (rv == NS_ERROR_REMOTE_XUL || 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<nsIStreamListener> forget = dont_AddRef(*aContentHandler);
+ *aContentHandler = nullptr;
+ return rv;
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ nsCOMPtr<nsPIDOMWindowOuter> 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<nsIURIContentListener> 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), mDocShell);
+ *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<nsDocLoader> 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<nsIURIContentListener> 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..a9e7da728f
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.h
@@ -0,0 +1,99 @@
+/* -*- 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:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ 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<mozilla::dom::BrowsingContext> ChooseNewBrowsingContext(
+ mozilla::dom::BrowsingContext* aBC);
+
+ /**
+ * The dom window associated to handle content.
+ */
+ RefPtr<mozilla::dom::BrowsingContext> 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<mozilla::dom::BrowsingContext> mBCToClose;
+ nsCOMPtr<nsITimer> 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<nsIStreamListener> mExistingJPEGStreamListener;
+ nsCOMPtr<nsIChannel> 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..207a757eef
--- /dev/null
+++ b/docshell/base/nsDocShell.cpp
@@ -0,0 +1,13439 @@
+/* -*- 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 <algorithm>
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // 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/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/Services.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/Telemetry.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.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/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/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/ParentChannelWrapper.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "ReferrerInfo.h"
+
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIAppShell.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 "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 "nsIReflowObserver.h"
+#include "nsIScriptChannel.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollObserver.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 "nsIWebBrowserChrome3.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 "nsISearchService.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 "BRNameMatchingPolicy.h"
+#include "GeckoProfiler.h"
+#include "mozilla/NullPrincipal.h"
+#include "Navigator.h"
+#include "prenv.h"
+#include "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
+
+// Hint for native dispatch of events on how long to delay after
+// all documents have loaded in milliseconds before favoring normal
+// native event dispatch priorites over performance
+// Can be overridden with docshell.event_starvation_delay_hint pref.
+#define NS_EVENT_STARVATION_DELAY_HINT 2000
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+// Number of documents currently loading
+static int32_t gNumberOfDocumentsLoading = 0;
+
+// Global count of docshells with the private attribute set
+static uint32_t gNumberOfPrivateDocShells = 0;
+
+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");
+
+const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties";
+const char kAppstringsBundleURL[] =
+ "chrome://global/locale/appstrings.properties";
+
+static void FavorPerformanceHint(bool aPerfOverStarvation) {
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+ if (appShell) {
+ appShell->FavorPerformanceHint(
+ aPerfOverStarvation,
+ Preferences::GetUint("docshell.event_starvation_delay_hint",
+ NS_EVENT_STARVATION_DELAY_HINT));
+ }
+}
+
+static void IncreasePrivateDocShellCount() {
+ gNumberOfPrivateDocShells++;
+ if (gNumberOfPrivateDocShells > 1 || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ cc->SendPrivateDocShellsExist(true);
+}
+
+static void DecreasePrivateDocShellCount() {
+ MOZ_ASSERT(gNumberOfPrivateDocShells > 0);
+ gNumberOfPrivateDocShells--;
+ if (!gNumberOfPrivateDocShells) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ cc->SendPrivateDocShellsExist(false);
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
+ if (obsvc) {
+ obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
+ }
+ }
+}
+
+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(),
+ mContentWindowID(aContentWindowID),
+ mBrowsingContext(aBrowsingContext),
+ mForcedCharset(nullptr),
+ mParentCharset(nullptr),
+ mTreeOwner(nullptr),
+ mScrollbarPref(ScrollbarPreference::Auto),
+ mCharsetReloadState(eCharsetReloadInit),
+ mParentCharsetSource(0),
+ mFrameMargins(-1, -1),
+ mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome),
+ mPreviousEntryIndex(-1),
+ mLoadedEntryIndex(-1),
+ mChildOffset(0),
+ mBusyFlags(BUSY_FLAGS_NONE),
+ mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
+ mLoadType(0),
+ mFailedLoadType(0),
+ mJSRunToCompletionDepth(0),
+ mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE),
+ mCreatingDocument(false),
+#ifdef DEBUG
+ mInEnsureScriptEnv(false),
+#endif
+ mInitialized(false),
+ mAllowSubframes(true),
+ mAllowJavascript(true),
+ mAllowMetaRedirects(true),
+ mAllowImages(true),
+ mAllowMedia(true),
+ mAllowDNSPrefetch(true),
+ mAllowWindowControl(true),
+ mCSSErrorReportingEnabled(false),
+ mAllowAuth(mItemType == typeContent),
+ mAllowKeywordFixup(false),
+ mIsOffScreenBrowser(false),
+ mDisableMetaRefreshWhenInactive(false),
+ mIsAppTab(false),
+ mDeviceSizeIsPageSize(false),
+ mWindowDraggingAllowed(false),
+ mInFrameSwap(false),
+ mCanExecuteScripts(false),
+ mFiredUnloadEvent(false),
+ mEODForCurrentDocument(false),
+ mURIResultedInDocument(false),
+ mIsBeingDestroyed(false),
+ mIsExecutingOnLoadHandler(false),
+ mSavingOldViewer(false),
+ mAffectPrivateSessionLifetime(true),
+ mInvisible(false),
+ mHasLoadedNonBlankURI(false),
+ mBlankTiming(false),
+ mTitleValidForCurrentURI(false),
+ mWillChangeProcess(false),
+ mIsNavigating(false),
+ mSuspendMediaWhenInactive(false),
+ mForcedAutodetection(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);
+
+ mDeviceSizeIsPageSize = Preferences::GetBool(
+ "docshell.device_size_is_page_size", mDeviceSizeIsPageSize);
+
+ if (nsCOMPtr<nsIObserverService> 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> nsDocShell::Create(
+ BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) {
+ MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");
+
+ nsresult rv;
+ RefPtr<nsDocShell> 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);
+
+ // If parent intercept is not enabled then we must forward to
+ // the network controller from docshell. We also 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 (!ServiceWorkerParentInterceptEnabled() || 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<nsIInterfaceRequestor> 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<WindowContext> 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<Element> 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<nsIDocShell> 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<nsIDocShellTreeItem> 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_ENTRY(nsIDeprecationWarner)
+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<nsICommandManager*>(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<Document> doc = mContentViewer->GetDocument();
+ doc.forget(aSink);
+ return *aSink ? NS_OK : NS_NOINTERFACE;
+ } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) {
+ *aSink = nullptr;
+
+ // Return application cache associated with this docshell, if any
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ GetContentViewer(getter_AddRefs(contentViewer));
+ if (!contentViewer) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ RefPtr<Document> doc = contentViewer->GetDocument();
+ NS_ASSERTION(doc, "Should have a document.");
+ if (!doc) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+#if defined(DEBUG)
+ MOZ_LOG(
+ gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]: returning app cache container %p", this, doc.get()));
+#endif
+ return doc->QueryInterface(aIID, aSink);
+ } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) &&
+ NS_SUCCEEDED(EnsureScriptEnvironment())) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> 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<nsIDocShellTreeOwner> 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<nsIBrowserChild> browserChild = GetBrowserChild();
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SetCancelContentJSEpoch(aEpoch);
+ return NS_OK;
+}
+
+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!");
+ 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;
+ }
+
+ bool oldIsNavigating = mIsNavigating;
+ auto cleanupIsNavigating =
+ MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; });
+ if (aSetNavigating) {
+ mIsNavigating = true;
+ }
+
+ PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden;
+ if (aLoadState->LoadFlags() & 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->LoadFlags() & LOAD_FLAGS_FORCE_TRR) {
+ defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE;
+ } else if (aLoadState->LoadFlags() & 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()) {
+ return LoadHistoryEntry(aLoadState->SHEntry(), aLoadState->LoadType());
+ }
+
+ // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()?
+ return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(),
+ aLoadState->LoadType());
+ }
+
+ // 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");
+
+ 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;
+}
+
+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<nsIDocShellTreeItem> parentAsItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentAsItem));
+ nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
+
+ if (!parentDS || parentDS == static_cast<nsIDocShell*>(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();
+ if (contentChild) {
+ RefPtr<Document> parentDoc = parentDS->GetDocument();
+ parentDoc->BlockOnload();
+ RefPtr<BrowsingContext> browsingContext = mBrowsingContext;
+ Maybe<uint64_t> currentLoadIdentifier =
+ mBrowsingContext->GetCurrentLoadIdentifier();
+ RefPtr<nsDocShellLoadState> loadState = aLoadState;
+ bool isNavigating = mIsNavigating;
+
+ auto resolve =
+ [currentLoadIdentifier, browsingContext, parentDoc, loadState,
+ isNavigating](Tuple<mozilla::Maybe<LoadingSessionHistoryInfo>,
+ int32_t, int32_t>&& aResult) {
+ if (currentLoadIdentifier ==
+ browsingContext->GetCurrentLoadIdentifier() &&
+ Get<0>(aResult).isSome()) {
+ loadState->SetLoadingSessionHistoryInfo(
+ Get<0>(aResult).value());
+ loadState->SetLoadIsFromSessionHistory(
+ Get<1>(aResult), Get<2>(aResult), false);
+ }
+ RefPtr<nsDocShell> docShell =
+ static_cast<nsDocShell*>(browsingContext->GetDocShell());
+ if (docShell) {
+ // We got the results back from the parent process, call
+ // LoadURI again with the possibly updated data.
+ docShell->LoadURI(loadState, isNavigating, true);
+ }
+ parentDoc->UnblockOnload(false);
+ };
+ auto reject = [parentDoc](mozilla::ipc::ResponseRejectReason) {
+ parentDoc->UnblockOnload(false);
+ };
+ contentChild->SendGetLoadingSessionHistoryInfoFromParent(
+ mBrowsingContext, std::move(resolve), std::move(reject));
+ return true;
+ }
+ } else {
+ Maybe<LoadingSessionHistoryInfo> info;
+ int32_t requestedIndex = -1;
+ int32_t sessionHistoryLength = 0;
+ mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent(
+ info, &requestedIndex, &sessionHistoryLength);
+ if (info.isSome()) {
+ aLoadState->SetLoadingSessionHistoryInfo(info.value());
+ aLoadState->SetLoadIsFromSessionHistory(
+ requestedIndex, sessionHistoryLength, false);
+ }
+ }
+ }
+ } else {
+ // Get the ShEntry for the child from the parent
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> shEntry;
+ currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ mChildOffset, 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<nsISHEntry> 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 ||
+ parentLoadType == LOAD_NORMAL_EXTERNAL)) {
+ // 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<nsIContentViewer> contentViewer(mContentViewer);
+ mFiredUnloadEvent = true;
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventStart();
+ }
+
+ contentViewer->PageHide(aIsUnload);
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventEnd();
+ }
+
+ AutoTArray<nsCOMPtr<nsIDocShell>, 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<nsDocShell> child = static_cast<nsDocShell*>(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<ChildSHistory> 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();
+ }
+}
+
+nsresult nsDocShell::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ nsCOMPtr<nsPIDOMWindowOuter> 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<nsPIDOMWindowOuter> outerWindow = GetWindow();
+ if (outerWindow) {
+ outerWindow->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
+ }
+ 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;
+}
+
+//
+// Bug 13871: Prevent frameset spoofing
+//
+// This routine answers: 'Is origin's document from same domain as
+// target's document?'
+//
+// file: uris are considered the same domain for the purpose of
+// frame navigation regardless of script accessibility (bug 420425)
+//
+/* static */
+bool nsDocShell::ValidateOrigin(BrowsingContext* aOrigin,
+ BrowsingContext* aTarget) {
+ nsIDocShell* originDocShell = aOrigin->GetDocShell();
+ MOZ_ASSERT(originDocShell, "originDocShell must not be null");
+ Document* originDocument = originDocShell->GetDocument();
+ NS_ENSURE_TRUE(originDocument, false);
+
+ nsIDocShell* targetDocShell = aTarget->GetDocShell();
+ MOZ_ASSERT(targetDocShell, "targetDocShell must not be null");
+ Document* targetDocument = targetDocShell->GetDocument();
+ NS_ENSURE_TRUE(targetDocument, false);
+
+ bool equal;
+ nsresult rv = originDocument->NodePrincipal()->Equals(
+ targetDocument->NodePrincipal(), &equal);
+ if (NS_SUCCEEDED(rv) && equal) {
+ return true;
+ }
+ // Not strictly equal, special case if both are file: uris
+ nsCOMPtr<nsIURI> originURI;
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> innerOriginURI;
+ nsCOMPtr<nsIURI> innerTargetURI;
+
+ // Casting to BasePrincipal, as we can't get InnerMost URI otherwise
+ auto* originDocumentBasePrincipal =
+ BasePrincipal::Cast(originDocument->NodePrincipal());
+
+ rv = originDocumentBasePrincipal->GetURI(getter_AddRefs(originURI));
+ if (NS_SUCCEEDED(rv) && originURI) {
+ innerOriginURI = NS_GetInnermostURI(originURI);
+ }
+
+ auto* targetDocumentBasePrincipal =
+ BasePrincipal::Cast(targetDocument->NodePrincipal());
+
+ rv = targetDocumentBasePrincipal->GetURI(getter_AddRefs(targetURI));
+ if (NS_SUCCEEDED(rv) && targetURI) {
+ innerTargetURI = NS_GetInnermostURI(targetURI);
+ }
+
+ return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) &&
+ SchemeIsFile(innerTargetURI);
+}
+
+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<EventTarget> handler = mChromeEventHandler;
+ handler.forget(aChromeEventHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentURI(nsIURI* aURI) {
+ // Note that securityUI will set STATE_IS_INSECURE, even if
+ // the scheme of |aURI| is "https".
+ SetCurrentURI(aURI, nullptr, true, 0);
+ return NS_OK;
+}
+
+bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange,
+ 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;
+ }
+
+ bool isRoot = mBrowsingContext->IsTop();
+ bool isSubFrame = false; // Is this a subframe navigation?
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (mLoadingEntry) {
+ isSubFrame = mLoadingEntry->mInfo.IsSubFrame();
+ } else {
+ isSubFrame = !mBrowsingContext->IsTop() && mActiveEntry;
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p SetCurrentURI, isSubFrame=%d", this, isSubFrame));
+ } else {
+ if (mLSHE) {
+ isSubFrame = mLSHE->GetIsSubFrame();
+ }
+ }
+
+ if (!isSubFrame && !isRoot) {
+ /*
+ * We don't want to send OnLocationChange notifications when
+ * a subframe is being loaded for the first time, while
+ * visiting a frameset page
+ */
+ return false;
+ }
+
+ 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::GatherCharsetMenuTelemetry() {
+ nsCOMPtr<nsIContentViewer> viewer;
+ GetContentViewer(getter_AddRefs(viewer));
+ if (!viewer) {
+ return NS_OK;
+ }
+
+ Document* doc = viewer->GetDocument();
+ if (!doc || doc->WillIgnoreCharsetOverride()) {
+ return NS_OK;
+ }
+
+ if (mForcedAutodetection) {
+ LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_AUTOMATIC"));
+ Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_AUTOMATIC,
+ true);
+ } else {
+ LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_MANUAL"));
+ Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_MANUAL,
+ true);
+ }
+
+ nsIURI* url = doc->GetOriginalURI();
+ bool isFileURL = url && SchemeIsFile(url);
+
+ int32_t charsetSource = doc->GetDocumentCharacterSetSource();
+ auto encoding = doc->GetDocumentCharacterSet();
+ switch (charsetSource) {
+ case kCharsetFromInitialUserForcedAutoDetection:
+ case kCharsetFromFinalUserForcedAutoDetection:
+ LOGCHARSETMENU(("AutoOverridden"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::AutoOverridden);
+ break;
+ case kCharsetFromUserForced:
+ case kCharsetFromUserForcedJapaneseAutoDetection:
+ LOGCHARSETMENU(("ManuallyOverridden"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ManuallyOverridden);
+ break;
+ case kCharsetFromTopLevelDomain:
+ if (encoding == WINDOWS_1252_ENCODING) {
+ LOGCHARSETMENU(("UnlabeledInLk"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledInLk);
+ } else {
+ LOGCHARSETMENU(("UnlabeledJp"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp);
+ }
+ break;
+ case kCharsetFromFinalJapaneseAutoDetection:
+ LOGCHARSETMENU(("UnlabeledJp"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp);
+ break;
+ case kCharsetFromInitialAutoDetectionASCII:
+ // Deliberately no final version
+ LOGCHARSETMENU(("UnlabeledAscii"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledAscii);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ LOGCHARSETMENU(("UnlabeledNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ LOGCHARSETMENU(("UnlabeledNonUtf8TLD"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8TLD);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8:
+ LOGCHARSETMENU(("UnlabeledUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledUtf8);
+ break;
+ case kCharsetFromChannel:
+ if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("ChannelUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelUtf8);
+ } else {
+ LOGCHARSETMENU(("ChannelNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelNonUtf8);
+ }
+ break;
+ case kCharsetFromMetaPrescan:
+ case kCharsetFromMetaTag:
+ if (isFileURL) {
+ LOGCHARSETMENU(("LocalLabeled"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalLabeled);
+ } else if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("MetaUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaUtf8);
+ } else {
+ LOGCHARSETMENU(("MetaNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaNonUtf8);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionFile:
+ if (isFileURL) {
+ LOGCHARSETMENU(("LocalUnlabeled"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalUnlabeled);
+ } else {
+ LOGCHARSETMENU(("Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug);
+ }
+ break;
+ default:
+ LOGCHARSETMENU(("Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug);
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCharset(const nsACString& aCharset) {
+ mForcedAutodetection = false;
+ if (aCharset.IsEmpty()) {
+ mForcedCharset = nullptr;
+ return NS_OK;
+ }
+ if (aCharset.EqualsLiteral("_autodetect_all")) {
+ mForcedCharset = WINDOWS_1252_ENCODING;
+ mForcedAutodetection = true;
+ return NS_OK;
+ }
+ const Encoding* encoding = Encoding::ForLabel(aCharset);
+ if (!encoding) {
+ // Reject unknown labels
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (!encoding->IsAsciiCompatible() && encoding != ISO_2022_JP_ENCODING) {
+ // Reject XSS hazards
+ return NS_ERROR_INVALID_ARG;
+ }
+ mForcedCharset = encoding;
+ 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<Document> doc(GetDocument());
+ RefPtr<Promise> 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<Document::GetContentBlockingEventsPromise> 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::GetAllowJavascript(bool* aAllowJavascript) {
+ NS_ENSURE_ARG_POINTER(aAllowJavascript);
+
+ *aAllowJavascript = mAllowJavascript;
+ return NS_OK;
+}
+
+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::SetAllowJavascript(bool aAllowJavascript) {
+ mAllowJavascript = aAllowJavascript;
+ RecomputeCanExecuteScripts();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+ NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
+ return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing);
+}
+
+void nsDocShell::NotifyPrivateBrowsingChanged() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ if (mAffectPrivateSessionLifetime) {
+ if (UsePrivateBrowsing()) {
+ IncreasePrivateDocShellCount();
+ } else {
+ DecreasePrivateDocShellCount();
+ }
+ }
+
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIPrivacyTransitionObserver> 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::SetAffectPrivateSessionLifetime(bool aAffectLifetime) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ bool change = aAffectLifetime != mAffectPrivateSessionLifetime;
+ if (change && UsePrivateBrowsing()) {
+ if (aAffectLifetime) {
+ IncreasePrivateDocShellCount();
+ } else {
+ DecreasePrivateDocShellCount();
+ }
+ }
+ mAffectPrivateSessionLifetime = aAffectLifetime;
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->SetAffectPrivateSessionLifetime(aAffectLifetime);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime) {
+ *aAffectLifetime = mAffectPrivateSessionLifetime;
+ return NS_OK;
+}
+
+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<nsWeakPtr>::ForwardIterator iter(mReflowObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIReflowObserver> 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::GetCharsetAutodetected(bool* aCharsetAutodetected) {
+ *aCharsetAutodetected = false;
+ if (!mContentViewer) {
+ return NS_OK;
+ }
+ Document* doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return NS_OK;
+ }
+ int32_t source = doc->GetDocumentCharacterSetSource();
+
+ if ((source >= kCharsetFromInitialAutoDetectionASCII &&
+ source <= kCharsetFromFinalAutoDetectionFile) ||
+ source == kCharsetFromUserForcedJapaneseAutoDetection ||
+ source == kCharsetFromPendingUserForcedAutoDetection ||
+ source == kCharsetFromInitialUserForcedAutoDetection ||
+ source == kCharsetFromFinalUserForcedAutoDetection) {
+ *aCharsetAutodetected = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType,
+ DocShellEnumeratorDirection aDirection,
+ nsTArray<RefPtr<nsIDocShell>>& 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<nsIWebBrowserChromeFocus> 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<nsILoadURIDelegate> delegate = GetLoadURIDelegate();
+ delegate.forget(aLoadURIDelegate);
+ return NS_OK;
+}
+
+already_AddRefed<nsILoadURIDelegate> nsDocShell::GetLoadURIDelegate() {
+ if (nsCOMPtr<nsILoadURIDelegate> 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<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->HistoryPurged(aNumEntries);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::TriggerParentCheckDocShellIsEmpty() {
+ if (RefPtr<nsDocShell> 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<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(aIndex);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) {
+ bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
+ if (currentValue == aValue) {
+ return NS_OK;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines) {
+ return NS_OK;
+ }
+
+ if (aValue) {
+ MOZ_ASSERT(!timelines->HasConsumer(this));
+ timelines->AddConsumer(this);
+ MOZ_ASSERT(timelines->HasConsumer(this));
+ UseEntryScriptProfiling();
+ } else {
+ MOZ_ASSERT(timelines->HasConsumer(this));
+ timelines->RemoveConsumer(this);
+ MOZ_ASSERT(!timelines->HasConsumer(this));
+ UnuseEntryScriptProfiling();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) {
+ *aValue = !!mObserved;
+ return NS_OK;
+}
+
+nsresult nsDocShell::PopProfileTimelineMarkers(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOut) {
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines) {
+ return NS_OK;
+ }
+
+ nsTArray<dom::ProfileTimelineMarker> store;
+ SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store);
+
+ timelines->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<nsDocShell> 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<nsDocShell> 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<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStarted();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+void nsDocShell::NotifyAsyncPanZoomStopped() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStopped();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::NotifyScrollObservers() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> 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<nsGlobalWindowInner> win =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+ if (win) {
+ Navigator* navigator = win->Navigator();
+ if (navigator) {
+ navigator->ClearPlatformCache();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::ClearCachedUserAgent() {
+ RefPtr<nsGlobalWindowInner> 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> 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> nsDocShell::GetInProcessParentDocshell() {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
+ return docshell.forget().downcast<nsDocShell>();
+}
+
+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;
+ }
+
+ nsIPrincipal* principal =
+ aPrincipal ? aPrincipal : GetInheritedPrincipal(false);
+
+ // 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<nsPIDOMWindowOuter> 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<nsIDocShell> parent = GetInProcessParentDocshell();
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+ if (!parentInner) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> 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<ServiceWorkerDescriptor> controller(parentInner->GetController());
+ if (controller.isNothing() ||
+ !ServiceWorkerAllowedToControlWindow(principal, uri)) {
+ return;
+ }
+
+ mInitialClientSource->InheritController(controller.ref());
+}
+
+Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const {
+ if (mInitialClientSource) {
+ Maybe<ClientInfo> 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<ClientInfo>();
+ }
+
+ return innerWindow->GetClientInfo();
+}
+
+void nsDocShell::RecomputeCanExecuteScripts() {
+ bool old = mCanExecuteScripts;
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+
+ // If we have no tree owner, that means that we've been detached from the
+ // docshell tree (this is distinct from having no parent docshell, which
+ // is the case for root docshells). It would be nice to simply disallow
+ // script in detached docshells, but bug 986542 demonstrates that this
+ // behavior breaks at least one website.
+ //
+ // So instead, we use our previous value, unless mAllowJavascript has been
+ // explicitly set to false.
+ if (!mTreeOwner) {
+ mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript;
+ // If scripting has been explicitly disabled on our docshell, we're done.
+ } else if (!mAllowJavascript) {
+ mCanExecuteScripts = false;
+ // If we have a parent, inherit.
+ } else if (parent) {
+ mCanExecuteScripts = parent->mCanExecuteScripts;
+ // Otherwise, we're the root of the tree, and we haven't explicitly disabled
+ // script. Allow.
+ } else {
+ mCanExecuteScripts = true;
+ }
+
+ // Inform our active DOM window.
+ //
+ // This will pass the outer, which will be in the scope of the active inner.
+ if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) {
+ xpc::Scriptability& scriptability =
+ xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject());
+ scriptability.SetDocShellAllowsScript(mCanExecuteScripts);
+ }
+
+ // If our value has changed, our children might be affected. Recompute their
+ // value as well.
+ if (old != mCanExecuteScripts) {
+ for (auto* child : mChildList.ForwardRange()) {
+ static_cast<nsDocShell*>(child)->RecomputeCanExecuteScripts();
+ }
+ }
+}
+
+nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) {
+ bool wasFrame = IsFrame();
+
+ nsresult rv = nsDocLoader::SetDocLoaderParent(aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup);
+ if (wasFrame != IsFrame() && 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<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));
+
+ if (parentAsDocShell) {
+ if (mAllowJavascript &&
+ NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) {
+ SetAllowJavascript(value);
+ }
+ 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);
+ SetAffectPrivateSessionLifetime(
+ parentAsDocShell->GetAffectPrivateSessionLifetime());
+
+ // 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<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
+ if (parentURIListener) {
+ mContentListener->SetParentContentListener(parentURIListener);
+ }
+
+ // Our parent has changed. Recompute scriptability.
+ RecomputeCanExecuteScripts();
+
+ // Inform windows when they're being removed from their parent.
+ if (!aParent) {
+ MaybeClearStorageAccessFlag();
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::MaybeClearStorageAccessFlag() {
+ if (mScriptGlobal) {
+ // Tell our window that the parent has now changed.
+ mScriptGlobal->ParentWindowChanged();
+
+ // Tell all of our children about the change recursively as well.
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ static_cast<nsDocShell*>(child.get())->MaybeClearStorageAccessFlag();
+ }
+ }
+ }
+}
+
+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<nsIDocShellTreeItem> parent =
+ do_QueryInterface(GetAsSupports(mParent));
+ if (!parent) {
+ return NS_OK;
+ }
+
+ if (parent->ItemType() == mItemType) {
+ nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent);
+ parentDS.forget(aParent);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) {
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+
+ RefPtr<nsDocShell> root = this;
+ RefPtr<nsDocShell> 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<nsIDocShellTreeItem*>(this);
+
+ nsCOMPtr<nsIDocShellTreeItem> 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 (!IsFrame()) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+
+ if (webProgress) {
+ nsCOMPtr<nsIWebProgressListener> oldListener =
+ do_QueryInterface(mTreeOwner);
+ nsCOMPtr<nsIWebProgressListener> 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<nsIDocShellTreeItem> 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<nsIBrowserChild> newBrowserChild = do_GetInterface(mTreeOwner);
+ MOZ_ASSERT(newBrowserChild,
+ "No BrowserChild actor for tree owner in Content!");
+
+ if (mBrowserChild) {
+ nsCOMPtr<nsIBrowserChild> oldBrowserChild =
+ do_QueryReferent(mBrowserChild);
+ MOZ_RELEASE_ASSERT(
+ oldBrowserChild == newBrowserChild,
+ "Cannot change BrowserChild during nsDocShell lifetime!");
+ } else {
+ mBrowserChild = do_GetWeakReference(newBrowserChild);
+ }
+ }
+
+ // Our tree owner has changed. Recompute scriptability.
+ //
+ // Note that this is near-redundant with the recomputation in
+ // SetDocLoaderParent(), but not so for the root DocShell, where the call to
+ // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
+ // and we never set another parent. Given that this is neither expensive nor
+ // performance-critical, let's be safe and unconditionally recompute this
+ // state whenever dependent state changes.
+ RecomputeCanExecuteScripts();
+
+ return NS_OK;
+}
+
+void nsDocShell::SetChildOffset(int32_t aChildOffset) {
+ mChildOffset = aChildOffset;
+}
+
+int32_t nsDocShell::GetChildOffset() { return mChildOffset; }
+
+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<nsDocLoader> 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");
+
+ nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(aChild);
+ bool dynamic = nsDocShell::Cast(childDocShell)->GetCreatedDynamically();
+ if (!dynamic) {
+ nsCOMPtr<nsISHEntry> currentSH;
+ bool oshe = false;
+ GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
+ if (currentSH) {
+ currentSH->HasDynamicallyAddedChild(&dynamic);
+ }
+ }
+ childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1);
+
+ /* 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(nsDocShell::Cast(childDocShell)
+ ->mBrowsingContext->GetUseGlobalHistory());
+ }
+
+ if (aChild->ItemType() != mItemType) {
+ return NS_OK;
+ }
+
+ aChild->SetTreeOwner(mTreeOwner);
+
+ nsCOMPtr<nsIDocShell> 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<nsDocLoader> 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<nsDocShell> 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<uint32_t>(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<nsDocShell*>(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<ChildSHistory> 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<ChildSHistory> 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<nsIDocShell> 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<nsGlobalWindowOuter> window = mScriptGlobal;
+ window.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
+ RefPtr<ContentFrameMessageManager> mm;
+ if (RefPtr<BrowserChild> 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;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetDeviceSizeIsPageSize(bool aValue) {
+ if (mDeviceSizeIsPageSize != aValue) {
+ mDeviceSizeIsPageSize = aValue;
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (presContext) {
+ presContext->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::DeviceSizeIsPageSizeChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) {
+ *aValue = mDeviceSizeIsPageSize;
+ return NS_OK;
+}
+
+void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ RefPtr<ChildSHistory> 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<ChildSHistory> 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<ChildSHistory> 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) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(-1, aRequireUserInteraction, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsDocShell::GoForward(bool aRequireUserInteraction) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(1, aRequireUserInteraction, 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) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+
+ ErrorResult rv;
+ rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, rv);
+ return rv.StealNSResult();
+}
+
+nsresult nsDocShell::LoadURI(const nsAString& aURI,
+ const LoadURIOptions& aLoadURIOptions) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState));
+
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+ if (NS_ERROR_MALFORMED_URI == rv) {
+ if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURI).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::LoadURIFromScript(const nsAString& aURI,
+ JS::Handle<JS::Value> 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);
+}
+
+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> element = mBrowsingContext->GetEmbedderElement();
+ if (element) {
+ if (aFireFrameErrorEvent) {
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(element)) {
+ if (RefPtr<nsFrameLoader> 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 = 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<nsIPrompt> prompter;
+ nsCOMPtr<nsIStringBundle> 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<nsString, 3> formatStrs;
+ bool addHostPort = 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<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> 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<nsIURI> 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_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<nsINSSErrorsService> 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<nsISupports> securityInfo;
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ if (aFailedChannel) {
+ aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ }
+ tsi = do_QueryInterface(securityInfo);
+ 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).
+ uint32_t flags =
+ UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+ bool isStsHost = false;
+ bool isPinnedHost = false;
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISiteSecurityService> sss =
+ do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
+ GetOriginAttributes(), nullptr, nullptr,
+ &isStsHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI,
+ flags, GetOriginAttributes(), nullptr, nullptr,
+ &isPinnedHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+ cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
+ GetOriginAttributes(), &isStsHost);
+ cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags,
+ GetOriginAttributes(), &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) {
+ 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<EventTarget> handler = mChromeEventHandler;
+ if (handler) {
+ nsCOMPtr<Element> 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_REMOTE_XUL:
+ error = "remoteXUL";
+ 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:
+ // 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;
+ }
+ }
+
+ // 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");
+ }
+
+ if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) {
+ nsCOMPtr<nsIURI> errorPageURI;
+ rv = loadURIDelegate->HandleLoadError(aURI, aError,
+ NS_ERROR_GET_MODULE(aError),
+ 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(
+ IsFrame() ? "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<nsITextToSubURI> 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) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aFailedChannel) {
+ aFailedChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ 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<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID);
+ int32_t cpsState;
+ if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) &&
+ cpsState == nsICaptivePortalService::LOCKED_PORTAL) {
+ errorPageUrl.AppendLiteral("&captive=true");
+ }
+
+ // netError.xhtml's getDescription only handles the "d" parameter at the
+ // end of the URL, so append it last.
+ errorPageUrl.AppendLiteral("&d=");
+ errorPageUrl.AppendASCII(escapedDescription.get());
+
+ nsCOMPtr<nsIURI> 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<nsDocShellLoadState> 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<LoadingSessionHistoryInfo>(*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<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this));
+ bool forceReload = IsForceReloadType(loadType);
+ if (!XRE_IsParentProcess()) {
+ RefPtr<nsDocShell> docShell(this);
+ nsCOMPtr<nsIContentViewer> cv(mContentViewer);
+
+ bool okToUnload = true;
+ MOZ_TRY(cv->PermitUnload(&okToUnload));
+ if (!okToUnload) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc(GetDocument());
+ RefPtr<BrowsingContext> browsingContext(mBrowsingContext);
+ nsCOMPtr<nsIURI> currentURI(mCurrentURI);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo(mReferrerInfo);
+
+ ContentChild::GetSingleton()->SendNotifyOnHistoryReload(
+ mBrowsingContext, forceReload,
+ [docShell, doc, loadType, browsingContext, currentURI, referrerInfo](
+ Tuple<bool, Maybe<RefPtr<nsDocShellLoadState>>, Maybe<bool>>&&
+ aResult) {
+ bool canReload;
+ Maybe<RefPtr<nsDocShellLoadState>> loadState;
+ Maybe<bool> reloadingActiveEntry;
+
+ 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<RefPtr<nsDocShellLoadState>> loadState;
+ Maybe<bool> 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));
+ ReloadDocument(this, GetDocument(), loadType, mBrowsingContext,
+ mCurrentURI, mReferrerInfo);
+ }
+ }
+ }
+ 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) {
+ return LoadHistoryEntry(mOSHE, loadType);
+ }
+
+ if (mLSHE) { // In case a reload happened before the current load is done
+ return LoadHistoryEntry(mLSHE, loadType);
+ }
+
+ return ReloadDocument(this, GetDocument(), loadType, mBrowsingContext,
+ mCurrentURI, mReferrerInfo);
+}
+
+/* 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<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ bool loadReplace = false;
+
+ nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> 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<nsIChannel> chan = aDocument->GetChannel();
+ if (chan) {
+ uint32_t loadFlags;
+ chan->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
+ if (httpChan) {
+ httpChan->GetOriginalURI(getter_AddRefs(originalURI));
+ }
+
+ nsCOMPtr<nsILoadInfo> 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<nsIURI> currentURI = aCurrentURI;
+
+ // Reload always rewrites result principal URI.
+ Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+
+ RefPtr<WindowContext> context = aBrowsingContext->GetCurrentWindowContext();
+ RefPtr<nsDocShellLoadState> 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->SetLoadFlags(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<nsISHEntry*>(mLSHE));
+ }
+ mActiveEntryIsLoadingFromSessionHistory = false;
+
+ mFailedChannel = nullptr;
+ mFailedURI = nullptr;
+ }
+
+ if (nsIWebNavigation::STOP_CONTENT & aStopFlags) {
+ // Stop the document loading
+ if (mContentViewer) {
+ nsCOMPtr<nsIContentViewer> cv = mContentViewer;
+ cv->Stop();
+ }
+ }
+
+ 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();
+ }
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIWebNavigation> 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<Document> 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<nsIURI> uri = mCurrentURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) {
+ NS_ENSURE_ARG_POINTER(aSessionHistory);
+ RefPtr<ChildSHistory> 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<nsIURI> newURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> 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<nsISHEntry> 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<nsISHEntry> 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<nsIInputStream> nsDocShell::GetPostDataFromCurrentEntry()
+ const {
+ nsCOMPtr<nsIInputStream> 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<uint32_t> 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<nsIObserverService> 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<nsIDocShellTreeItem> 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->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();
+
+ if (UsePrivateBrowsing() && mAffectPrivateSessionLifetime) {
+ DecreasePrivateDocShellCount();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) {
+ if (mParentWidget) {
+ *aScale = mParentWidget->GetDefaultScale().scale;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
+ if (ownerWindow) {
+ return ownerWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mParentWidget) {
+ *aScale = mParentWidget->GetDesktopToDeviceScale().scale;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBaseWindow> 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<nsIBaseWindow> 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<nsIContentViewer> 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<Document> 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::Repaint(bool aForce) {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ RefPtr<nsViewManager> 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() == nsViewVisibility_kHide) {
+ 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<nsDocShell> docShell = this;
+ RefPtr<nsDocShell> 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;
+ bool isDocShellOffScreen = false;
+ docShell->GetIsOffScreenBrowser(&isDocShellOffScreen);
+ if (frame &&
+ !frame->IsVisibleConsideringAncestors(
+ nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) &&
+ !isDocShellOffScreen) {
+ return NS_OK;
+ }
+
+ docShell = parentItem;
+ parentItem = docShell->GetInProcessParentDocshell();
+ }
+
+ nsCOMPtr<nsIBaseWindow> 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.
+ return treeOwnerAsWin->GetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) {
+ mIsOffScreenBrowser = aIsOffScreen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen) {
+ *aIsOffScreen = mIsOffScreenBrowser;
+ return NS_OK;
+}
+
+void nsDocShell::ActivenessMaybeChanged() {
+ bool isActive = mBrowsingContext->IsActive();
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->SetIsActive(isActive);
+ }
+
+ // Tell the window about it
+ if (mScriptGlobal) {
+ mScriptGlobal->SetIsBackground(!isActive);
+ if (RefPtr<Document> 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.
+ uint16_t orientation = mBrowsingContext->GetOrientationLock();
+ ScreenOrientation::UpdateActiveOrientationLock(orientation);
+ }
+
+ doc->PostVisibilityUpdateEvent();
+ }
+ }
+
+ // Tell the nsDOMNavigationTiming about it
+ RefPtr<nsDOMNavigationTiming> 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::SetIsAppTab(bool aIsAppTab) {
+ mIsAppTab = aIsAppTab;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsAppTab(bool* aIsAppTab) {
+ *aIsAppTab = mIsAppTab;
+ return NS_OK;
+}
+
+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::SetMixedContentChannel(nsIChannel* aMixedContentChannel) {
+#ifdef DEBUG
+ // if the channel is non-null
+ if (aMixedContentChannel) {
+ NS_WARNING_ASSERTION(mBrowsingContext->IsTop(),
+ "Setting mMixedContentChannel on a docshell that is "
+ "not the root docshell");
+ }
+#endif
+ mMixedContentChannel = aMixedContentChannel;
+ 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::GetMixedContentChannel(nsIChannel** aMixedContentChannel) {
+ NS_ENSURE_ARG_POINTER(aMixedContentChannel);
+ NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetVisibility(bool aVisibility) {
+ // Show()/Hide() may change mContentViewer.
+ nsCOMPtr<nsIContentViewer> 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::SetFocus() { return NS_OK; }
+
+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<nsIBaseWindow> 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();
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::SetTitleOnHistoryEntry() {
+ if (mOSHE) {
+ mOSHE->SetTitle(mTitle);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetTitle(mTitle);
+ 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> 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->IsRootContentDocument()) {
+ 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::StyleChange,
+ NS_FRAME_IS_DIRTY);
+}
+
+//*****************************************************************************
+// nsDocShell::nsIRefreshURI
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, int32_t aDelay,
+ bool aRepeat, bool aMetaRefresh) {
+ 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<nsITimerCallback> refreshTimer =
+ new nsRefreshTimer(this, aURI, aPrincipal, aDelay, aRepeat, aMetaRefresh);
+
+ 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<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsITimer> 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,
+ int32_t aDelay, bool aMetaRefresh,
+ 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<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (timer == aTimer) {
+ mRefreshURIList->RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ return ForceRefreshURI(aURI, aPrincipal, aDelay, aMetaRefresh);
+}
+
+NS_IMETHODIMP
+nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ int32_t aDelay, bool aMetaRefresh) {
+ NS_ENSURE_ARG(aURI);
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetOriginalURI(mCurrentURI);
+ loadState->SetResultPrincipalURI(aURI);
+ loadState->SetResultPrincipalURIIsSome(true);
+ loadState->SetKeepResultPrincipalURIIfSet(true);
+
+ // Set the triggering pricipal to aPrincipal if available, or current
+ // document's principal otherwise.
+ nsCOMPtr<nsIPrincipal> principal = aPrincipal;
+ RefPtr<Document> 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<nsIReferrerInfo> referrerInfo;
+ if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh &&
+ 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_NORMAL_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<ReferrerInfo*>(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;
+}
+
+nsresult nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI,
+ nsIPrincipal* aPrincipal,
+ uint64_t aInnerWindowID,
+ const nsACString& aHeader) {
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Refresh headers are parsed with the following format in mind
+ // <META HTTP-EQUIV=REFRESH CONTENT="5; URL=http://uri">
+ // By the time we are here, the following is true:
+ // header = "REFRESH"
+ // content = "5; URL=http://uri" // note the URL attribute is
+ // optional, if it is absent, the currently loaded url is used.
+ // Also note that the seconds and URL separator can be either
+ // a ';' or a ','. The ',' separator should be illegal but CNN
+ // is using it.
+ //
+ // We need to handle the following strings, where
+ // - X is a set of digits
+ // - URI is either a relative or absolute URI
+ //
+ // Note that URI should start with "url=" but we allow omission
+ //
+ // "" || ";" || ","
+ // empty string. use the currently loaded URI
+ // and refresh immediately.
+ // "X" || "X;" || "X,"
+ // Refresh the currently loaded URI in X seconds.
+ // "X; URI" || "X, URI"
+ // Refresh using URI as the destination in X seconds.
+ // "URI" || "; URI" || ", URI"
+ // Refresh immediately using URI as the destination.
+ //
+ // Currently, anything immediately following the URI, if
+ // separated by any char in the set "'\"\t\r\n " will be
+ // ignored. So "10; url=go.html ; foo=bar" will work,
+ // and so will "10; url='go.html'; foo=bar". However,
+ // "10; url=go.html; foo=bar" will result in the uri
+ // "go.html;" since ';' and ',' are valid uri characters.
+ //
+ // Note that we need to remove any tokens wrapping the URI.
+ // These tokens currently include spaces, double and single
+ // quotes.
+
+ // when done, seconds is 0 or the given number of seconds
+ // uriAttrib is empty or the URI specified
+ MOZ_ASSERT(aPrincipal);
+
+ nsAutoCString uriAttrib;
+ int32_t seconds = 0;
+ bool specifiesSeconds = false;
+
+ nsACString::const_iterator iter, tokenStart, doneIterating;
+
+ aHeader.BeginReading(iter);
+ aHeader.EndReading(doneIterating);
+
+ // skip leading whitespace
+ while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ tokenStart = iter;
+
+ // skip leading + and -
+ if (iter != doneIterating && (*iter == '-' || *iter == '+')) {
+ ++iter;
+ }
+
+ // parse number
+ while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) {
+ seconds = seconds * 10 + (*iter - '0');
+ specifiesSeconds = true;
+ ++iter;
+ }
+
+ if (iter != doneIterating) {
+ // if we started with a '-', number is negative
+ if (*tokenStart == '-') {
+ seconds = -seconds;
+ }
+
+ // skip to next ';' or ','
+ nsACString::const_iterator iterAfterDigit = iter;
+ while (iter != doneIterating && !(*iter == ';' || *iter == ',')) {
+ if (specifiesSeconds) {
+ // Non-whitespace characters here mean that the string is
+ // malformed but tolerate sites that specify a decimal point,
+ // even though meta refresh only works on whole seconds.
+ if (iter == iterAfterDigit && !nsCRT::IsAsciiSpace(*iter) &&
+ *iter != '.') {
+ // The characters between the seconds and the next
+ // section are just garbage!
+ // e.g. content="2a0z+,URL=http://www.mozilla.org/"
+ // Just ignore this redirect.
+ return NS_ERROR_FAILURE;
+ } else if (nsCRT::IsAsciiSpace(*iter)) {
+ // We've had at least one whitespace so tolerate the mistake
+ // and drop through.
+ // e.g. content="10 foo"
+ ++iter;
+ break;
+ }
+ }
+ ++iter;
+ }
+
+ // skip any remaining whitespace
+ while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ // skip ';' or ','
+ if (iter != doneIterating && (*iter == ';' || *iter == ',')) {
+ ++iter;
+ }
+
+ // skip whitespace
+ while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+ }
+
+ // possible start of URI
+ tokenStart = iter;
+
+ // skip "url = " to real start of URI
+ if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) {
+ ++iter;
+ if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) {
+ ++iter;
+ if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) {
+ ++iter;
+
+ // skip whitespace
+ while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ if (iter != doneIterating && *iter == '=') {
+ ++iter;
+
+ // skip whitespace
+ while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ // found real start of URI
+ tokenStart = iter;
+ }
+ }
+ }
+ }
+
+ // skip a leading '"' or '\''.
+
+ bool isQuotedURI = false;
+ if (tokenStart != doneIterating &&
+ (*tokenStart == '"' || *tokenStart == '\'')) {
+ isQuotedURI = true;
+ ++tokenStart;
+ }
+
+ // set iter to start of URI
+ iter = tokenStart;
+
+ // tokenStart here points to the beginning of URI
+
+ // grab the rest of the URI
+ while (iter != doneIterating) {
+ if (isQuotedURI && (*iter == '"' || *iter == '\'')) {
+ break;
+ }
+ ++iter;
+ }
+
+ // move iter one back if the last character is a '"' or '\''
+ if (iter != tokenStart && isQuotedURI) {
+ --iter;
+ if (!(*iter == '"' || *iter == '\'')) {
+ ++iter;
+ }
+ }
+
+ // URI is whatever's contained from tokenStart to iter.
+ // note: if tokenStart == doneIterating, so is iter.
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIURI> uri;
+ bool specifiesURI = false;
+ if (tokenStart == iter) {
+ uri = aBaseURI;
+ } else {
+ uriAttrib = Substring(tokenStart, iter);
+ // NS_NewURI takes care of any whitespace surrounding the URL
+ rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI);
+ specifiesURI = true;
+ }
+
+ // No URI or seconds were specified
+ if (!specifiesSeconds && !specifiesURI) {
+ // Do nothing because the alternative is to spin around in a refresh
+ // loop forever!
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIScriptSecurityManager> securityManager(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = securityManager->CheckLoadURIWithPrincipal(
+ aPrincipal, uri,
+ nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT,
+ aInnerWindowID);
+
+ if (NS_SUCCEEDED(rv)) {
+ bool isjs = true;
+ rv = NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isjs) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // Since we can't travel back in time yet, just pretend
+ // negative numbers do nothing at all.
+ if (seconds < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = RefreshURI(uri, aPrincipal, seconds * 1000, false, true);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetupRefreshURI(nsIChannel* aChannel) {
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString refreshHeader;
+ rv = httpChannel->GetResponseHeader("refresh"_ns, refreshHeader);
+
+ if (!refreshHeader.IsEmpty()) {
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = secMan->GetChannelResultPrincipal(aChannel,
+ getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetupReferrerInfoFromChannel(aChannel);
+ // We have no idea what window id to use for error reporting
+ // here, so just pass 0.
+ rv = SetupRefreshURIFromHeader(mCurrentURI, principal, 0, refreshHeader);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_REFRESHURI_HEADER_FOUND;
+ }
+ }
+ }
+ return rv;
+}
+
+static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) {
+ if (!aTimerList) {
+ return;
+ }
+
+ uint32_t n = 0;
+ aTimerList->GetLength(&n);
+
+ while (n) {
+ nsCOMPtr<nsITimer> 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);
+ mRefreshURIList = nullptr;
+ mSavedRefreshURIList = 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;
+}
+
+NS_IMETHODIMP
+nsDocShell::SuspendRefreshURIs() {
+ if (mRefreshURIList) {
+ uint32_t n = 0;
+ mRefreshURIList->GetLength(&n);
+
+ for (uint32_t i = 0; i < n; ++i) {
+ nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (!timer) {
+ continue; // this must be a nsRefreshURI already
+ }
+
+ // Replace this timer object with a nsRefreshTimer object.
+ nsCOMPtr<nsITimerCallback> callback;
+ timer->GetCallback(getter_AddRefs(callback));
+
+ timer->Cancel();
+
+ mRefreshURIList->ReplaceElementAt(callback, i);
+ }
+ }
+
+ // Suspend refresh URIs for our child shells as well.
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> 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<nsIDocShell> 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<nsITimerCallback> 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<nsRefreshTimer*>(
+ static_cast<nsITimerCallback*>(refreshInfo))
+ ->GetDelay();
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ if (win) {
+ nsCOMPtr<nsITimer> 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;
+}
+
+nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer,
+ WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist) {
+ // 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<nsISHEntry*>(mLSHE));
+ }
+
+ if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this));
+ MoveLoadingToActiveEntry(aPersist);
+ }
+
+ bool updateHistory = true;
+
+ // Determine if this type of load should update history
+ switch (mLoadType) {
+ case LOAD_NORMAL_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<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsIURI> 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<DocumentChannel> 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<nsIWidget> mainWidget;
+ GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget) {
+ mainWidget->SetCursor(eCursor_spinning, nullptr, 0, 0);
+ }
+ }
+ }
+ } 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<nsIWidget> mainWidget;
+ GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget) {
+ mainWidget->SetCursor(eCursor_standard, nullptr, 0, 0);
+ }
+ }
+ }
+ if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+ // Is the document stop notification for this document?
+ if (aProgress == webProgress.get()) {
+ nsCOMPtr<nsIChannel> 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) {
+ if (XRE_IsParentProcess()) {
+ // 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.
+ if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) {
+ 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 mixed content is allowed for the old channel, we forward
+ // the permission to the new channel if it has the same origin
+ // as the old one.
+ if (mMixedContentChannel && mMixedContentChannel == aOldChannel) {
+ nsresult rv =
+ nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel);
+ if (NS_SUCCEEDED(rv)) {
+ SetMixedContentChannel(aNewChannel); // Same origin: forward permission.
+ } else {
+ SetMixedContentChannel(
+ nullptr); // Different origin: clear mMixedContentChannel.
+ }
+ }
+
+ if (!(aStateFlags & STATE_IS_DOCUMENT)) {
+ return; // not a toplevel document
+ }
+
+ nsCOMPtr<nsIURI> 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<DocumentChannel> 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<nsIURI> 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<nsIHttpChannel> 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);
+ }
+ }
+
+ // check if the new load should go through the application cache.
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(aNewChannel);
+ if (appCacheChannel && !docChannel) {
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ // Permission will be checked in the parent process.
+ appCacheChannel->SetChooseApplicationCache(true);
+ } else {
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+ if (secMan) {
+ nsCOMPtr<nsIPrincipal> principal;
+ secMan->GetDocShellContentPrincipal(newURI, this,
+ getter_AddRefs(principal));
+ appCacheChannel->SetChooseApplicationCache(
+ NS_ShouldCheckAppCache(principal));
+ }
+ }
+ }
+
+ 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<nsIURIFixupInfo> nsDocShell::KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext) {
+ nsCOMPtr<nsIURIFixupInfo> info;
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info));
+ }
+ }
+ return info.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> 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<nsILoadInfo> 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<nsISupports> securityInfo;
+ rv = aChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi = do_QueryInterface(securityInfo);
+ if (NS_WARN_IF(!tsi)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> 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<const uint8_t*, const char*>(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.
+ mozilla::psm::BRNameMatchingPolicy nameMatchingPolicy(
+ mozilla::psm::BRNameMatchingPolicy::Mode::Enforce);
+ rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput,
+ nameMatchingPolicy);
+ if (rv1 != mozilla::pkix::Success) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize(
+ getter_AddRefs(newURI));
+
+ return newURI.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& 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<nsIURI> 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<nsIURI> newURI;
+ nsCOMPtr<nsIInputStream> 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<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService) {
+ nsAutoCString suffix;
+ attemptFixup =
+ NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) &&
+ suffix.IsEmpty();
+ }
+ }
+ if (attemptFixup) {
+ nsCOMPtr<nsIURIFixupInfo> 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<nsIIDNService> 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) {
+ bool doCreateAlternate = true;
+
+ // Skip fixup for anything except a normal document load
+ // operation on the topframe.
+
+ if (aLoadType != LOAD_NORMAL || !aIsTopFrame) {
+ doCreateAlternate = false;
+ } else {
+ // Test if keyword lookup produced a new URI or not
+ if (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) {
+ nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
+ // Skip doing this if our channel was redirected, because we
+ // shouldn't be guessing things about the post-redirect URI.
+ if (!info->RedirectChain().IsEmpty()) {
+ doCreateAlternate = false;
+ }
+ }
+ }
+ if (doCreateAlternate) {
+ newURI = nullptr;
+ newPostData = nullptr;
+ keywordProviderName.Truncate();
+ keywordAsSent.Truncate();
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ nsCOMPtr<nsIURIFixupInfo> 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) &&
+ (aIsTopFrame || aUseErrorPages)) {
+ return aStatus;
+ }
+
+ if (aStatus == NS_ERROR_NET_TIMEOUT ||
+ 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_REMOTE_XUL ||
+ 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<nsILoadInfo> 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<uint32_t>(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<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
+ if (reporter) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ reporter->FlushConsoleReports(loadGroup);
+ } else {
+ reporter->FlushConsoleReports(GetDocument());
+ }
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(url));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsITimedChannel> 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<nsISHEntry> 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<nsIDocShell> 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<nsIContentViewer> contentViewer = mContentViewer;
+ contentViewer->LoadComplete(aStatus);
+ mIsExecutingOnLoadHandler = false;
+
+ mEODForCurrentDocument = true;
+
+ // If all documents have completed their loading
+ // favor native event dispatch priorities
+ // over performance
+ if (--gNumberOfDocumentsLoading == 0) {
+ // Hint to use normal native event dispatch priorities
+ FavorPerformanceHint(false);
+ }
+ }
+ /* 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<nsIHttpChannel> 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();
+
+ // 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);
+ if (NS_FAILED(aStatus)) {
+ DisplayLoadError(aStatus, url, nullptr, aChannel);
+ } else if (skippedUnknownProtocolNavigation) {
+ nsTArray<nsString> 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<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes());
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell: Content Viewer Management
+//*****************************************************************************
+
+nsresult nsDocShell::EnsureContentViewer() {
+ if (mContentViewer) {
+ return NS_OK;
+ }
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
+ nsCOMPtr<nsIURI> baseURI;
+ nsIPrincipal* principal = GetInheritedPrincipal(false);
+ nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) {
+ nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal();
+ if (parentElement) {
+ baseURI = parentElement->GetBaseURI();
+ cspToInheritForAboutBlank = parentElement->GetCsp();
+ }
+ }
+ }
+
+ nsresult rv = CreateAboutBlankContentViewer(
+ principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI);
+
+ NS_ENSURE_STATE(mContentViewer);
+
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> doc(GetDocument());
+ NS_ASSERTION(doc,
+ "Should have doc if CreateAboutBlankContentViewer "
+ "succeeded!");
+
+ doc->SetIsInitialDocument(true);
+
+ // 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,
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP,
+ bool aTryToSaveOldPresentation, bool aCheckPermitUnload,
+ WindowGlobalChild* aActor) {
+ RefPtr<Document> blankDoc;
+ nsCOMPtr<nsIContentViewer> 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->RemoveRootFromBFCacheSync();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // mContentViewer->PermitUnload may release |this| docshell.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ AutoRestore<bool> 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);
+
+ // 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<nsIDocumentLoaderFactory> docFactory =
+ nsContentUtils::FindInternalContentViewer("text/html"_ns);
+
+ if (docFactory) {
+ nsCOMPtr<nsIPrincipal> 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::CreateWithInheritedAttributes(this);
+ }
+ partitionedPrincipal = principal;
+ } else {
+ principal = aPrincipal;
+ partitionedPrincipal = aPartitionedPrincipal;
+ }
+
+ MaybeCreateInitialClientSource(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<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(aCSP));
+ blankDoc->SetCsp(cspToInherit);
+ }
+
+ 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);
+
+ // 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);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 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);
+}
+
+nsresult nsDocShell::CreateContentViewerForActor(
+ WindowGlobalChild* aWindowActor) {
+ MOZ_ASSERT(aWindowActor);
+
+ // FIXME: WindowGlobalChild should provide the PartitionedPrincipal.
+ nsresult rv = CreateAboutBlankContentViewer(
+ aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(),
+ /* aCsp */ nullptr,
+ /* aBaseURI */ nullptr,
+ /* aCOEP */ Nothing(),
+ /* aTryToSaveOldPresentation */ true,
+ /* aCheckPermitUnload */ true, aWindowActor);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> 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");
+
+ // FIXME: We may want to support non-initial documents here.
+ doc->SetIsInitialDocument(true);
+ }
+
+ return rv;
+}
+
+bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
+ nsIRequest* aNewRequest,
+ Document* aNewDocument) {
+ if (!mOSHE) {
+ return false; // no entry to save into
+ }
+
+ nsCOMPtr<nsIContentViewer> 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<Document> doc = mScriptGlobal->GetExtantDoc();
+
+ uint16_t bfCacheCombo = 0;
+ bool canSavePresentation =
+ doc->CanSavePresentation(aNewRequest, bfCacheCombo);
+ MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0);
+ if (canSavePresentation && doc->IsTopLevelContentDocument()) {
+ auto* browsingContextGroup = mBrowsingContext->Group();
+ nsTArray<RefPtr<BrowsingContext>>& 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;
+ }
+ }
+ }
+ ReportBFCacheComboTelemetry(bfCacheCombo);
+
+ return doc && canSavePresentation;
+}
+
+void nsDocShell::ReportBFCacheComboTelemetry(uint16_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 : uint16_t {
+ BFCACHE_SUCCESS,
+ NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG,
+ 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
+ };
+
+ 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 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(!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() {
+ 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<nsISupports> windowState = mScriptGlobal->SaveWindowState();
+ NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsAutoCString spec;
+ nsCOMPtr<nsIURI> uri;
+ if (mozilla::SessionHistoryInParent()) {
+ uri = mActiveEntry->GetURI();
+ } else {
+ 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<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i));
+ NS_ASSERTION(childShell, "null child shell");
+
+ mOSHE->AddChildShell(childShell);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RestorePresentationEvent::Run() {
+ if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) {
+ NS_WARNING("RestoreFromHistory failed");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) {
+ 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<Document> 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() {
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ nsresult rv = child->BeginRestore(nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::FinishRestore() {
+ // 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<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ child->FinishRestore();
+ }
+ }
+
+ if (mOSHE && mOSHE->HasDetachedEditor()) {
+ ReattachEditorToWindow(mOSHE);
+ }
+
+ RefPtr<Document> 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(!mIsBeingDestroyed);
+
+ NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY,
+ "RestorePresentation should only be called for history loads");
+
+ nsCOMPtr<nsIContentViewer> viewer = aSHEntry->GetContentViewer();
+
+ nsAutoCString spec;
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> 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<nsIDocShell> 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<RestorePresentationEvent> 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<nsDocShell::RestorePresentationEvent>&
+ aRestorePresentationEvent)
+ : mRestorePresentationEvent(aRestorePresentationEvent),
+ mEvent(aRestorePresentationEvent.get()) {}
+
+ ~PresentationEventForgetter() { Forget(); }
+
+ void Forget() {
+ if (mRestorePresentationEvent.get() == mEvent) {
+ mRestorePresentationEvent.Forget();
+ mEvent = nullptr;
+ }
+ }
+
+ private:
+ nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
+ mRestorePresentationEvent;
+ RefPtr<nsDocShell::RestorePresentationEvent> mEvent;
+};
+
+} // namespace
+
+bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) {
+ return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0;
+}
+
+nsresult nsDocShell::RestoreFromHistory() {
+ MOZ_ASSERT(mRestorePresentationEvent.IsPending());
+ PresentationEventForgetter forgetter(mRestorePresentationEvent);
+
+ // This section of code follows the same ordering as CreateContentViewer.
+ if (!mLSHE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContentViewer> 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<Document> doc = viewer->GetDocument();
+ nsIRequest* request = nullptr;
+ if (doc) {
+ request = doc->GetChannel();
+ }
+ mSavingOldViewer = CanSavePresentation(mLoadType, request, doc);
+ }
+
+ nsCOMPtr<nsIContentViewer> oldCv(mContentViewer);
+ nsCOMPtr<nsIContentViewer> newCv(viewer);
+ float overrideDPPX = 0.0f;
+
+ if (oldCv) {
+ oldCv->GetOverrideDPPX(&overrideDPPX);
+ }
+
+ // Protect against mLSHE going away via a load triggered from
+ // pagehide or unload.
+ nsCOMPtr<nsISHEntry> 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<RestorePresentationEvent> 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<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ mPreviousEntryIndex = rootSH->Index();
+ if (!mozilla::SessionHistoryInParent()) {
+ 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<nsIContentViewer> 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<nsIContent> container;
+ RefPtr<Document> 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<nsISupports> windowState = mLSHE->GetWindowState();
+ mLSHE->SetWindowState(nullptr);
+
+ bool sticky = mLSHE->GetSticky();
+
+ RefPtr<Document> document = mContentViewer->GetDocument();
+
+ nsCOMArray<nsIDocShellTreeItem> childShells;
+ int32_t i = 0;
+ nsCOMPtr<nsIDocShellTreeItem> 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<nsIMutableArray> 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<nsDocShellEditorData> data(mLSHE->ForgetEditorData());
+
+ // Now remove it from the cached presentation.
+ mLSHE->SetContentViewer(nullptr);
+ mEODForCurrentDocument = false;
+
+ mLSHE->SetEditorData(data.release());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIMutableArray> refreshURIs = mLSHE->GetRefreshURIList();
+ nsCOMPtr<nsIDocShellTreeItem> 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<nsISHEntry*>(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;
+
+ // Tell the event loop to favor plevents over user events, see comments
+ // in CreateContentViewer.
+ if (++gNumberOfDocumentsLoading == 1) {
+ FavorPerformanceHint(true);
+ }
+
+ if (oldCv) {
+ newCv->SetOverrideDPPX(overrideDPPX);
+ }
+
+ if (document) {
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> 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<nsIURI> uri = origLSHE->GetURI();
+ SetCurrentURI(uri, document->GetChannel(), true, 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<nsPIDOMWindowOuter> privWin = GetWindow();
+ NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface");
+
+ // Now, dispatch a title change event which would happen as the
+ // <head> 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<nsIDocShell> childShell = do_QueryInterface(childItem);
+
+ // Make sure to not clobber the state of the child. Since AddChild
+ // always clobbers it, save it off first.
+ bool allowJavascript;
+ childShell->GetAllowJavascript(&allowJavascript);
+
+ 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->SetAllowJavascript(allowJavascript);
+ 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> 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<nsPresContext> 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<nsPIDOMWindowInner> 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.
+ privWinInner->GetPerformance()->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<nsIDocShell> 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();
+}
+
+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->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<nsIContentViewer> 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<Document> doc = viewer->GetDocument();
+ mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc);
+ }
+
+ NS_ASSERTION(!mLoadingURI, "Re-entering unload?");
+
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+ if (aOpenedChannel) {
+ aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI));
+ }
+ 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<nsIChannel> failedChannel = mFailedChannel;
+ nsCOMPtr<nsIURI> 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<nsIPrincipal> 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<nsIURI> 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<nsILoadGroup> 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<nsILoadInfo> 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()) && IsFrame()) {
+ // 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<Document> newDoc = viewer->GetDocument();
+
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ nsCOMPtr<nsIPrincipal> parentPrincipal =
+ parent->GetDocument()->NodePrincipal();
+ nsCOMPtr<nsIPrincipal> 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)) {
+#ifdef MOZ_GECKO_PROFILER
+ nsCOMPtr<nsIURI> prinURI;
+ BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI));
+ nsPrintfCString marker("Iframe loaded in background: %s",
+ prinURI->GetSpecOrDefault().get());
+ PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker);
+#endif
+ SetBackgroundLoadIframe();
+ }
+ }
+ }
+
+ NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false,
+ ShouldAddToSessionHistory(finalURI, aOpenedChannel)),
+ NS_ERROR_FAILURE);
+
+ if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) {
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true));
+ }
+
+ if (TreatAsBackgroundLoad()) {
+ nsCOMPtr<nsIRunnable> 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<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest));
+ if (multiPartChannel) {
+ if (PresShell* presShell = GetPresShell()) {
+ if (Document* doc = presShell->GetDocument()) {
+ uint32_t partID;
+ multiPartChannel->GetPartID(&partID);
+ doc->SetPartID(partID);
+ }
+ }
+ }
+
+ // Give hint to native plevent dispatch mechanism. If a document
+ // is loading the native plevent dispatch mechanism should favor
+ // performance over normal native event dispatch priorities.
+ if (++gNumberOfDocumentsLoading == 1) {
+ // Hint to favor performance for the plevent notification mechanism.
+ // We want the pages to load as fast as possible even if its means
+ // native messages might be starved.
+ FavorPerformanceHint(true);
+ }
+
+ 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<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> 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 <SCRIPT>s and event handlers fail! (bug 20315)
+ //
+ // In this block of code, if we get an error result, we return it
+ // but if we get a null pointer, that's perfectly legal for parent
+ // and parentContentViewer.
+ //
+
+ int32_t x = 0;
+ int32_t y = 0;
+ int32_t cx = 0;
+ int32_t cy = 0;
+
+ // This will get the size from the current content viewer or from the
+ // Init settings
+ DoGetPositionAndSize(&x, &y, &cx, &cy);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)),
+ NS_ERROR_FAILURE);
+ nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
+
+ const Encoding* hintCharset = nullptr;
+ int32_t hintCharsetSource = kCharsetUninitialized;
+ float overrideDPPX = 1.0;
+ // |newMUDV| also serves as a flag to set the data from the above vars
+ nsCOMPtr<nsIContentViewer> newCv;
+
+ if (mContentViewer || parent) {
+ nsCOMPtr<nsIContentViewer> oldCv;
+ if (mContentViewer) {
+ // Get any interesting state from old content viewer
+ // XXX: it would be far better to just reuse the document viewer ,
+ // since we know we're just displaying the same document as before
+ oldCv = mContentViewer;
+
+ // Tell the old content viewer to hibernate in session history when
+ // it is destroyed.
+
+ if (mSavingOldViewer && NS_FAILED(CaptureState())) {
+ if (mOSHE) {
+ mOSHE->SyncPresentationState();
+ }
+ mSavingOldViewer = false;
+ }
+ } else {
+ // No old content viewer, so get state from parent's content viewer
+ parent->GetContentViewer(getter_AddRefs(oldCv));
+ }
+
+ if (oldCv) {
+ newCv = aNewViewer;
+ if (newCv) {
+ hintCharset = oldCv->GetHintCharset();
+ NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX),
+ NS_ERROR_FAILURE);
+ }
+ }
+ }
+
+ nscolor bgcolor = NS_RGBA(0, 0, 0, 0);
+ bool isActive = false;
+ // Ensure that the content viewer is destroyed *after* the GC - bug 71515
+ nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
+ if (contentViewer) {
+ // Stop any activity that may be happening in the old document before
+ // releasing it...
+ contentViewer->Stop();
+
+ // Try to extract the canvas background color from the old
+ // presentation shell, so we can use it for the next document.
+ if (PresShell* presShell = contentViewer->GetPresShell()) {
+ bgcolor = presShell->GetCanvasBackground();
+ isActive = presShell->IsActive();
+ }
+
+ contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
+ aNewViewer->SetPreviousViewer(contentViewer);
+ }
+ 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();
+ }
+
+ 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 = aNewViewer;
+
+ nsCOMPtr<nsIWidget> widget;
+ NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);
+
+ nsIntRect bounds(x, y, cx, cy);
+
+ mContentViewer->SetNavigationTiming(mTiming);
+
+ if (NS_FAILED(mContentViewer->Init(widget, bounds, aWindowActor))) {
+ mContentViewer = nullptr;
+ NS_WARNING("ContentViewer Initialization failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we have old state to copy, set the old state onto the new content
+ // viewer
+ if (newCv) {
+ newCv->SetHintCharset(hintCharset);
+ NS_ENSURE_SUCCESS(newCv->SetHintCharacterSetSource(hintCharsetSource),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX), NS_ERROR_FAILURE);
+ }
+
+ // Stuff the bgcolor from the old pres shell into the new
+ // pres shell. This improves page load continuity.
+ if (RefPtr<PresShell> presShell = mContentViewer->GetPresShell()) {
+ presShell->SetCanvasBackground(bgcolor);
+ if (isActive) {
+ presShell->SetIsActive(isActive);
+ }
+ }
+
+ // XXX: It looks like the LayoutState gets restored again in Embed()
+ // right after the call to SetupNewViewer(...)
+
+ // We don't show the mContentViewer yet, since we want to draw the old page
+ // until we have enough of the new page to show. Just return with the new
+ // viewer still set to hidden.
+
+ return NS_OK;
+}
+
+void nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ SessionHistoryInfo* aInfo) {
+ NS_ENSURE_TRUE_VOID(mContentViewer);
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE_VOID(document);
+
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+ if (mozilla::SessionHistoryInParent()) {
+ // If aInfo is null, just set the document's state object to null.
+ if (aInfo) {
+ scContainer = aInfo->GetStateData();
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p SetCurrentDocState %p", this, scContainer.get()));
+ } else {
+ if (aShEntry) {
+ scContainer = aShEntry->GetStateData();
+
+ // If aShEntry is null, just set the document's state object to null.
+ }
+ }
+
+ // It's OK for scContainer too be null here; that just means there's no
+ // state data associated with this history entry.
+ document->SetStateObject(scContainer);
+}
+
+nsresult nsDocShell::CheckLoadingPermissions() {
+ // This method checks whether the caller may load content into
+ // this docshell. Even though we've done our best to hide windows
+ // from code that doesn't have the right to access them, it's
+ // still possible for an evil site to open a window and access
+ // frames in the new window through window.frames[] (which is
+ // allAccess for historic reasons), so we still need to do this
+ // check on load.
+ nsresult rv = NS_OK;
+
+ if (!IsFrame()) {
+ // We're not a frame. Permit all loads.
+ return rv;
+ }
+
+ // Note - The check for a current JSContext here isn't necessarily sensical.
+ // It's just designed to preserve the old semantics during a mass-conversion
+ // patch.
+ if (!nsContentUtils::GetCurrentJSContext()) {
+ return NS_OK;
+ }
+
+ // Check if the caller is from the same origin as this docshell,
+ // or any of its ancestors.
+ for (RefPtr<BrowsingContext> bc = mBrowsingContext; bc;
+ bc = bc->GetParent()) {
+ // If the BrowsingContext is not in process, then it
+ // is true by construction that its principal will not
+ // subsume the current docshell principal.
+ if (!bc->IsInProcess()) {
+ continue;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ bc->GetDocShell()->GetScriptGlobalObject();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo));
+
+ nsIPrincipal* p;
+ if (!sop || !(p = sop->GetPrincipal())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) {
+ // Same origin, permit load
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+}
+
+//*****************************************************************************
+// nsDocShell: Site Loading
+//*****************************************************************************
+
+void nsDocShell::CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (contentChild) {
+ contentChild->SendCopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing);
+ }
+ return;
+ }
+
+#ifdef MOZ_PLACES
+ nsCOMPtr<nsIFaviconService> favSvc =
+ do_GetService("@mozilla.org/browser/favicon-service;1");
+ if (favSvc) {
+ favSvc->CopyFavicons(aOldURI, aNewURI,
+ aInPrivateBrowsing
+ ? nsIFaviconService::FAVICON_LOAD_PRIVATE
+ : nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
+ nullptr);
+ }
+#endif
+}
+
+class InternalLoadEvent : public Runnable {
+ public:
+ InternalLoadEvent(nsDocShell* aDocShell, nsDocShellLoadState* aLoadState)
+ : mozilla::Runnable("InternalLoadEvent"),
+ mDocShell(aDocShell),
+ mLoadState(aLoadState) {
+ // For events, both target and filename should be the version of "null" they
+ // expect. By the time the event is fired, both window targeting and file
+ // downloading have been handled, so we should never have an internal load
+ // event that retargets or had a download.
+ mLoadState->SetTarget(u""_ns);
+ mLoadState->SetFileName(VoidString());
+ }
+
+ NS_IMETHOD
+ Run() override {
+#ifndef ANDROID
+ MOZ_ASSERT(mLoadState->TriggeringPrincipal(),
+ "InternalLoadEvent: Should always have a principal here");
+#endif
+ return mDocShell->InternalLoad(mLoadState);
+ }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ RefPtr<nsDocShellLoadState> mLoadState;
+};
+
+/**
+ * Returns true if we started an asynchronous load (i.e., from the network), but
+ * the document we're loading there hasn't yet become this docshell's active
+ * document.
+ *
+ * When JustStartedNetworkLoad is true, you should be careful about modifying
+ * mLoadType and mLSHE. These are both set when the asynchronous load first
+ * starts, and the load expects that, when it eventually runs InternalLoad,
+ * mLoadType and mLSHE will have their original values.
+ */
+bool nsDocShell::JustStartedNetworkLoad() {
+ return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel();
+}
+
+// The contentType will be INTERNAL_(I)FRAME if this docshell is for a
+// non-toplevel browsing context in spec terms. (frame, iframe, <object>,
+// <embed>, etc)
+//
+// This return value will be used when we call NS_CheckContentLoadPolicy, and
+// later when we call DoURILoad.
+nsContentPolicyType nsDocShell::DetermineContentType() {
+ if (!IsFrame()) {
+ return nsIContentPolicy::TYPE_DOCUMENT;
+ }
+
+ const auto& maybeEmbedderElementType =
+ GetBrowsingContext()->GetEmbedderElementType();
+ if (!maybeEmbedderElementType) {
+ // If the EmbedderElementType hasn't been set yet, just assume we're
+ // an iframe since that's more common.
+ return nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+ }
+
+ return maybeEmbedderElementType->EqualsLiteral("iframe")
+ ? nsIContentPolicy::TYPE_INTERNAL_IFRAME
+ : nsIContentPolicy::TYPE_INTERNAL_FRAME;
+}
+
+nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!");
+ MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(),
+ "should not have picked target yet");
+
+ nsresult rv = NS_OK;
+ RefPtr<BrowsingContext> targetContext;
+
+ // Only _self, _parent, and _top are supported in noopener case. But we
+ // have to be careful to not apply that to the noreferrer case. See bug
+ // 1358469.
+ bool allowNamedTarget =
+ !aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) ||
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER);
+ if (allowNamedTarget ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_self") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_parent") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_top")) {
+ targetContext = mBrowsingContext->FindWithName(
+ aLoadState->Target(), /* aUseEntryGlobalForAccessCheck */ false);
+ }
+
+ if (!targetContext) {
+ // If the targetContext doesn't exist, then this is a new docShell and we
+ // should consider this a TYPE_DOCUMENT load
+ //
+ // For example, when target="_blank"
+
+ // If there's no targetContext, that means we are about to create a new
+ // window. Perform a content policy check before creating the window. Please
+ // note for all other docshell loads content policy checks are performed
+ // within the contentSecurityManager when the channel is about to be
+ // openend.
+ nsISupports* requestingContext = nullptr;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process). So we just pass mScriptGlobal.
+ requestingContext = ToSupports(mScriptGlobal);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ mScriptGlobal->GetFrameElementInternal();
+ requestingContext = requestingElement;
+ }
+
+ // Ideally we should use the same loadinfo as within DoURILoad which
+ // should match this one when both are applicable.
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
+ mScriptGlobal, aLoadState->TriggeringPrincipal(), requestingContext,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 0);
+
+ // Since Content Policy checks are performed within docShell as well as
+ // the ContentSecurityManager we need a reliable way to let certain
+ // nsIContentPolicy consumers ignore duplicate calls.
+ secCheckLoadInfo->SetSkipContentPolicyCheckForWebRequest(true);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(aLoadState->URI(), secCheckLoadInfo,
+ ""_ns, // mime guess
+ &shouldLoad);
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldLoad == nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+ if (shouldLoad == nsIContentPolicy::REJECT_POLICY) {
+ return NS_ERROR_BLOCKED_BY_POLICY;
+ }
+ }
+
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ }
+
+ //
+ // Resolve the window target before going any further...
+ // If the load has been targeted to another DocShell, then transfer the
+ // load to it...
+ //
+
+ // We've already done our owner-inheriting. Mask out that bit, so we
+ // don't try inheriting an owner from the target window if we came up
+ // with a null owner above.
+ aLoadState->UnsetLoadFlag(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
+
+ if (!targetContext) {
+ // If the docshell's document is sandboxed, only open a new window
+ // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.
+ // (i.e. if allow-popups is specified)
+ NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
+ Document* doc = mContentViewer->GetDocument();
+
+ const bool isDocumentAuxSandboxed =
+ doc && (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
+
+ if (isDocumentAuxSandboxed) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);
+
+ RefPtr<BrowsingContext> newBC;
+ nsAutoCString spec;
+ aLoadState->URI()->GetSpec(spec);
+
+ // If we are a noopener load, we just hand the whole thing over to our
+ // window.
+ if (aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER)) {
+ // Various asserts that we know to hold because NO_OPENER loads can only
+ // happen for links.
+ MOZ_ASSERT(!aLoadState->LoadReplace());
+ MOZ_ASSERT(aLoadState->PrincipalToInherit() ==
+ aLoadState->TriggeringPrincipal());
+ MOZ_ASSERT(aLoadState->LoadFlags() == INTERNAL_LOAD_FLAGS_NO_OPENER ||
+ aLoadState->LoadFlags() ==
+ (INTERNAL_LOAD_FLAGS_NO_OPENER |
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER));
+ MOZ_ASSERT(!aLoadState->PostDataStream());
+ MOZ_ASSERT(!aLoadState->HeadersStream());
+ // If OnLinkClickSync was invoked inside the onload handler, the load
+ // type would be set to LOAD_NORMAL_REPLACE; otherwise it should be
+ // LOAD_LINK.
+ MOZ_ASSERT(aLoadState->LoadType() == LOAD_LINK ||
+ aLoadState->LoadType() == LOAD_NORMAL_REPLACE);
+ MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory());
+ MOZ_ASSERT(aLoadState->FirstParty()); // Windowwatcher will assume this.
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(aLoadState->URI());
+
+ // Set up our loadinfo so it will do the load as much like we would have
+ // as possible.
+ loadState->SetReferrerInfo(aLoadState->GetReferrerInfo());
+ loadState->SetOriginalURI(aLoadState->OriginalURI());
+
+ Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(resultPrincipalURI);
+
+ loadState->SetMaybeResultPrincipalURI(resultPrincipalURI);
+ loadState->SetKeepResultPrincipalURIIfSet(
+ aLoadState->KeepResultPrincipalURIIfSet());
+ // LoadReplace will always be false due to asserts above, skip setting
+ // it.
+ loadState->SetTriggeringPrincipal(aLoadState->TriggeringPrincipal());
+ loadState->SetTriggeringSandboxFlags(
+ aLoadState->TriggeringSandboxFlags());
+ loadState->SetCsp(aLoadState->Csp());
+ loadState->SetInheritPrincipal(
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL));
+ // Explicit principal because we do not want any guesses as to what the
+ // principal to inherit is: it should be aTriggeringPrincipal.
+ loadState->SetPrincipalIsExplicit(true);
+ loadState->SetLoadType(LOAD_LINK);
+ loadState->SetForceAllowDataURI(
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+
+ loadState->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+
+ rv = win->Open(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ loadState,
+ true, // aForceNoOpener
+ getter_AddRefs(newBC));
+ MOZ_ASSERT(!newBC);
+ return rv;
+ }
+
+ rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ getter_AddRefs(newBC));
+
+ // In some cases the Open call doesn't actually result in a new
+ // window being opened. We can detect these cases by examining the
+ // document in |newBC|, if any.
+ nsCOMPtr<nsPIDOMWindowOuter> piNewWin =
+ newBC ? newBC->GetDOMWindow() : nullptr;
+ if (piNewWin) {
+ RefPtr<Document> newDoc = piNewWin->GetExtantDoc();
+ if (!newDoc || newDoc->IsInitialDocument()) {
+ aLoadState->SetLoadFlag(INTERNAL_LOAD_FLAGS_FIRST_LOAD);
+ }
+ }
+
+ if (newBC) {
+ targetContext = newBC;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(targetContext, rv);
+
+ // If our target BrowsingContext is still pending initialization, ignore the
+ // navigation request targeting it.
+ if (NS_WARN_IF(targetContext->GetPendingInitialization())) {
+ return NS_OK;
+ }
+
+ aLoadState->SetTargetBrowsingContext(targetContext);
+ //
+ // Transfer the load to the target BrowsingContext... Clear the window target
+ // name to the empty string to prevent recursive retargeting!
+ //
+ // No window target
+ aLoadState->SetTarget(u""_ns);
+ // No forced download
+ aLoadState->SetFileName(VoidString());
+ return targetContext->InternalLoad(aLoadState);
+}
+
+bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState) {
+ MOZ_ASSERT(aLoadState);
+ if (!(aLoadState->LoadType() == LOAD_NORMAL ||
+ aLoadState->LoadType() == LOAD_STOP_CONTENT ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY) ||
+ aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_LINK)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash);
+ if (NS_SUCCEEDED(rvURINew)) {
+ rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
+ }
+
+ if (currentURI && NS_SUCCEEDED(rvURINew)) {
+ nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(),
+ &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ }
+ }
+
+ if (!aState.mSameExceptHashes && currentURI && NS_SUCCEEDED(rvURINew)) {
+ // Maybe aLoadState->URI() came from the exposable form of currentURI?
+ nsCOMPtr<nsIURI> currentExposableURI =
+ nsIOService::CreateExposableURI(currentURI);
+ nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentExposableURI->EqualsExceptRef(
+ aLoadState->URI(), &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ }
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry && aLoadState->LoadIsFromSessionHistory()) {
+ aState.mHistoryNavBetweenSameDoc = mActiveEntry->SharesDocumentWith(
+ aLoadState->GetLoadingSessionHistoryInfo()->mInfo);
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p NavBetweenSameDoc=%d",
+ this, aState.mHistoryNavBetweenSameDoc));
+ } else {
+ if (mOSHE && aLoadState->LoadIsFromSessionHistory()) {
+ // We're doing a history load.
+
+ mOSHE->SharesDocumentWith(aLoadState->SHEntry(),
+ &aState.mHistoryNavBetweenSameDoc);
+ }
+ }
+
+#ifdef DEBUG
+ if (aState.mHistoryNavBetweenSameDoc) {
+ nsCOMPtr<nsIInputStream> currentPostData;
+ if (mozilla::SessionHistoryInParent()) {
+ currentPostData = mActiveEntry->GetPostData();
+ } else {
+ currentPostData = mOSHE->GetPostData();
+ }
+ NS_ASSERTION(currentPostData == aLoadState->PostDataStream(),
+ "Different POST data for entries for the same page?");
+ }
+#endif
+
+ // A same document navigation happens when we navigate between two SHEntries
+ // for the same document. We do a same document navigation under two
+ // circumstances. Either
+ //
+ // a) we're navigating between two different SHEntries which share a
+ // document, or
+ //
+ // b) we're navigating to a new shentry whose URI differs from the
+ // current URI only in its hash, the new hash is non-empty, and
+ // we're not doing a POST.
+ //
+ // The restriction that the SHEntries in (a) must be different ensures
+ // that history.go(0) and the like trigger full refreshes, rather than
+ // same document navigations.
+ if (!mozilla::SessionHistoryInParent()) {
+ bool doSameDocumentNavigation =
+ (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
+ (!aLoadState->SHEntry() && !aLoadState->PostDataStream() &&
+ aState.mSameExceptHashes && aState.mNewURIHasRef);
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p NavBetweenSameDoc=%d is same doc = %d", this,
+ aState.mHistoryNavBetweenSameDoc, doSameDocumentNavigation));
+ return doSameDocumentNavigation;
+ }
+
+ if (aState.mHistoryNavBetweenSameDoc &&
+ !aLoadState->GetLoadingSessionHistoryInfo()->mLoadingCurrentActiveEntry) {
+ return true;
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p !LoadIsFromSessionHistory=%s "
+ "!PostDataStream: %s mSameExceptHashes: %s mNewURIHasRef: %s",
+ this, !aLoadState->LoadIsFromSessionHistory() ? "true" : "false",
+ !aLoadState->PostDataStream() ? "true" : "false",
+ aState.mSameExceptHashes ? "true" : "false",
+ aState.mNewURIHasRef ? "true" : "false"));
+ return !aLoadState->LoadIsFromSessionHistory() &&
+ !aLoadState->PostDataStream() && aState.mSameExceptHashes &&
+ aState.mNewURIHasRef;
+}
+
+nsresult nsDocShell::HandleSameDocumentNavigation(
+ nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) {
+#ifdef DEBUG
+ SameDocumentNavigationState state;
+ MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state));
+#endif
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::HandleSameDocumentNavigation %p %s -> %s", this,
+ mCurrentURI->GetSpecOrDefault().get(),
+ aLoadState->URI()->GetSpecOrDefault().get()));
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ // Save the position of the scrollers.
+ nsPoint scrollPos = GetCurScrollPos();
+
+ // Reset mLoadType to its original value once we exit this block, because this
+ // same document navigation might have started after a normal, network load,
+ // and we don't want to clobber its load type. See bug 737307.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // If a non-same-document-navigation (i.e., a network load) is pending, make
+ // this a replacement load, so that we don't add a SHEntry here and the
+ // network load goes into the SHEntry it expects to.
+ if (JustStartedNetworkLoad() && (aLoadState->LoadType() & LOAD_CMD_NORMAL)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ } else {
+ mLoadType = aLoadState->LoadType();
+ }
+
+ mURIResultedInDocument = true;
+
+ nsCOMPtr<nsISHEntry> oldLSHE = mLSHE;
+
+ // we need to assign aLoadState->SHEntry() to mLSHE right here, so that on
+ // History loads, SetCurrentURI() called from OnNewURI() will send proper
+ // onLocationChange() notifications to the browser to update back/forward
+ // buttons.
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> oldLoadingEntry;
+ mLoadingEntry.swap(oldLoadingEntry);
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(
+ *aLoadState->GetLoadingSessionHistoryInfo());
+ }
+
+ // Set the doc's URI according to the new history entry's URI.
+ RefPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ doc->SetDocumentURI(aLoadState->URI());
+
+ /* This is a anchor traversal within the same page.
+ * call OnNewURI() so that, this traversal will be
+ * recorded in session and global history.
+ */
+ nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> newCsp;
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ if (mozilla::SessionHistoryInParent()) {
+ newURITriggeringPrincipal = mActiveEntry->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mActiveEntry->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mActiveEntry->GetPartitionedPrincipalToInherit();
+ newCsp = mActiveEntry->GetCsp();
+ } else {
+ newURITriggeringPrincipal = mOSHE->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mOSHE->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mOSHE->GetPartitionedPrincipalToInherit();
+ newCsp = mOSHE->GetCsp();
+ }
+ } else {
+ newURITriggeringPrincipal = aLoadState->TriggeringPrincipal();
+ newURIPrincipalToInherit = doc->NodePrincipal();
+ newURIPartitionedPrincipalToInherit = doc->PartitionedPrincipal();
+ newCsp = doc->GetCsp();
+ }
+ // Pass true for aCloneSHChildren, since we're not
+ // changing documents here, so all of our subframes are
+ // still relevant to the new session history entry.
+ //
+ // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT
+ // flag on firing onLocationChange(...).
+ // Anyway, aCloneSHChildren param is simply reflecting
+ // doSameDocumentNavigation in this scope.
+ //
+ // Note: we'll actually fire onLocationChange later, in order to preserve
+ // ordering of HistoryCommit() in the parent vs onLocationChange (bug
+ // 1668126)
+ bool locationChangeNeeded =
+ OnNewURI(aLoadState->URI(), nullptr, newURITriggeringPrincipal,
+ newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit,
+ newCsp, false, true, true);
+
+ nsCOMPtr<nsIInputStream> postData;
+ uint32_t cacheKey = 0;
+
+ bool scrollRestorationIsManual = false;
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mOSHE) {
+ /* save current position of scroller(s) (bug 59774) */
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ // Get the postdata and page ident from the current page, if
+ // the new load is being done via normal means. Note that
+ // "normal means" can be checked for just by checking for
+ // LOAD_CMD_NORMAL, given the loadType and allowScroll check
+ // above -- it filters out some LOAD_CMD_NORMAL cases that we
+ // wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mOSHE->GetPostData();
+ cacheKey = mOSHE->GetCacheKey();
+ }
+
+ // Link our new SHEntry to the old SHEntry's back/forward
+ // cache data, since the two SHEntries correspond to the
+ // same document.
+ if (mLSHE) {
+ if (!aLoadState->LoadIsFromSessionHistory()) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ SetScrollRestorationIsManualOnHistoryEntry(mLSHE,
+ scrollRestorationIsManual);
+ }
+ mLSHE->AdoptBFCacheEntry(mOSHE);
+ }
+ }
+ } else {
+ if (mLoadingEntry) {
+ if (!mLoadingEntry->mLoadIsFromSessionHistory) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ // XXX This needs most probably tweaks once fragment navigation is
+ // fixed to work with session-history-in-parent.
+ SetScrollRestorationIsManualOnHistoryEntry(nullptr,
+ scrollRestorationIsManual);
+ }
+ }
+ }
+
+ // If we're doing a history load, use its scroll restoration state.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = aLoadState->GetLoadingSessionHistoryInfo()
+ ->mInfo.GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual =
+ aLoadState->SHEntry()->GetScrollRestorationIsManual();
+ }
+ }
+
+ /* Assign mLSHE to mOSHE. This will either be a new entry created
+ * by OnNewURI() for normal loads or aLoadState->SHEntry() for history
+ * loads.
+ */
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mLSHE) {
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
+ // Save the postData obtained from the previous page
+ // in to the session history entry created for the
+ // anchor page, so that any history load of the anchor
+ // page will restore the appropriate postData.
+ if (postData) {
+ mOSHE->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mOSHE->SetCacheKey(cacheKey);
+ }
+ }
+
+ /* Set the title for the SH entry for this target url so that
+ * SH menus in go/back/forward buttons won't be empty for this.
+ * Note, this happens on mOSHE (and mActiveEntry in the future) because of
+ * the code above.
+ * XXX HandleSameDocumentNavigation needs to be made work with
+ * session-history-in-parent, and then this might not be needed.
+ */
+ SetTitleOnHistoryEntry();
+ } else {
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p to "
+ "%s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+ bool hadActiveEntry = !!mActiveEntry;
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ mBrowsingContext->SessionHistoryCommit(*mLoadingEntry, mLoadType,
+ hadActiveEntry, true, true);
+ // FIXME Need to set postdata.
+ SetCacheKeyOnHistoryEntry(nullptr, cacheKey);
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ SetTitleOnHistoryEntry();
+ } else {
+ Maybe<bool> scrollRestorationIsManual;
+ if (mActiveEntry) {
+ scrollRestorationIsManual.emplace(
+ mActiveEntry->GetScrollRestorationIsManual());
+
+ // Get the postdata and page ident from the current page, if the new
+ // load is being done via normal means. Note that "normal means" can be
+ // checked for just by checking for LOAD_CMD_NORMAL, given the loadType
+ // and allowScroll check above -- it filters out some LOAD_CMD_NORMAL
+ // cases that we wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mActiveEntry->GetPostData();
+ cacheKey = mActiveEntry->GetCacheKey();
+ }
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+ if (mActiveEntry) {
+ mActiveEntry =
+ MakeUnique<SessionHistoryInfo>(*mActiveEntry, aLoadState->URI());
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ aLoadState->URI(), newURITriggeringPrincipal,
+ newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit,
+ newCsp, mContentTypeHint);
+ }
+
+ // Save the postData obtained from the previous page in to the session
+ // history entry created for the anchor page, so that any history load of
+ // the anchor page will restore the appropriate postData.
+ if (postData) {
+ mActiveEntry->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mActiveEntry->SetCacheKey(cacheKey);
+ }
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ mActiveEntry->SetTitle(mTitle);
+
+ if (scrollRestorationIsManual.isSome()) {
+ mActiveEntry->SetScrollRestorationIsManual(
+ scrollRestorationIsManual.value());
+ }
+
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ Some(scrollPos), mActiveEntry.get(), mLoadType, cacheKey);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+ }
+ }
+
+ if (locationChangeNeeded) {
+ FireOnLocationChange(this, nullptr, aLoadState->URI(),
+ LOCATION_CHANGE_SAME_DOCUMENT);
+ }
+
+ /* Restore the original LSHE if we were loading something
+ * while same document navigation was initiated.
+ */
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(oldLSHE), Nothing());
+ mLoadingEntry.swap(oldLoadingEntry);
+
+ /* Set the title for the Global History entry for this anchor url.
+ */
+ UpdateGlobalHistoryTitle(aLoadState->URI());
+
+ SetDocCurrentStateObj(mOSHE, mActiveEntry.get());
+
+ // Inform the favicon service that the favicon for oldURI also
+ // applies to aLoadState->URI().
+ CopyFavicon(currentURI, aLoadState->URI(), UsePrivateBrowsing());
+
+ RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal;
+ RefPtr<nsGlobalWindowInner> win =
+ scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+
+ // ScrollToAnchor doesn't necessarily cause us to scroll the window;
+ // the function decides whether a scroll is appropriate based on the
+ // arguments it receives. But even if we don't end up scrolling,
+ // ScrollToAnchor performs other important tasks, such as informing
+ // the presShell that we have a new hash. See bug 680257.
+ nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef,
+ aState.mNewHash, aLoadState->LoadType());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* restore previous position of scroller(s), if we're moving
+ * back in history (bug 59774)
+ */
+ nscoord bx = 0;
+ nscoord by = 0;
+ bool needsScrollPosUpdate = false;
+ if ((mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_RELOAD_NORMAL) &&
+ !scrollRestorationIsManual) {
+ needsScrollPosUpdate = true;
+ if (mozilla::SessionHistoryInParent()) {
+ mActiveEntry->GetScrollPosition(&bx, &by);
+ } else {
+ mOSHE->GetScrollPosition(&bx, &by);
+ }
+ }
+
+ // Dispatch the popstate and hashchange events, as appropriate.
+ //
+ // The event dispatch below can cause us to re-enter script and
+ // destroy the docshell, nulling out mScriptGlobal. Hold a stack
+ // reference to avoid null derefs. See bug 914521.
+ if (win) {
+ // Fire a hashchange event URIs differ, and only in their hashes.
+ bool doHashchange = aState.mSameExceptHashes &&
+ (aState.mCurrentURIHasRef != aState.mNewURIHasRef ||
+ !aState.mCurrentHash.Equals(aState.mNewHash));
+
+ if (aState.mHistoryNavBetweenSameDoc || doHashchange) {
+ win->DispatchSyncPopState();
+ }
+
+ if (needsScrollPosUpdate && win->HasActiveDocument()) {
+ SetCurScrollPosEx(bx, by);
+ }
+
+ if (doHashchange) {
+ // Note that currentURI hasn't changed because it's on the
+ // stack, so we can just use it directly as the old URI.
+ win->DispatchAsyncHashchange(currentURI, aLoadState->URI());
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool NavigationShouldTakeFocus(nsDocShell* aDocShell,
+ nsDocShellLoadState* aLoadState) {
+ if (!aLoadState->AllowFocusMove()) {
+ return false;
+ }
+
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (!sourceBC || !sourceBC->IsActive()) {
+ // If the navigation didn't come from a foreground tab, then we don't steal
+ // focus.
+ return false;
+ }
+ auto* bc = aDocShell->GetBrowsingContext();
+ if (sourceBC.get() == bc) {
+ // If it comes from the same tab / frame, don't steal focus either.
+ return false;
+ }
+ auto* fm = nsFocusManager::GetFocusManager();
+ if (fm && bc->IsActive() && fm->IsInActiveWindow(bc)) {
+ // If we're already on the foreground tab of the foreground window, then we
+ // don't need to do this. This helps to e.g. not steal focus from the
+ // browser chrome unnecessarily.
+ return false;
+ }
+ if (auto* doc = aDocShell->GetExtantDocument()) {
+ if (doc->IsInitialDocument()) {
+ // If we're the initial load for the browsing context, the browser
+ // chrome determines what to focus. This is important because the
+ // browser chrome may want to e.g focus the url-bar
+ return false;
+ }
+ }
+ // Take loadDivertedInBackground into account so the behavior would be the
+ // same as how the tab first opened.
+ return !Preferences::GetBool("browser.tabs.loadDivertedInBackground", false);
+}
+
+nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid TriggeringPrincipal");
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "InternalLoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(mBrowsingContext->GetPendingInitialization())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const bool shouldTakeFocus = NavigationShouldTakeFocus(this, aLoadState);
+
+ mOriginalUriString.Truncate();
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p InternalLoad %s\n", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ NS_ENSURE_TRUE(IsValidLoadType(aLoadState->LoadType()), NS_ERROR_INVALID_ARG);
+
+ // Cancel loads coming from Docshells that are being destroyed.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = EnsureScriptEnvironment();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If we have a target to move to, do that now.
+ if (!aLoadState->Target().IsEmpty()) {
+ return PerformRetargeting(aLoadState);
+ }
+
+ if (aLoadState->TargetBrowsingContext().IsNull()) {
+ aLoadState->SetTargetBrowsingContext(GetBrowsingContext());
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadState->TargetBrowsingContext() == GetBrowsingContext(),
+ "Load must be targeting this BrowsingContext");
+
+ // If we don't have a target, we're loading into ourselves, and our load
+ // delegate may want to intercept that load.
+ SameDocumentNavigationState sameDocumentNavigationState;
+ bool sameDocument =
+ IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState) &&
+ !aLoadState->GetPendingRedirectedChannel();
+
+ // Note: We do this check both here and in BrowsingContext::
+ // LoadURI/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(mBrowsingContext->CheckSandboxFlags(aLoadState));
+
+ NS_ENSURE_STATE(!HasUnloadedParent());
+
+ rv = CheckLoadingPermissions();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mFiredUnloadEvent) {
+ if (IsOKToLoadURI(aLoadState->URI())) {
+ MOZ_ASSERT(aLoadState->Target().IsEmpty(),
+ "Shouldn't have a window target here!");
+
+ // If this is a replace load, make whatever load triggered
+ // the unload event also a replace load, so we don't
+ // create extra history entries.
+ if (LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // Do this asynchronously
+ nsCOMPtr<nsIRunnable> ev = new InternalLoadEvent(this, aLoadState);
+ return Dispatch(TaskCategory::Other, ev.forget());
+ }
+
+ // Just ignore this load attempt
+ return NS_OK;
+ }
+
+ // If we are loading a URI that should inherit a security context (basically
+ // javascript: at this point), and the caller has said that principal
+ // inheritance is allowed, there are a few possible cases:
+ //
+ // 1) We are provided with the principal to inherit. In that case, we just use
+ // it.
+ //
+ // 2) The load is coming from some other application. In this case we don't
+ // want to inherit from whatever document we have loaded now, since the
+ // load is unrelated to it.
+ //
+ // 3) It's a load from our application, but does not provide an explicit
+ // principal to inherit. In that case, we want to inherit the principal of
+ // our current document, or of our parent document (if any) if we don't
+ // have a current document.
+ {
+ bool inherits;
+
+ if (aLoadState->LoadType() != LOAD_NORMAL_EXTERNAL &&
+ !aLoadState->PrincipalToInherit() &&
+ (aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) &&
+ NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(
+ aLoadState->URI(), &inherits)) &&
+ inherits) {
+ aLoadState->SetPrincipalToInherit(GetInheritedPrincipal(true));
+ }
+ // If principalToInherit is still null (e.g. if some of the conditions of
+ // were not satisfied), then no inheritance of any sort will happen: the
+ // load will just get a principal based on the URI being loaded.
+ }
+
+ // If this docshell is owned by a frameloader, make sure to cancel
+ // possible frameloader initialization before loading a new page.
+ nsCOMPtr<nsIDocShellTreeItem> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> doc = parent->GetDocument();
+ if (doc) {
+ doc->TryCancelFrameLoaderInitialization(this);
+ }
+ }
+
+ // Before going any further vet loads initiated by external programs.
+ if (aLoadState->LoadType() == LOAD_NORMAL_EXTERNAL) {
+ // Disallow external chrome: loads targetted at content windows
+ if (SchemeIsChrome(aLoadState->URI())) {
+ NS_WARNING("blocked external chrome: url -- use '--chrome' option");
+ return NS_ERROR_FAILURE;
+ }
+
+ // clear the decks to prevent context bleed-through (bug 298255)
+ rv = CreateAboutBlankContentViewer(nullptr, nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // reset loadType so we don't have to add lots of tests for
+ // LOAD_NORMAL_EXTERNAL after this point
+ aLoadState->SetLoadType(LOAD_NORMAL);
+ }
+
+ mAllowKeywordFixup =
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+ mURIResultedInDocument = false; // reset the clock...
+
+ // See if this is actually a load between two history entries for the same
+ // document. If the process fails, or if we successfully navigate within the
+ // same document, return.
+ if (sameDocument) {
+ nsresult rv =
+ HandleSameDocumentNavigation(aLoadState, sameDocumentNavigationState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ // mContentViewer->PermitUnload can destroy |this| docShell, which
+ // causes the next call of CanSavePresentation to crash.
+ // Hold onto |this| until we return, to prevent a crash from happening.
+ // (bug#331040)
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ // Don't init timing for javascript:, since it generally doesn't
+ // actually start a load or anything. If it does, we'll init
+ // timing then, from OnStateChange.
+
+ // XXXbz mTiming should know what channel it's for, so we don't
+ // need this hackery.
+ bool toBeReset = false;
+ bool isJavaScript = SchemeIsJavascript(aLoadState->URI());
+
+ if (!isJavaScript) {
+ toBeReset = MaybeInitTiming();
+ }
+ bool isNotDownload = aLoadState->FileName().IsVoid();
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyBeforeUnload();
+ }
+ // Check if the page doesn't want to be unloaded. The javascript:
+ // protocol handler deals with this for javascript: URLs.
+ if (!isJavaScript && isNotDownload &&
+ !aLoadState->NotifiedBeforeUnloadListeners() && mContentViewer) {
+ 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_OK;
+ }
+ }
+
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyUnloadAccepted(mCurrentURI);
+ }
+
+ // Check if the webbrowser chrome wants the load to proceed; this can be
+ // used to cancel attempts to load URIs in the wrong process. testing
+ // GetPendingRedirectedChannel() helps to avoid revisiting an earlier
+ // redirect decision.
+ nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
+ if (browserChrome3 && !aLoadState->GetPendingRedirectedChannel()) {
+ bool shouldLoad;
+ rv = browserChrome3->ShouldLoadURI(
+ this, aLoadState->URI(), aLoadState->GetReferrerInfo(),
+ !!aLoadState->PostDataStream(), aLoadState->TriggeringPrincipal(),
+ aLoadState->Csp(), &shouldLoad);
+ if (NS_SUCCEEDED(rv) && !shouldLoad) {
+ return NS_OK;
+ }
+ }
+
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ // Similar check will be performed by the ParentProcessDocumentChannel if in
+ // use.
+ if (XRE_IsE10sParentProcess() &&
+ !DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) &&
+ !CanLoadInParentProcess(aLoadState->URI())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Whenever a top-level browsing context is navigated, the user agent MUST
+ // lock the orientation of the document to the document's default
+ // orientation. We don't explicitly check for a top-level browsing context
+ // here because orientation is only set on top-level browsing contexts.
+ if (mBrowsingContext->GetOrientationLock() != hal::eScreenOrientation_None) {
+ MOZ_ASSERT(mBrowsingContext->IsTop());
+ MOZ_ALWAYS_SUCCEEDS(
+ mBrowsingContext->SetOrientationLock(hal::eScreenOrientation_None));
+ if (mBrowsingContext->IsActive()) {
+ ScreenOrientation::UpdateActiveOrientationLock(
+ hal::eScreenOrientation_None);
+ }
+ }
+
+ // Check for saving the presentation here, before calling Stop().
+ // This is necessary so that we can catch any pending requests.
+ // Since the new request has not been created yet, we pass null for the
+ // new request parameter.
+ // Also pass nullptr for the document, since it doesn't affect the return
+ // value for our purposes here.
+ bool savePresentation =
+ CanSavePresentation(aLoadState->LoadType(), nullptr, nullptr);
+
+ // Don't stop current network activity for javascript: URL's since
+ // they might not result in any data, and thus nothing should be
+ // stopped in those cases. In the case where they do result in
+ // data, the javascript: URL channel takes care of stopping
+ // current network activity.
+ if (!isJavaScript && isNotDownload) {
+ // Stop any current network activity.
+ // Also stop content if this is a zombie doc. otherwise
+ // the onload will be delayed by other loads initiated in the
+ // background by the first document that
+ // didn't fully load before the next load was initiated.
+ // If not a zombie, don't stop content until data
+ // starts arriving from the new URI...
+
+ if ((mContentViewer && mContentViewer->GetPreviousViewer()) ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_STOP_CONTENT)) {
+ rv = Stop(nsIWebNavigation::STOP_ALL);
+ } else {
+ rv = Stop(nsIWebNavigation::STOP_NETWORK);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mLoadType = aLoadState->LoadType();
+
+ // aLoadState->SHEntry() should be assigned to mLSHE, only after Stop() has
+ // been called. But when loading an error page, do not clear the
+ // mLSHE for the real page.
+ if (mLoadType != LOAD_ERROR_PAGE) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ // We're making history navigation or a reload. Make sure our history ID
+ // points to the same ID as SHEntry's docshell ID.
+ nsID historyID = {};
+ aLoadState->SHEntry()->GetDocshellID(historyID);
+
+ Unused << mBrowsingContext->SetHistoryID(historyID);
+ }
+ }
+
+ mSavingOldViewer = savePresentation;
+
+ // If we have a saved content viewer in history, restore and show it now.
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ (mLoadType & LOAD_CMD_HISTORY)) {
+ // https://html.spec.whatwg.org/#history-traversal:
+ // To traverse the history
+ // "If entry has a different Document object than the current entry, then
+ // run the following substeps: Remove any tasks queued by the history
+ // traversal task source..."
+ // Same document object case was handled already above with
+ // HandleSameDocumentNavigation call.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+ if (!mozilla::SessionHistoryInParent()) {
+ // It's possible that the previous viewer of mContentViewer is the
+ // viewer that will end up in aLoadState->SHEntry() when it gets closed.
+ // If that's the case, we need to go ahead and force it into its shentry
+ // so we can restore it.
+ if (mContentViewer) {
+ nsCOMPtr<nsIContentViewer> prevViewer =
+ mContentViewer->GetPreviousViewer();
+ if (prevViewer) {
+#ifdef DEBUG
+ nsCOMPtr<nsIContentViewer> prevPrevViewer =
+ prevViewer->GetPreviousViewer();
+ NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here");
+#endif
+ nsCOMPtr<nsISHEntry> viewerEntry;
+ prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry));
+ if (viewerEntry == aLoadState->SHEntry()) {
+ // Make sure this viewer ends up in the right place
+ mContentViewer->SetPreviousViewer(nullptr);
+ prevViewer->Destroy();
+ }
+ }
+ }
+ nsCOMPtr<nsISHEntry> oldEntry = mOSHE;
+ bool restoring;
+ rv = RestorePresentation(aLoadState->SHEntry(), &restoring);
+ if (restoring) {
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, true);
+ return rv;
+ }
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, false);
+
+ // We failed to restore the presentation, so clean up.
+ // Both the old and new history entries could potentially be in
+ // an inconsistent state.
+ if (NS_FAILED(rv)) {
+ if (oldEntry) {
+ oldEntry->SyncPresentationState();
+ }
+
+ aLoadState->SHEntry()->SyncPresentationState();
+ }
+ }
+ }
+
+ bool isTopLevelDoc = mBrowsingContext->IsTopContent();
+
+ OriginAttributes attrs = GetOriginAttributes();
+ attrs.SetFirstPartyDomain(isTopLevelDoc, aLoadState->URI());
+
+ PredictorLearn(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
+ PredictorPredict(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
+
+ nsCOMPtr<nsIRequest> req;
+ rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
+ UnblockEmbedderLoadEventForFailure();
+ if (DisplayLoadError(rv, aLoadState->URI(), nullptr, chan) &&
+ aLoadState->HasLoadFlags(LOAD_FLAGS_ERROR_LOAD_CHANGES_RV)) {
+ return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
+ }
+
+ // We won't report any error if this is an unknown protocol error. The
+ // reason behind this is that it will allow enumeration of external
+ // protocols if we report an error for each unknown protocol.
+ if (NS_ERROR_UNKNOWN_PROTOCOL == rv) {
+ return NS_OK;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool nsDocShell::CanLoadInParentProcess(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> uri = aURI;
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ bool canLoadInParent = false;
+ if (NS_SUCCEEDED(NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) &&
+ canLoadInParent) {
+ // We allow UI resources.
+ return true;
+ }
+ // For about: and extension-based URIs, which don't get
+ // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present.
+ while (uri && uri->SchemeIs("view-source")) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
+ if (nested) {
+ nested->GetInnerURI(getter_AddRefs(uri));
+ } else {
+ break;
+ }
+ }
+ // Allow about: URIs, and allow moz-extension ones if we're running
+ // extension content in the parent process.
+ if (!uri || uri->SchemeIs("about") ||
+ (!StaticPrefs::extensions_webextensions_remote() &&
+ uri->SchemeIs("moz-extension"))) {
+ return true;
+ }
+#ifdef MOZ_THUNDERBIRD
+ if (uri->SchemeIs("imap") || uri->SchemeIs("mailbox") ||
+ uri->SchemeIs("news") || uri->SchemeIs("nntp") ||
+ uri->SchemeIs("snews")) {
+ return true;
+ }
+#endif
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ // Allow ext+foo URIs (extension-registered custom protocols). See
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
+ if (StringBeginsWith(scheme, "ext+"_ns) &&
+ !StaticPrefs::extensions_webextensions_remote()) {
+ return true;
+ }
+ // Final exception for some legacy automated tests:
+ if (xpc::IsInAutomation() &&
+ StaticPrefs::security_allow_unsafe_parent_loads()) {
+ return true;
+ }
+ return false;
+}
+
+nsIPrincipal* nsDocShell::GetInheritedPrincipal(
+ bool aConsiderCurrentDocument, bool aConsiderPartitionedPrincipal) {
+ RefPtr<Document> document;
+ bool inheritedFromCurrent = false;
+
+ if (aConsiderCurrentDocument && mContentViewer) {
+ document = mContentViewer->GetDocument();
+ inheritedFromCurrent = true;
+ }
+
+ if (!document) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ document = parentItem->GetDocument();
+ }
+ }
+
+ if (!document) {
+ if (!aConsiderCurrentDocument) {
+ return nullptr;
+ }
+
+ // Make sure we end up with _something_ as the principal no matter
+ // what.If this fails, we'll just get a null docViewer and bail.
+ EnsureContentViewer();
+ if (!mContentViewer) {
+ return nullptr;
+ }
+ document = mContentViewer->GetDocument();
+ }
+
+ //-- Get the document's principal
+ if (document) {
+ nsIPrincipal* docPrincipal = aConsiderPartitionedPrincipal
+ ? document->PartitionedPrincipal()
+ : document->NodePrincipal();
+
+ // Don't allow loads in typeContent docShells to inherit the system
+ // principal from existing documents.
+ if (inheritedFromCurrent && mItemType == typeContent &&
+ docPrincipal->IsSystemPrincipal()) {
+ return nullptr;
+ }
+
+ return docPrincipal;
+ }
+
+ return nullptr;
+}
+
+/* static */ nsresult nsDocShell::CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI) {
+ nsCOMPtr<nsIChannel> channel;
+ if (aSrcdoc.IsVoid()) {
+ MOZ_TRY(NS_NewChannelInternal(getter_AddRefs(channel), aURI, aLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ aCallbacks, aLoadFlags));
+
+ if (aBaseURI) {
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel);
+ if (vsc) {
+ MOZ_ALWAYS_SUCCEEDS(vsc->SetBaseURI(aBaseURI));
+ }
+ }
+ } else if (SchemeIsViewSource(aURI)) {
+ // Instantiate view source handler protocol, if it doesn't exist already.
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ MOZ_ASSERT(io);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv =
+ io->GetProtocolHandler("view-source", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance();
+ if (!vsh) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_TRY(vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, aLoadInfo,
+ getter_AddRefs(channel)));
+ } else {
+ MOZ_TRY(NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI,
+ aSrcdoc, "text/html"_ns, aLoadInfo,
+ true));
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ nsresult rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(aChannel);
+ return NS_OK;
+}
+
+/* static */ bool nsDocShell::CreateAndConfigureRealChannelForLoadState(
+ BrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
+ LoadInfo* aLoadInfo, nsIInterfaceRequestor* aCallbacks,
+ nsDocShell* aDocShell, const OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& aRv,
+ nsIChannel** aChannel) {
+ MOZ_ASSERT(aLoadInfo);
+
+ nsString srcdoc = VoidString();
+ bool isSrcdoc = aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ if (isSrcdoc) {
+ srcdoc = aLoadState->SrcdocData();
+ }
+
+ if (aLoadState->PrincipalToInherit()) {
+ aLoadInfo->SetPrincipalToInherit(aLoadState->PrincipalToInherit());
+ }
+ aLoadInfo->SetLoadTriggeredFromExternal(aLoadState->LoadType() ==
+ LOAD_NORMAL_EXTERNAL);
+ aLoadInfo->SetForceAllowDataURI(
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+ aLoadInfo->SetOriginalFrameSrcLoad(
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC));
+
+ bool inheritAttrs = false;
+ if (aLoadState->PrincipalToInherit()) {
+ inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+ }
+
+ OriginAttributes attrs;
+
+ // Inherit origin attributes from PrincipalToInherit if inheritAttrs is
+ // true. Otherwise we just use the origin attributes from docshell.
+ if (inheritAttrs) {
+ MOZ_ASSERT(aLoadState->PrincipalToInherit(),
+ "We should have PrincipalToInherit here.");
+ attrs = aLoadState->PrincipalToInherit()->OriginAttributesRef();
+ // If firstPartyIsolation is not enabled, then PrincipalToInherit should
+ // have the same origin attributes with docshell.
+ MOZ_ASSERT_IF(!OriginAttributes::IsFirstPartyEnabled(),
+ attrs == aOriginAttributes);
+ } else {
+ attrs = aOriginAttributes;
+ attrs.SetFirstPartyDomain(IsTopLevelDoc(aBrowsingContext, aLoadInfo),
+ aLoadState->URI());
+ }
+
+ aRv = aLoadInfo->SetOriginAttributes(attrs);
+ if (NS_WARN_IF(NS_FAILED(aRv))) {
+ return false;
+ }
+
+ if (aLoadState->GetIsFromProcessingFrameAttributes()) {
+ aLoadInfo->SetIsFromProcessingFrameAttributes();
+ }
+
+ // Propagate the IsFormSubmission flag to the loadInfo.
+ if (aLoadState->IsFormSubmission()) {
+ aLoadInfo->SetIsFormSubmission(true);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ aRv = CreateRealChannelForDocument(getter_AddRefs(channel), aLoadState->URI(),
+ aLoadInfo, aCallbacks, aLoadFlags, srcdoc,
+ aLoadState->BaseURI());
+ NS_ENSURE_SUCCESS(aRv, false);
+
+ if (!channel) {
+ return false;
+ }
+
+ // If the HTTPS-Only mode is enabled, every insecure request gets upgraded to
+ // HTTPS by default. This behavior can be disabled through the loadinfo flag
+ // HTTPS_ONLY_EXEMPT.
+ nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(channel);
+
+ if (nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel)) {
+ // Any document load should not inherit application cache.
+ appCacheChannel->SetInheritApplicationCache(false);
+
+ // Loads with the correct permissions should check for a matching
+ // application cache.
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ // Permission will be checked in the parent process
+ appCacheChannel->SetChooseApplicationCache(true);
+ } else if (aDocShell) {
+ // TODO: Figure out how to handle this in the parent,
+ // on behalf of a content process.
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+ if (secMan) {
+ nsCOMPtr<nsIPrincipal> principal;
+ secMan->GetDocShellContentPrincipal(aLoadState->URI(), aDocShell,
+ getter_AddRefs(principal));
+ appCacheChannel->SetChooseApplicationCache(
+ NS_ShouldCheckAppCache(principal));
+ }
+ }
+ }
+
+ // hack
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
+ do_QueryInterface(channel));
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ if (httpChannelInternal) {
+ if (aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES)) {
+ aRv = httpChannelInternal->SetThirdPartyFlags(
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ if (aLoadState->FirstParty()) {
+ aRv = httpChannelInternal->SetDocumentURI(aLoadState->URI());
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ } else {
+ aRv = httpChannelInternal->SetDocumentURI(referrer);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ aRv = httpChannelInternal->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ if (httpChannel) {
+ if (aLoadState->HeadersStream()) {
+ aRv = AddHeadersToChannel(aLoadState->HeadersStream(), httpChannel);
+ }
+ // Set the referrer explicitly
+ // Referrer is currenly only set for link clicks here.
+ if (referrerInfo) {
+ aRv = httpChannel->SetReferrerInfo(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ // Mark the http channel as UrgentStart for top level document loading in
+ // active tab.
+ if (IsUrgentStart(aBrowsingContext, aLoadInfo, aLoadState->LoadType())) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+ }
+ }
+
+ channel->SetOriginalURI(aLoadState->OriginalURI() ? aLoadState->OriginalURI()
+ : aLoadState->URI());
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ channel->SetContentType(typeHint);
+ }
+
+ const nsAString& fileName = aLoadState->FileName();
+ if (!fileName.IsVoid()) {
+ aRv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (!fileName.IsEmpty()) {
+ aRv = channel->SetContentDispositionFilename(fileName);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+ }
+
+ if (nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel)) {
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ // save true referrer for those who need it (e.g. xpinstall whitelisting)
+ // Currently only http and ftp channels support this.
+ props->SetPropertyAsInterface(u"docshell.internalReferrer"_ns, referrer);
+
+ if (aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FIRST_LOAD)) {
+ props->SetPropertyAsBool(u"docshell.newWindowTarget"_ns, true);
+ }
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
+ auto loadType = aLoadState->LoadType();
+
+ // figure out if we need to set the post data stream on the channel...
+ if (aLoadState->PostDataStream()) {
+ if (nsCOMPtr<nsIFormPOSTActionChannel> postChannel =
+ do_QueryInterface(channel)) {
+ // XXX it's a bit of a hack to rewind the postdata stream here but
+ // it has to be done in case the post data is being reused multiple
+ // times.
+ nsCOMPtr<nsISeekableStream> postDataSeekable =
+ do_QueryInterface(aLoadState->PostDataStream());
+ if (postDataSeekable) {
+ aRv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+
+ // we really need to have a content type associated with this stream!!
+ postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1);
+ }
+
+ /* If there is a valid postdata *and* it is a History Load,
+ * set up the cache key on the channel, to retrieve the
+ * data *only* from the cache. If it is a normal reload, the
+ * cache is free to go to the server for updated postdata.
+ */
+ if (cacheChannel && aCacheKey != 0) {
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) {
+ channel->SetLoadFlags(loadFlags |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ }
+ } else if (loadType == LOAD_RELOAD_NORMAL) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ } else {
+ /* If there is no postdata, set the cache key on the channel, and
+ * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel
+ * will be free to get it from net if it is not found in cache.
+ * New cache may use it creatively on CGI pages with GET
+ * method and even on those that say "no-cache"
+ */
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
+ if (cacheChannel && aCacheKey != 0) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ }
+
+ if (nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel)) {
+ // Allow execution against our context if the principals match
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ }
+
+ if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
+ timedChannel->SetTimingEnabled(true);
+
+ nsString initiatorType;
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ initiatorType = u"embed"_ns;
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ initiatorType = u"object"_ns;
+ break;
+ default: {
+ const auto& embedderElementType =
+ aBrowsingContext->GetEmbedderElementType();
+ if (embedderElementType) {
+ initiatorType = *embedderElementType;
+ }
+ break;
+ }
+ }
+
+ if (!initiatorType.IsEmpty()) {
+ timedChannel->SetInitiatorType(initiatorType);
+ }
+ }
+
+ if (httpChannelInternal && aBrowsingContext->GetSandboxFlags() != 0) {
+ httpChannelInternal->SetHasNonEmptySandboxingFlag(true);
+ }
+
+ nsCOMPtr<nsIURI> rpURI;
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(rpURI));
+ Maybe<nsCOMPtr<nsIURI>> originalResultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(originalResultPrincipalURI);
+ if (originalResultPrincipalURI &&
+ (!aLoadState->KeepResultPrincipalURIIfSet() || !rpURI)) {
+ // Unconditionally override, we want the replay to be equal to what has
+ // been captured.
+ aLoadInfo->SetResultPrincipalURI(originalResultPrincipalURI.ref());
+ }
+
+ if (aLoadState->OriginalURI() && aLoadState->LoadReplace()) {
+ // The LOAD_REPLACE flag and its handling here will be removed as part
+ // of bug 1319110. For now preserve its restoration here to not break
+ // any code expecting it being set specially on redirected channels.
+ // If the flag has originally been set to change result of
+ // NS_GetFinalChannelURI it won't have any effect and also won't cause
+ // any harm.
+ uint32_t loadFlags;
+ aRv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(aRv, false);
+ channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
+ if (csp) {
+ // Navigational requests that are same origin need to be upgraded in case
+ // upgrade-insecure-requests is present. Please note that for document
+ // navigations that bit is re-computed in case we encounter a server
+ // side redirect so the navigation is not same-origin anymore.
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ // only upgrade if the navigation is same origin
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ aRv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ channel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ aLoadState->TriggeringPrincipal(), resultPrincipal)) {
+ aLoadInfo->SetUpgradeInsecureRequests(true);
+ }
+ }
+
+ // For document loads we store the CSP that potentially needs to
+ // be inherited by the new document, e.g. in case we are loading
+ // an opaque origin like a data: URI. The actual inheritance
+ // check happens within Document::InitCSP().
+ // Please create an actual copy of the CSP (do not share the same
+ // reference) otherwise a Meta CSP of an opaque origin will
+ // incorrectly be propagated to the embedding document.
+ RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
+ aLoadInfo->SetCSPToInherit(cspToInherit);
+ }
+
+ channel.forget(aChannel);
+ return true;
+}
+
+nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey,
+ nsIRequest** aRequest) {
+ // Double-check that we're still around to load this URI.
+ if (mIsBeingDestroyed) {
+ // Return NS_OK despite not doing anything to avoid throwing exceptions
+ // from nsLocation::SetHref if the unload handler of the existing page
+ // tears us down.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURILoader> uriLoader = components::URILoader::Service();
+ if (NS_WARN_IF(!uriLoader)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ nsContentPolicyType contentPolicyType = DetermineContentType();
+
+ if (IsFrame()) {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME,
+ "DoURILoad thinks this is a frame and InternalLoad does not");
+
+ if (StaticPrefs::dom_block_external_protocol_in_iframes()) {
+ // Only allow URLs able to return data in iframes.
+ bool doesNotReturnData = false;
+ NS_URIChainHasFlags(aLoadState->URI(),
+ nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
+ &doesNotReturnData);
+ if (doesNotReturnData) {
+ // The context to check user-interaction with for the purposes of
+ // popup-blocking.
+ //
+ // We generally want to check the context that initiated the navigation.
+ WindowContext* sourceWindowContext = [&] {
+ const MaybeDiscardedBrowsingContext& sourceBC =
+ aLoadState->SourceBrowsingContext();
+ if (!sourceBC.IsNullOrDiscarded()) {
+ if (WindowContext* wc = sourceBC.get()->GetCurrentWindowContext()) {
+ return wc;
+ }
+ }
+ return mBrowsingContext->GetParentWindowContext();
+ }();
+
+ MOZ_ASSERT(sourceWindowContext);
+ // FIXME: We can't check user-interaction against an OOP window. This is
+ // the next best thing we can really do. The load state keeps whether
+ // the navigation had a user interaction in process
+ // (aLoadState->HasValidUserGestureActivation()), but we can't really
+ // consume it, which we want to prevent popup-spamming from the same
+ // click event.
+ WindowContext* context =
+ sourceWindowContext->IsInProcess()
+ ? sourceWindowContext
+ : mBrowsingContext->GetCurrentWindowContext();
+ const bool popupBlocked = [&] {
+ const bool active = mBrowsingContext->IsActive();
+
+ // For same-origin-with-top windows, we grant a single free popup
+ // without user activation, see bug 1680721.
+ //
+ // We consume the flag now even if there's no user activation.
+ const bool hasFreePass = [&] {
+ if (!active || !context->SameOriginWithTop()) {
+ return false;
+ }
+ nsGlobalWindowInner* win =
+ context->TopWindowContext()->GetInnerWindow();
+ return win && win->TryOpenExternalProtocolIframe();
+ }();
+
+ if (context->ConsumeTransientUserGestureActivation()) {
+ // If the user has interacted with the page, consume it.
+ return false;
+ }
+
+ // TODO(emilio): Can we remove this check? It seems like what prompted
+ // this code (bug 1514547) should be covered by transient user
+ // activation, see bug 1514547.
+ if (active &&
+ PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
+ return false;
+ }
+
+ if (sourceWindowContext->CanShowPopup()) {
+ return false;
+ }
+
+ if (hasFreePass) {
+ return false;
+ }
+
+ return true;
+ }();
+
+ // No error must be returned when iframes are blocked.
+ if (popupBlocked) {
+ nsAutoString message;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES,
+ "ExternalProtocolFrameBlockedNoUserActivation", message);
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ message, nsIScriptError::warningFlag, "DOM"_ns,
+ context->InnerWindowId());
+ }
+ return NS_OK;
+ }
+ }
+ }
+
+ // Only allow view-source scheme in top-level docshells. view-source is
+ // the only scheme to which this applies at the moment due to potential
+ // timing attacks to read data from cross-origin iframes. If this widens
+ // we should add a protocol flag for whether the scheme is allowed in
+ // frames and use something like nsNetUtil::NS_URIChainHasFlags.
+ nsCOMPtr<nsIURI> tempURI = aLoadState->URI();
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
+ while (nestedURI) {
+ // view-source should always be an nsINestedURI, loop and check the
+ // scheme on this and all inner URIs that are also nested URIs.
+ if (SchemeIsViewSource(tempURI)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+ nestedURI->GetInnerURI(getter_AddRefs(tempURI));
+ nestedURI = do_QueryInterface(tempURI);
+ }
+ } else {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "DoURILoad thinks this is a document and InternalLoad does not");
+ }
+
+ // FIXME We still have a ton of codepaths that don't pass through
+ // DocumentLoadListener, so probably need to create session history info
+ // in more places.
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ SetLoadingSessionHistoryInfo(*aLoadState->GetLoadingSessionHistoryInfo());
+ }
+
+ // open a channel for the url
+
+ // If we have a pending channel, use the channel we've already created here.
+ // We don't need to set up load flags for our channel, as it has already been
+ // created.
+
+ if (nsCOMPtr<nsIChannel> channel =
+ aLoadState->GetPendingRedirectedChannel()) {
+ // If we have a request outparameter, shove our channel into it.
+ if (aRequest) {
+ nsCOMPtr<nsIRequest> outRequest = channel;
+ outRequest.forget(aRequest);
+ }
+
+ return OpenRedirectedChannel(aLoadState);
+ }
+
+ // There are two cases we care about:
+ // * Top-level load: In this case, loadingNode is null, but loadingWindow
+ // is our mScriptGlobal. We pass null for loadingPrincipal in this case.
+ // * Subframe load: loadingWindow is null, but loadingNode is the frame
+ // element for the load. loadingPrincipal is the NodePrincipal of the
+ // frame element.
+ nsCOMPtr<nsINode> loadingNode;
+ nsCOMPtr<nsPIDOMWindowOuter> loadingWindow;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ nsCOMPtr<nsISupports> topLevelLoadingContext;
+
+ if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ loadingNode = nullptr;
+ loadingPrincipal = nullptr;
+ loadingWindow = mScriptGlobal;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process).
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ topLevelLoadingContext = ToSupports(browserChild);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ loadingWindow->GetFrameElementInternal();
+ topLevelLoadingContext = requestingElement;
+ }
+ } else {
+ loadingWindow = nullptr;
+ loadingNode = mScriptGlobal->GetFrameElementInternal();
+ if (loadingNode) {
+ // If we have a loading node, then use that as our loadingPrincipal.
+ loadingPrincipal = loadingNode->NodePrincipal();
+#ifdef DEBUG
+ // Get the docshell type for requestingElement.
+ RefPtr<Document> requestingDoc = loadingNode->OwnerDoc();
+ nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();
+ // requestingElement docshell type = current docshell type.
+ MOZ_ASSERT(
+ mItemType == elementDocShell->ItemType(),
+ "subframes should have the same docshell type as their parent");
+#endif
+ } else {
+ if (mIsBeingDestroyed) {
+ // If this isn't a top-level load and mScriptGlobal's frame element is
+ // null, then the element got removed from the DOM while we were trying
+ // to load this resource. This docshell is scheduled for destruction
+ // already, so bail out here.
+ return NS_OK;
+ }
+ // If we are not being destroyed and we do not have access to the loading
+ // node, then we are a remote subframe. Set the loading principal
+ // to be a null principal and then set it correctly in the parent.
+ loadingPrincipal = NullPrincipal::Create(GetOriginAttributes(), nullptr);
+ }
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "DoURILoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+
+ // We want to inherit aLoadState->PrincipalToInherit() when:
+ // 1. ChannelShouldInheritPrincipal returns true.
+ // 2. aLoadState->URI() is not data: URI, or data: URI is not
+ // configured as unique opaque origin.
+ bool inheritPrincipal = false;
+
+ if (aLoadState->PrincipalToInherit()) {
+ bool isSrcdoc = aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+
+ inheritPrincipal = inheritAttrs && !SchemeIsData(aLoadState->URI());
+ }
+
+ uint32_t sandboxFlags = mBrowsingContext->GetSandboxFlags();
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
+ }
+
+ if (inheritPrincipal) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ // Must never have a parent for TYPE_DOCUMENT loads
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ !mBrowsingContext->GetParent());
+ // Subdocuments must have a parent
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT,
+ mBrowsingContext->GetParent());
+ mBrowsingContext->SetTriggeringAndInheritPrincipals(
+ aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
+ aLoadState->GetLoadIdentifier());
+ RefPtr<LoadInfo> loadInfo =
+ (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT)
+ ? new LoadInfo(loadingWindow, aLoadState->TriggeringPrincipal(),
+ topLevelLoadingContext, securityFlags, sandboxFlags)
+ : new LoadInfo(loadingPrincipal, aLoadState->TriggeringPrincipal(),
+ loadingNode, securityFlags, contentPolicyType,
+ Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(),
+ sandboxFlags);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ if (mLoadType != LOAD_ERROR_PAGE && context &&
+ context->HasValidTransientUserGestureActivation()) {
+ aLoadState->SetHasValidUserGestureActivation(true);
+ }
+
+ // in case this docshell load was triggered by a valid transient user gesture,
+ // or also the load originates from external, then we pass that information on
+ // to the loadinfo, which allows e.g. setting Sec-Fetch-User request headers.
+ if (aLoadState->HasValidUserGestureActivation() ||
+ aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) {
+ loadInfo->SetHasValidUserGestureActivation(true);
+ }
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+
+ uint32_t cacheKey = 0;
+ if (aCacheKey) {
+ cacheKey = *aCacheKey;
+ } else if (mozilla::SessionHistoryInParent()) {
+ if (mLoadingEntry) {
+ cacheKey = mLoadingEntry->mInfo.GetCacheKey();
+ } else if (mActiveEntry) { // for reload cases
+ cacheKey = mActiveEntry->GetCacheKey();
+ }
+ } else {
+ if (mLSHE) {
+ cacheKey = mLSHE->GetCacheKey();
+ } else if (mOSHE) { // for reload cases
+ cacheKey = mOSHE->GetCacheKey();
+ }
+ }
+
+ bool uriModified;
+ if (mLSHE || mLoadingEntry) {
+ if (mLoadingEntry) {
+ uriModified = mLoadingEntry->mInfo.GetURIWasModified();
+ } else {
+ uriModified = mLSHE->GetURIWasModified();
+ }
+ } else {
+ uriModified = false;
+ }
+
+ bool isXFOError = false;
+ if (mFailedChannel) {
+ nsresult status;
+ mFailedChannel->GetStatus(&status);
+ isXFOError = status == NS_ERROR_XFO_VIOLATION;
+ }
+
+ nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
+ mBrowsingContext, Some(uriModified), Some(isXFOError));
+
+ nsCOMPtr<nsIChannel> channel;
+ if (DocumentChannel::CanUseDocumentChannel(aLoadState->URI())) {
+ channel = DocumentChannel::CreateForDocument(aLoadState, loadInfo,
+ loadFlags, this, cacheKey,
+ uriModified, isXFOError);
+ MOZ_ASSERT(channel);
+
+ // Disable keyword fixup when using DocumentChannel, since
+ // DocumentLoadListener will handle this for us (in the parent process).
+ mAllowKeywordFixup = false;
+ } else if (!CreateAndConfigureRealChannelForLoadState(
+ mBrowsingContext, aLoadState, loadInfo, this, this,
+ GetOriginAttributes(), loadFlags, cacheKey, rv,
+ getter_AddRefs(channel))) {
+ return rv;
+ }
+
+ // Make sure to give the caller a channel if we managed to create one
+ // This is important for correct error page/session history interaction
+ if (aRequest) {
+ NS_ADDREF(*aRequest = channel);
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
+ if (csp) {
+ // Check CSP navigate-to
+ bool allowsNavigateTo = false;
+ rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
+ aLoadState->IsFormSubmission(),
+ false, /* aWasRedirected */
+ false, /* aEnforceWhitelist */
+ &allowsNavigateTo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!allowsNavigateTo) {
+ return NS_ERROR_CSP_NAVIGATE_TO_VIOLATION;
+ }
+ }
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ mContentTypeHint = typeHint;
+ } else {
+ mContentTypeHint.Truncate();
+ }
+
+ // Load attributes depend on load type...
+ if (mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we
+ // only want to force cache load for this channel, not the whole
+ // loadGroup.
+ nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel);
+ if (cachingChannel) {
+ cachingChannel->SetAllowStaleCacheContent(true);
+ }
+ }
+
+ uint32_t openFlags =
+ nsDocShell::ComputeURILoaderFlags(mBrowsingContext, mLoadType);
+ return OpenInitializedChannel(channel, uriLoader, openFlags);
+}
+
+static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ // aFromSegment now contains aCount bytes of data.
+
+ nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure);
+ buf->Append(aFromRawSegment, aCount);
+
+ // Indicate that we have consumed all of aFromSegment
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+/* static */ nsresult nsDocShell::AddHeadersToChannel(
+ nsIInputStream* aHeadersData, nsIChannel* aGenericChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ uint32_t numRead;
+ nsAutoCString headersString;
+ nsresult rv = aHeadersData->ReadSegments(
+ AppendSegmentToString, &headersString, UINT32_MAX, &numRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // used during the manipulation of the String from the InputStream
+ nsAutoCString headerName;
+ nsAutoCString headerValue;
+ int32_t crlf;
+ int32_t colon;
+
+ //
+ // Iterate over the headersString: for each "\r\n" delimited chunk,
+ // add the value as a header to the nsIHttpChannel
+ //
+
+ static const char kWhitespace[] = "\b\t\r\n ";
+ while (true) {
+ crlf = headersString.Find("\r\n");
+ if (crlf == kNotFound) {
+ return NS_OK;
+ }
+
+ const nsACString& oneHeader = StringHead(headersString, crlf);
+
+ colon = oneHeader.FindChar(':');
+ if (colon == kNotFound) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ headerName = StringHead(oneHeader, colon);
+ headerValue = Substring(oneHeader, colon + 1);
+
+ headerName.Trim(kWhitespace);
+ headerValue.Trim(kWhitespace);
+
+ headersString.Cut(0, crlf + 2);
+
+ //
+ // FINALLY: we can set the header!
+ //
+
+ rv = httpChannel->SetRequestHeader(headerName, headerValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("oops");
+ return NS_ERROR_UNEXPECTED;
+}
+
+/* static */ uint32_t nsDocShell::ComputeURILoaderFlags(
+ BrowsingContext* aBrowsingContext, uint32_t aLoadType) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ uint32_t openFlags = 0;
+ if (aLoadType == LOAD_LINK) {
+ openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
+ }
+ if (!aBrowsingContext->GetAllowContentRetargeting()) {
+ openFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ return openFlags;
+}
+
+void nsDocShell::UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel) {
+ if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT ||
+ mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+ SetMixedContentChannel(aChannel);
+ } else if (mMixedContentChannel) {
+ /*
+ * If the user "Disables Protection on This Page", we call
+ * SetMixedContentChannel for the first time, otherwise
+ * mMixedContentChannel is still null.
+ * Later, if the new channel passes a same orign check, we remember the
+ * users decision by calling SetMixedContentChannel using the new channel.
+ * This way, the user does not have to click the disable protection button
+ * over and over for browsing the same site.
+ */
+ nsresult rv =
+ nsContentUtils::CheckSameOrigin(mMixedContentChannel, aChannel);
+ if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(aChannel))) {
+ SetMixedContentChannel(nullptr);
+ }
+ }
+}
+
+nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags) {
+ nsresult rv = NS_OK;
+
+ UpdateMixedContentChannelForNewLoad(aChannel);
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ // Let the client channel helper know if we are using DocumentChannel,
+ // since redirects get handled in the parent process in that case.
+ RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aChannel);
+ if (docChannel && XRE_IsContentProcess()) {
+ // Tell the content process nsDocumentOpenInfo to not try to do
+ // any sort of targeting.
+ aOpenFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ // Since we are loading a document we need to make sure the proper reserved
+ // and initial client data is stored on the nsILoadInfo. The
+ // ClientChannelHelper does this and ensures that it is propagated properly
+ // on redirects. We pass no reserved client here so that the helper will
+ // create the reserved ClientSource if necessary.
+ Maybe<ClientInfo> noReservedClient;
+ if (docChannel) {
+ // When using DocumentChannel, all redirect handling is done in the parent,
+ // so we just need the child variant to watch for the internal redirect
+ // to the final channel.
+ rv = AddClientChannelHelperInChild(
+ aChannel, win->EventTargetFor(TaskCategory::Other));
+ docChannel->SetInitialClientInfo(GetInitialClientInfo());
+ } else {
+ rv = AddClientChannelHelper(aChannel, std::move(noReservedClient),
+ GetInitialClientInfo(),
+ win->EventTargetFor(TaskCategory::Other));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aURILoader->OpenURI(aChannel, aOpenFlags, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're about to load a new page and it may take time before necko
+ // gives back any data, so main thread might have a chance to process a
+ // collector slice
+ nsJSContext::MaybeRunNextCollectorSlice(this, JS::GCReason::DOCSHELL);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
+ nsCOMPtr<nsIChannel> channel = aLoadState->GetPendingRedirectedChannel();
+ MOZ_ASSERT(channel);
+
+ UpdateMixedContentChannelForNewLoad(channel);
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ li->UpdateBrowsingContextID(mBrowsingContext->Id());
+ } else if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
+ }
+ // TODO: more attributes need to be updated on the LoadInfo (bug 1561706)
+
+ // If we did a process switch, then we should have an existing allocated
+ // ClientInfo, so we just need to allocate a corresponding ClientSource.
+ CreateReservedSourceIfNeeded(channel,
+ win->EventTargetFor(TaskCategory::Other));
+
+ RefPtr<nsDocumentOpenInfo> loader =
+ new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr);
+ channel->SetLoadGroup(mLoadGroup);
+
+ MOZ_ALWAYS_SUCCEEDS(loader->Prepare());
+
+ nsresult rv = NS_OK;
+ if (XRE_IsParentProcess()) {
+ // If we're in the parent, the we don't have an nsIChildChannel, just
+ // the original channel, which is already open in this process.
+
+ // DocumentLoadListener expects to get an nsIParentChannel, so
+ // we create a wrapper around the channel and nsIStreamListener
+ // that forwards functionality as needed, and then we register
+ // it under the provided identifier.
+ RefPtr<ParentChannelWrapper> wrapper =
+ new ParentChannelWrapper(channel, loader);
+ wrapper->Register(aLoadState->GetPendingRedirectChannelRegistrarId());
+
+ mLoadGroup->AddRequest(channel, nullptr);
+ } else if (nsCOMPtr<nsIChildChannel> childChannel =
+ do_QueryInterface(channel)) {
+ // Our channel was redirected from another process, so doesn't need to
+ // be opened again. However, it does need its listener hooked up
+ // correctly.
+ rv = childChannel->CompleteRedirectSetup(loader);
+ } else {
+ // It's possible for the redirected channel to not implement
+ // nsIChildChannel and be entirely local (like srcdoc). In that case we
+ // can just open the local instance and it will work.
+ rv = channel->AsyncOpen(loader);
+ }
+ if (rv == NS_ERROR_NO_CONTENT) {
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+ return NS_OK;
+}
+
+nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType) {
+ if (!mCurrentURI) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ // If we failed to get the shell, or if there is no shell,
+ // nothing left to do here.
+ return NS_OK;
+ }
+
+ nsIScrollableFrame* rootScroll = presShell->GetRootScrollFrameAsScrollable();
+ if (rootScroll) {
+ rootScroll->ClearDidHistoryRestore();
+ }
+
+ // If we have no new anchor, we do not want to scroll, unless there is a
+ // current anchor and we are doing a history load. So return if we have no
+ // new anchor, and there is no current anchor or the load is not a history
+ // load.
+ if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) {
+ return NS_OK;
+ }
+
+ // Both the new and current URIs refer to the same page. We can now
+ // browse to the hash stored in the new URI.
+
+ if (!aNewHash.IsEmpty()) {
+ // anchor is there, but if it's a load from history,
+ // we don't have any anchor jumping to do
+ bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL;
+
+ // We assume that the bytes are in UTF-8, as it says in the
+ // spec:
+ // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
+
+ // We try the UTF-8 string first, and then try the document's
+ // charset (see below). If the string is not UTF-8,
+ // conversion will fail and give us an empty Unicode string.
+ // In that case, we should just fall through to using the
+ // page's charset.
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_ConvertUTF8toUTF16 uStr(aNewHash);
+ if (!uStr.IsEmpty()) {
+ rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
+ }
+
+ if (NS_FAILED(rv)) {
+ char* str = ToNewCString(aNewHash, mozilla::fallible);
+ if (!str) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsUnescape(str);
+ NS_ConvertUTF8toUTF16 utf16Str(str);
+ if (!utf16Str.IsEmpty()) {
+ rv = presShell->GoToAnchor(utf16Str, scroll,
+ ScrollFlags::ScrollSmoothAuto);
+ }
+ free(str);
+ }
+
+ // Above will fail if the anchor name is not UTF-8. Need to
+ // convert from document charset to unicode.
+ if (NS_FAILED(rv)) {
+ // Get a document charset
+ NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
+ Document* doc = mContentViewer->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ nsAutoCString charset;
+ doc->GetDocumentCharacterSet()->Name(charset);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unescape and convert to unicode
+ nsAutoString uStr;
+
+ rv = textToSubURI->UnEscapeAndConvert(charset, aNewHash, uStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignore return value of GoToAnchor, since it will return an error
+ // if there is no such anchor in the document, which is actually a
+ // success condition for us (we want to update the session history
+ // with the new URI no matter whether we actually scrolled
+ // somewhere).
+ //
+ // When aNewHash contains "%00", unescaped string may be empty.
+ // And GoToAnchor asserts if we ask it to scroll to an empty ref.
+ presShell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(),
+ ScrollFlags::ScrollSmoothAuto);
+ }
+ } else {
+ // Tell the shell it's at an anchor, without scrolling.
+ presShell->GoToAnchor(u""_ns, false);
+
+ // An empty anchor was found, but if it's a load from history,
+ // we don't have to jump to the top of the page. Scrollbar
+ // position will be restored by the caller, based on positions
+ // stored in session history.
+ if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) {
+ return NS_OK;
+ }
+ // An empty anchor. Scroll to the top of the page. Ignore the
+ // return value; failure to scroll here (e.g. if there is no
+ // root scrollframe) is not grounds for canceling the load!
+ SetCurScrollPosEx(0, 0);
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::SetupReferrerInfoFromChannel(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
+ SetReferrerInfo(referrerInfo);
+ }
+}
+
+bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aFireOnLocationChange, bool aAddToGlobalHistory,
+ bool aCloneSHChildren) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+
+ MOZ_ASSERT(!aPrincipalToInherit ||
+ (aPrincipalToInherit && aTriggeringPrincipal));
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get(), mLoadType));
+ }
+#endif
+
+ bool equalUri = false;
+
+ // Get the post data and the HTTP response code from the channel.
+ uint32_t responseStatus = 0;
+ nsCOMPtr<nsIInputStream> inputStream;
+ if (aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the HTTPChannel is hiding under a multiPartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+
+ // If the response status indicates an error, unlink this session
+ // history entry from any entries sharing its document.
+ nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
+ if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
+ mLSHE->AbandonBFCacheEntry();
+ // FIXME Do the same for mLoadingEntry
+ }
+ }
+ }
+
+ // Determine if this type of load should update history.
+ bool updateGHistory = ShouldUpdateGlobalHistory(mLoadType);
+
+ // We don't update session history on reload unless we're loading
+ // an iframe in shift-reload case.
+ bool updateSHistory = mBrowsingContext->ShouldUpdateSessionHistory(mLoadType);
+
+ // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
+ // root browsing context.
+ // FIXME If session history in the parent is enabled then we only do this if
+ // the session history object is in process, otherwise we can't really
+ // use the mLSHE anyway. Once session history is only stored in the
+ // parent then this code will probably be removed anyway.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (!rootSH) {
+ updateSHistory = false;
+ updateGHistory = false; // XXX Why global history too?
+ }
+
+ // Check if the url to be loaded is the same as the one already loaded.
+ if (mCurrentURI) {
+ aURI->Equals(mCurrentURI, &equalUri);
+ }
+
+#ifdef DEBUG
+ bool shAvailable = (rootSH != nullptr);
+
+ // XXX This log message is almost useless because |updateSHistory|
+ // and |updateGHistory| are not correct at this point.
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ (" shAvailable=%i updateSHistory=%i updateGHistory=%i"
+ " equalURI=%i\n",
+ shAvailable, updateSHistory, updateGHistory, equalUri));
+#endif
+
+ /* If the url to be loaded is the same as the one already there,
+ * and the original loadType is LOAD_NORMAL, LOAD_LINK, or
+ * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that
+ * AddToSessionHistory() won't mess with the current SHEntry and
+ * if this page has any frame children, it also will be handled
+ * properly. see bug 83684
+ *
+ * NB: If mOSHE is null but we have a current URI, then it probably
+ * means that we must be at the transient about:blank content viewer;
+ * we should let the normal load continue, since there's nothing to
+ * replace. Sometimes this happens after a session restore (eg process
+ * switch) and mCurrentURI is not about:blank; we assume we can let the load
+ * continue (Bug 1301399).
+ *
+ * XXX Hopefully changing the loadType at this time will not hurt
+ * anywhere. The other way to take care of sequentially repeating
+ * frameset pages is to add new methods to nsIDocShellTreeItem.
+ * Hopefully I don't have to do that.
+ */
+ if (equalUri &&
+ (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (mLoadType == LOAD_NORMAL || mLoadType == LOAD_LINK ||
+ mLoadType == LOAD_STOP_CONTENT) &&
+ !inputStream) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // If this is a refresh to the currently loaded url, we don't
+ // have to update session or global history.
+ if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(mOSHE), Nothing());
+ }
+
+ /* If the user pressed shift-reload, cache will create a new cache key
+ * for the page. Save the new cacheKey in Session History.
+ * see bug 90098
+ */
+ if (aChannel && IsForceReloadType(mLoadType)) {
+ MOZ_ASSERT(!updateSHistory || IsFrame(),
+ "We shouldn't be updating session history for forced"
+ " reloads unless we're in a newly created iframe!");
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel));
+ uint32_t cacheKey = 0;
+ // Get the Cache Key and store it in SH.
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ // If we already have a loading history entry, store the new cache key
+ // in it. Otherwise, since we're doing a reload and won't be updating
+ // our history entry, store the cache key in our current history entry.
+ SetCacheKeyOnHistoryEntry(mLSHE ? mLSHE : mOSHE, cacheKey);
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Since we're force-reloading, clear all the sub frame history.
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Clear subframe history on refresh.
+ // XXX: history.go(0) won't go this path as mLoadType is LOAD_HISTORY in
+ // this case. One should re-validate after bug 1331865 fixed.
+ if (mLoadType == LOAD_REFRESH) {
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+
+ if (updateSHistory) {
+ // Update session history if necessary...
+ if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) {
+ /* This is a fresh page getting loaded for the first time
+ *.Create a Entry for it and add it to SH, if this is the
+ * rootDocShell
+ */
+ (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
+ aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp,
+ aCloneSHChildren, getter_AddRefs(mLSHE));
+ }
+ } else if (GetSessionHistory() && mLSHE && mURIResultedInDocument) {
+ // Even if we don't add anything to SHistory, ensure the current index
+ // points to the same SHEntry as our mLSHE.
+
+ GetSessionHistory()->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(
+ mLSHE);
+ }
+ }
+
+ // If this is a POST request, we do not want to include this in global
+ // history.
+ if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory &&
+ !net::ChannelIsPost(aChannel)) {
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+
+ if (mLoadType & LOAD_CMD_RELOAD) {
+ // On a reload request, we don't set redirecting flags.
+ previousURI = aURI;
+ } else {
+ ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags);
+ }
+
+ AddURIVisit(aURI, previousURI, previousFlags, responseStatus);
+ }
+
+ // If this was a history load or a refresh, or it was a history load but
+ // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index
+ // in session history.
+ if (!mozilla::SessionHistoryInParent() && rootSH &&
+ ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) ||
+ mLoadType == LOAD_NORMAL_REPLACE)) {
+ mPreviousEntryIndex = rootSH->Index();
+ if (!mozilla::SessionHistoryInParent()) {
+ rootSH->LegacySHistory()->UpdateIndex();
+ }
+ mLoadedEntryIndex = rootSH->Index();
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex,
+ mLoadedEntryIndex));
+ }
+
+ // aCloneSHChildren exactly means "we are not loading a new document".
+ uint32_t locationFlags =
+ aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0;
+
+ bool onLocationChangeNeeded =
+ SetCurrentURI(aURI, aChannel, aFireOnLocationChange, locationFlags);
+ // Make sure to store the referrer from the channel, if any
+ SetupReferrerInfoFromChannel(aChannel);
+ return onLocationChangeNeeded;
+}
+
+void nsDocShell::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo; // This assigment addrefs
+}
+
+//*****************************************************************************
+// nsDocShell: Session History
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
+ const nsAString& aURL, bool aReplace, JSContext* aCx) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell[%p]: AddState(..., %s, %s, %d)", this,
+ NS_ConvertUTF16toUTF8(aTitle).get(),
+ NS_ConvertUTF16toUTF8(aURL).get(), aReplace));
+ // Implements History.pushState and History.replaceState
+
+ // Here's what we do, roughly in the order specified by HTML5. The specific
+ // steps we are executing are at
+ // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate>
+ // and
+ // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>.
+ // This function basically implements #dom-history-pushstate and
+ // UpdateURLAndHistory implements #url-and-history-update-steps.
+ //
+ // A. Serialize aData using structured clone. This is #dom-history-pushstate
+ // step 5.
+ // B. If the third argument is present, #dom-history-pushstate step 7.
+ // 7.1. Resolve the url, relative to our document.
+ // 7.2. If (a) fails, raise a SECURITY_ERR
+ // 7.4. Compare the resulting absolute URL to the document's address. If
+ // any part of the URLs difer other than the <path>, <query>, and
+ // <fragment> components, raise a SECURITY_ERR and abort.
+ // C. If !aReplace, #url-and-history-update-steps steps 2.1-2.3:
+ // Remove from the session history all entries after the current entry,
+ // as we would after a regular navigation, and save the current
+ // entry's scroll position (bug 590573).
+ // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate,
+ // either add a state object entry to the session history after the
+ // current entry with the following properties, or modify the current
+ // session history entry to set
+ // a. cloned data as the state object,
+ // b. if the third argument was present, the absolute URL found in
+ // step 2
+ // Also clear the new history entry's POST data (see bug 580069).
+ // E. If aReplace is false (i.e. we're doing a pushState instead of a
+ // replaceState), notify bfcache that we've navigated to a new page.
+ // F. If the third argument is present, set the document's current address
+ // to the absolute URL found in step B. This is
+ // #url-and-history-update-steps step 4.
+ //
+ // It's important that this function not run arbitrary scripts after step A
+ // and before completing step E. For example, if a script called
+ // history.back() before we completed step E, bfcache might destroy an
+ // active content viewer. Since EvictOutOfRangeContentViewers at the end of
+ // step E might run script, we can't just put a script blocker around the
+ // critical section.
+ //
+ // Note that we completely ignore the aTitle parameter.
+
+ nsresult rv;
+
+ // Don't clobber the load type of an existing network load.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // pushState effectively becomes replaceState when we've started a network
+ // load but haven't adopted its document yet. This mirrors what we do with
+ // changes to the hash at this stage of the game.
+ if (JustStartedNetworkLoad()) {
+ aReplace = true;
+ }
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ // Step A: Serialize aData using structured clone.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 5.
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+
+ // scContainer->Init might cause arbitrary JS to run, and this code might
+ // navigate the page we're on, potentially to a different origin! (bug
+ // 634834) To protect against this, we abort if our principal changes due
+ // to the InitFromJSVal() call.
+ {
+ RefPtr<Document> origDocument = GetDocument();
+ if (!origDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal();
+
+ scContainer = new nsStructuredCloneContainer();
+ rv = scContainer->InitFromJSVal(aData, aCx);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Document> newDocument = GetDocument();
+ if (!newDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal();
+
+ bool principalsEqual = false;
+ origPrincipal->Equals(newPrincipal, &principalsEqual);
+ NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ // Check that the state object isn't too long.
+ // Default max length: 2097152 (0x200000) bytes.
+ int32_t maxStateObjSize =
+ Preferences::GetInt("browser.history.maxStateObjectSize", 2097152);
+ if (maxStateObjSize < 0) {
+ maxStateObjSize = 0;
+ }
+
+ uint64_t scSize;
+ rv = scContainer->GetSerializedNBytes(&scSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE);
+
+ // Step B: Resolve aURL.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 7.
+ bool equalURIs = true;
+ nsCOMPtr<nsIURI> currentURI;
+ if (mCurrentURI) {
+ currentURI = nsIOService::CreateExposableURI(mCurrentURI);
+ } else {
+ currentURI = mCurrentURI;
+ }
+ nsCOMPtr<nsIURI> newURI;
+ if (aURL.Length() == 0) {
+ newURI = currentURI;
+ } else {
+ // 7.1: Resolve aURL relative to mURI
+
+ nsIURI* docBaseURI = document->GetDocBaseURI();
+ if (!docBaseURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString spec;
+ docBaseURI->GetSpec(spec);
+
+ rv = NS_NewURI(getter_AddRefs(newURI), aURL,
+ document->GetDocumentCharacterSet(), docBaseURI);
+
+ // 7.2: If 2a fails, raise a SECURITY_ERR
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // 7.4 and 7.5: Same-origin check.
+ if (!nsContentUtils::URIIsLocalFile(newURI)) {
+ // In addition to checking that the security manager says that
+ // the new URI has the same origin as our current URI, we also
+ // check that the two URIs have the same userpass. (The
+ // security manager says that |http://foo.com| and
+ // |http://me@foo.com| have the same origin.) currentURI
+ // won't contain the password part of the userpass, so this
+ // means that it's never valid to specify a password in a
+ // pushState or replaceState URI.
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+ // It's very important that we check that newURI is of the same
+ // origin as currentURI, not docBaseURI, because a page can
+ // set docBaseURI arbitrarily to any domain.
+ nsAutoCString currentUserPass, newUserPass;
+ NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE);
+ bool isPrivateWin =
+ document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId >
+ 0;
+ if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true,
+ isPrivateWin)) ||
+ !currentUserPass.Equals(newUserPass)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ } else {
+ // It's a file:// URI
+ nsCOMPtr<nsIPrincipal> principal = document->GetPrincipal();
+
+ if (!principal || NS_FAILED(principal->CheckMayLoadWithReporting(
+ newURI, false, document->InnerWindowID()))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ if (currentURI) {
+ currentURI->Equals(newURI, &equalURIs);
+ } else {
+ equalURIs = false;
+ }
+
+ } // end of same-origin check
+
+ // Step 8: call "URL and history update steps"
+ rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace,
+ currentURI, equalURIs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI,
+ nsIStructuredCloneContainer* aData,
+ const nsAString& aTitle, bool aReplace,
+ nsIURI* aCurrentURI, bool aEqualURIs) {
+ // Implements
+ // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
+
+ // Step 2, if aReplace is false: Create a new entry in the session
+ // history. This will erase all SHEntries after the new entry and make this
+ // entry the current one. This operation may modify mOSHE, which we need
+ // later, so we keep a reference here.
+ NS_ENSURE_TRUE(mOSHE || mActiveEntry || aReplace, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
+
+ // If this push/replaceState changed the document's current URI and the new
+ // URI differs from the old URI in more than the hash, or if the old
+ // SHEntry's URI was modified in this way by a push/replaceState call
+ // set URIWasModified to true for the current SHEntry (bug 669671).
+ bool sameExceptHashes = true;
+ aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes);
+ bool uriWasModified;
+ if (sameExceptHashes) {
+ if (mozilla::SessionHistoryInParent()) {
+ uriWasModified = mActiveEntry && mActiveEntry->GetURIWasModified();
+ } else {
+ uriWasModified = oldOSHE && oldOSHE->GetURIWasModified();
+ }
+ } else {
+ uriWasModified = true;
+ }
+
+ mLoadType = LOAD_PUSHSTATE;
+
+ nsCOMPtr<nsISHEntry> newSHEntry;
+ if (!aReplace) {
+ // Step 2.
+
+ // Step 2.2, "Remove any tasks queued by the history traversal task
+ // source that are associated with any Document objects in the
+ // top-level browsing context's document family." This is very hard in
+ // SessionHistoryInParent since we can't synchronously access the
+ // pending navigations that are already sent to the parent. We can
+ // abort any AsyncGo navigations that are waiting to be sent. If we
+ // send a message to the parent, it would be processed after any
+ // navigations previously sent. So long as we consider the "history
+ // traversal task source" to be the list in this process we match the
+ // spec. If we move the entire list to the parent, we can handle the
+ // aborting of loads there, but we don't have a way to synchronously
+ // remove entries as we do here for non-SHIP.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+
+ nsPoint scrollPos = GetCurScrollPos();
+
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ // FIXME Need to save the current scroll position on mActiveEntry.
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ // Save the current scroll position (bug 590573). Step 2.3.
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (not replacing)", this));
+ nsString title(mActiveEntry->GetTitle());
+ UpdateActiveEntry(false,
+ /* aPreviousScrollPos = */ Some(scrollPos), aNewURI,
+ /* aOriginalURI = */ nullptr,
+ /* aTriggeringPrincipal = */ aDocument->NodePrincipal(),
+ csp, title, scrollRestorationIsManual, aData,
+ uriWasModified);
+ } else {
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
+
+ // Session history entries created by pushState inherit scroll restoration
+ // mode from the current entry.
+ newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);
+
+ nsString title;
+ mOSHE->GetTitle(title);
+
+ // Set the new SHEntry's title (bug 655273).
+ newSHEntry->SetTitle(title);
+
+ // Link the new SHEntry to the old SHEntry's BFCache entry, since the
+ // two entries correspond to the same document.
+ NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
+ NS_ERROR_FAILURE);
+
+ // AddToSessionHistory may not modify mOSHE. In case it doesn't,
+ // we'll just set mOSHE here.
+ mOSHE = newSHEntry;
+ }
+ } else if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (replacing) mActiveEntry %p",
+ this, mActiveEntry.get()));
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ // We don't use aTitle here, see bug 544535.
+ nsString title;
+ if (mActiveEntry) {
+ title = mActiveEntry->GetTitle();
+ }
+ UpdateActiveEntry(
+ true, /* aPreviousScrollPos = */ Nothing(), aNewURI, aNewURI,
+ aDocument->NodePrincipal(), aDocument->GetCsp(), title,
+ mActiveEntry && mActiveEntry->GetScrollRestorationIsManual(), aData,
+ uriWasModified);
+ } else {
+ // Step 3.
+ newSHEntry = mOSHE;
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p step 3", this));
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ if (!newSHEntry) {
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, aDocument->GetCsp(), true,
+ getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOSHE = newSHEntry;
+ }
+
+ newSHEntry->SetURI(aNewURI);
+ newSHEntry->SetOriginalURI(aNewURI);
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ newSHEntry->SetResultPrincipalURI(nullptr);
+ newSHEntry->SetLoadReplace(false);
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Step 2.4 and 3: Modify new/original session history entry and clear its
+ // POST data, if there is any.
+ newSHEntry->SetStateData(aData);
+ newSHEntry->SetPostData(nullptr);
+
+ newSHEntry->SetURIWasModified(uriWasModified);
+
+ // Step E as described at the top of AddState: If aReplace is false,
+ // indicating that we're doing a pushState rather than a replaceState,
+ // notify bfcache that we've added a page to the history so it can evict
+ // content viewers if appropriate. Otherwise call ReplaceEntry so that we
+ // notify nsIHistoryListeners that an entry was replaced. We may not have a
+ // root session history if this call is coming from a document.open() in a
+ // docshell subtree that disables session history.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ rootSH->LegacySHistory()->EvictContentViewersOrReplaceEntry(newSHEntry,
+ aReplace);
+ }
+ }
+
+ // Step 4: If the document's URI changed, update document's URI and update
+ // global history.
+ //
+ // We need to call FireOnLocationChange so that the browser's address bar
+ // gets updated and the back button is enabled, but we only need to
+ // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
+ // since SetCurrentURI will call FireOnLocationChange for us.
+ //
+ // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass
+ // nullptr for aRequest param to FireOnLocationChange(...). Such an update
+ // notification is allowed only when we know docshell is not loading a new
+ // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
+ // FireOnLocationChange(...) breaks security UI.
+ //
+ // If the docshell is shutting down, don't update the document URI, as we
+ // can't load into a docshell that is being destroyed.
+ if (!aEqualURIs && !mIsBeingDestroyed) {
+ aDocument->SetDocumentURI(aNewURI);
+ // We can't trust SetCurrentURI to do always fire locationchange events
+ // when we expect it to, so we hack around that by doing it ourselves...
+ SetCurrentURI(aNewURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT);
+ if (mLoadType != LOAD_ERROR_PAGE) {
+ FireDummyOnLocationChange();
+ }
+
+ AddURIVisit(aNewURI, aCurrentURI, 0);
+
+ // AddURIVisit doesn't set the title for the new URI in global history,
+ // so do that here.
+ UpdateGlobalHistoryTitle(aNewURI);
+
+ // Inform the favicon service that our old favicon applies to this new
+ // URI.
+ CopyFavicon(aCurrentURI, aNewURI, UsePrivateBrowsing());
+ } else {
+ FireDummyOnLocationChange();
+ }
+ aDocument->SetStateObject(aData);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) {
+ if (mozilla::SessionHistoryInParent()) {
+ *aIsManual = mActiveEntry && mActiveEntry->GetScrollRestorationIsManual();
+ return NS_OK;
+ }
+
+ *aIsManual = false;
+ if (mOSHE) {
+ return mOSHE->GetScrollRestorationIsManual(aIsManual);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) {
+ SetScrollRestorationIsManualOnHistoryEntry(mOSHE, aIsManual);
+
+ return NS_OK;
+}
+
+void nsDocShell::SetScrollRestorationIsManualOnHistoryEntry(
+ nsISHEntry* aSHEntry, bool aIsManual) {
+ if (aSHEntry) {
+ aSHEntry->SetScrollRestorationIsManual(aIsManual);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetScrollRestorationIsManual(aIsManual);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetScrollRestorationIsManual(aIsManual);
+ }
+ } else {
+ mozilla::Unused << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryScrollRestorationIsManual(
+ mBrowsingContext, aIsManual);
+ }
+ }
+}
+
+void nsDocShell::SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry,
+ uint32_t aCacheKey) {
+ if (aSHEntry) {
+ aSHEntry->SetCacheKey(aCacheKey);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetCacheKey(aCacheKey);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetCacheKey(aCacheKey);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryCacheKey(
+ mBrowsingContext, aCacheKey);
+ }
+ }
+}
+
+/* static */
+bool nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel) {
+ // I believe none of the about: urls should go in the history. But then
+ // that could just be me... If the intent is only deny about:blank then we
+ // should just do a spec compare, rather than two gets of the scheme and
+ // then the path. -Gagan
+ nsresult rv;
+ nsAutoCString buf;
+
+ rv = aURI->GetScheme(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("about")) {
+ rv = aURI->GetPathQueryRef(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("blank")) {
+ return false;
+ }
+ // We only want to add about:newtab if it's not privileged:
+ if (buf.EqualsLiteral("newtab")) {
+ NS_ENSURE_TRUE(aChannel, false);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(rv, false);
+ return !resultPrincipal->IsSystemPrincipal();
+ }
+ }
+
+ return true;
+}
+
+nsresult nsDocShell::AddToSessionHistory(
+ nsIURI* aURI, nsIChannel* aChannel, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, bool aCloneChildren,
+ nsISHEntry** aNewEntry) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+ MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent());
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get()));
+ }
+#endif
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISHEntry> entry;
+
+ /*
+ * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use
+ * the existing SH entry in the page and replace the url and
+ * other vitalities.
+ */
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) &&
+ !mBrowsingContext->IsTop()) {
+ // This is a subframe
+ entry = mOSHE;
+ if (entry) {
+ entry->ClearEntry();
+ }
+ }
+
+ // Create a new entry if necessary.
+ if (!entry) {
+ entry = new nsSHEntry();
+ }
+
+ // Get the post data & referrer
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ bool loadReplace = false;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ uint32_t cacheKey = 0;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aCsp;
+ bool expired = false; // by default the page is not expired
+ bool discardLayoutState = false;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel;
+ if (aChannel) {
+ cacheChannel = do_QueryInterface(aChannel);
+
+ /* If there is a caching channel, get the Cache Key and store it
+ * in SH.
+ */
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the httpChannel is hiding under a multipartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+ httpChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ uint32_t loadFlags;
+ aChannel->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ rv = httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ discardLayoutState = ShouldDiscardLayoutState(httpChannel);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (!triggeringPrincipal) {
+ triggeringPrincipal = loadInfo->TriggeringPrincipal();
+ }
+ if (!csp) {
+ csp = loadInfo->GetCspToInherit();
+ }
+
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+
+ // For now keep storing just the principal in the SHEntry.
+ if (!principalToInherit) {
+ if (loadInfo->GetLoadingSandboxed()) {
+ if (loadInfo->GetLoadingPrincipal()) {
+ principalToInherit = NullPrincipal::CreateWithInheritedAttributes(
+ loadInfo->GetLoadingPrincipal());
+ } else {
+ // get the OriginAttributes
+ OriginAttributes attrs;
+ loadInfo->GetOriginAttributes(&attrs);
+ principalToInherit = NullPrincipal::Create(attrs);
+ }
+ } else {
+ principalToInherit = loadInfo->PrincipalToInherit();
+ }
+ }
+
+ if (!partitionedPrincipalToInherit) {
+ // XXXehsan is it correct to fall back to the principal to inherit in all
+ // cases? For example, what about the cases where we are using the load
+ // info's principal to inherit? Do we need to add a similar concept to
+ // load info for partitioned principal?
+ partitionedPrincipalToInherit = principalToInherit;
+ }
+ }
+
+ nsAutoString srcdoc;
+ bool srcdocEntry = false;
+ nsCOMPtr<nsIURI> baseURI;
+
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ inStrmChan->GetSrcdocData(srcdoc);
+ srcdocEntry = true;
+ inStrmChan->GetBaseURI(getter_AddRefs(baseURI));
+ } else {
+ srcdoc.SetIsVoid(true);
+ }
+ }
+ /* If cache got a 'no-store', ask SH not to store
+ * HistoryLayoutState. By default, SH will set this
+ * flag to true and save HistoryLayoutState.
+ */
+ bool saveLayoutState = !discardLayoutState;
+
+ 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;
+ }
+ }
+
+ // Title is set in nsDocShell::SetTitle()
+ entry->Create(aURI, // uri
+ u""_ns, // Title
+ inputStream, // Post data stream
+ cacheKey, // CacheKey
+ mContentTypeHint, // Content-type
+ triggeringPrincipal, // Channel or provided principal
+ principalToInherit, partitionedPrincipalToInherit, csp,
+ HistoryID(), GetCreatedDynamically(), originalURI,
+ resultPrincipalURI, loadReplace, referrerInfo, srcdoc,
+ srcdocEntry, baseURI, saveLayoutState, expired);
+
+ if (mBrowsingContext->IsTop() && GetSessionHistory()) {
+ bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
+ Maybe<int32_t> previousEntryIndex;
+ Maybe<int32_t> loadedEntryIndex;
+ rv = GetSessionHistory()->LegacySHistory()->AddToRootSessionHistory(
+ aCloneChildren, mOSHE, mBrowsingContext, entry, mLoadType,
+ shouldPersist, &previousEntryIndex, &loadedEntryIndex);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Could not add entry to root session history");
+ if (previousEntryIndex.isSome()) {
+ mPreviousEntryIndex = previousEntryIndex.value();
+ }
+ if (loadedEntryIndex.isSome()) {
+ mLoadedEntryIndex = loadedEntryIndex.value();
+ }
+
+ // aCloneChildren implies that we are retaining the same document, thus we
+ // need to signal to the top WC that the new SHEntry may receive a fresh
+ // user interaction flag.
+ if (aCloneChildren) {
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ }
+ } else {
+ // This is a subframe, make sure that this new SHEntry will be
+ // marked with user interaction.
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren);
+ }
+ }
+
+ // Return the new SH entry...
+ if (aNewEntry) {
+ *aNewEntry = nullptr;
+ if (NS_SUCCEEDED(rv)) {
+ entry.forget(aNewEntry);
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::UpdateActiveEntry(
+ bool aReplace, const Maybe<nsPoint>& aPreviousScrollPos, nsIURI* aURI,
+ nsIURI* aOriginalURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp, const nsAString& aTitle,
+ bool aScrollRestorationIsManual, nsIStructuredCloneContainer* aData,
+ bool aURIWasModified) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(mLoadType == LOAD_PUSHSTATE,
+ "This code only deals with pushState");
+ MOZ_ASSERT_IF(aPreviousScrollPos.isSome(), !aReplace);
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ aURI->GetSpecOrDefault().get()));
+
+ // Even if we're replacing an existing entry we create new a
+ // SessionHistoryInfo. In the parent process we'll keep the existing
+ // SessionHistoryEntry, but just replace its SessionHistoryInfo, that way the
+ // entry keeps identity but its data is replaced.
+ bool replace = aReplace && mActiveEntry;
+ if (mActiveEntry) {
+ // Link this entry to the previous active entry.
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, aURI);
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ aURI, aTriggeringPrincipal, nullptr, nullptr, aCsp, mContentTypeHint);
+ }
+ mActiveEntry->SetOriginalURI(aOriginalURI);
+ mActiveEntry->SetTitle(aTitle);
+ mActiveEntry->SetStateData(static_cast<nsStructuredCloneContainer*>(aData));
+ mActiveEntry->SetURIWasModified(aURIWasModified);
+ mActiveEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual);
+
+ if (replace) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ aPreviousScrollPos, mActiveEntry.get(), mLoadType,
+ /* aCacheKey = */ 0);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) {
+ NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE);
+
+ nsresult rv;
+ RefPtr<nsDocShellLoadState> loadState;
+ rv = aEntry->CreateLoadInfo(getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if
+ // that's the only thing holding a ref to aEntry that will cause aEntry to
+ // die while we're loading it. So hold a strong ref to aEntry here, just
+ // in case.
+ nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry);
+
+ return LoadHistoryEntry(loadState, aLoadType, aEntry == mOSHE);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(const LoadingSessionHistoryInfo& aEntry,
+ uint32_t aLoadType) {
+ RefPtr<nsDocShellLoadState> loadState = aEntry.CreateLoadInfo();
+ return LoadHistoryEntry(loadState, aLoadType,
+ aEntry.mLoadingCurrentActiveEntry);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsDocShellLoadState* aLoadState,
+ uint32_t aLoadType,
+ bool aReloadingActiveEntry) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK;
+ }
+
+ // We are setting load type afterwards so we don't have to
+ // send it in an IPC message
+ aLoadState->SetLoadType(aLoadType);
+
+ nsresult rv;
+ if (SchemeIsJavascript(aLoadState->URI())) {
+ // We're loading a URL that will execute script from inside asyncOpen.
+ // Replace the current document with about:blank now to prevent
+ // anything from the current document from leaking into any JavaScript
+ // code in the URL.
+ // Don't cache the presentation if we're going to just reload the
+ // current entry. Caching would lead to trying to save the different
+ // content viewers in the same nsISHEntry object.
+ rv = CreateAboutBlankContentViewer(
+ aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), nullptr, nullptr,
+ Nothing(), !aReloadingActiveEntry);
+
+ if (NS_FAILED(rv)) {
+ // The creation of the intermittent about:blank content
+ // viewer failed for some reason (potentially because the
+ // user prevented it). Interrupt the history load.
+ return NS_OK;
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ // Ensure that we have a triggeringPrincipal. Otherwise javascript:
+ // URIs will pick it up from the about:blank page we just loaded,
+ // and we don't really want even that in this case.
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithInheritedAttributes(this);
+ aLoadState->SetTriggeringPrincipal(principal);
+ }
+ }
+
+ /* If there is a valid postdata *and* the user pressed
+ * reload or shift-reload, take user's permission before we
+ * repost the data to the server.
+ */
+ if ((aLoadType & LOAD_CMD_RELOAD) && aLoadState->PostDataStream()) {
+ bool repost;
+ rv = ConfirmRepost(&repost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the user pressed cancel in the dialog, return. We're done here.
+ if (!repost) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // If there is no valid triggeringPrincipal, we deny the load
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid triggeringPrincipal to load from history");
+ if (!aLoadState->TriggeringPrincipal()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return InternalLoad(aLoadState); // No nsIRequest
+}
+
+NS_IMETHODIMP
+nsDocShell::PersistLayoutHistoryState() {
+ nsresult rv = NS_OK;
+
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+ nsCOMPtr<nsILayoutHistoryState> layoutState;
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ rv = presShell->CaptureHistoryState(getter_AddRefs(layoutState));
+ } else if (scrollRestorationIsManual) {
+ // Even if we don't have layout anymore, we may want to reset the
+ // current scroll state in layout history.
+ GetLayoutHistoryState(getter_AddRefs(layoutState));
+ }
+
+ if (scrollRestorationIsManual && layoutState) {
+ layoutState->ResetScrollState();
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ if (aOldEntry == mOSHE) {
+ mOSHE = aNewEntry;
+ }
+
+ if (aOldEntry == mLSHE) {
+ mLSHE = aNewEntry;
+ }
+}
+
+void nsDocShell::SetHistoryEntryAndUpdateBC(const Maybe<nsISHEntry*>& aLSHE,
+ const Maybe<nsISHEntry*>& aOSHE) {
+ // We want to hold on to the reference in mLSHE before we update it.
+ // Otherwise, SetHistoryEntry could release the last reference to
+ // the entry while aOSHE is pointing to it.
+ nsCOMPtr<nsISHEntry> deathGripOldLSHE;
+ if (aLSHE.isSome()) {
+ deathGripOldLSHE = SetHistoryEntry(&mLSHE, aLSHE.value());
+ MOZ_ASSERT(mLSHE.get() == aLSHE.value());
+ }
+ nsCOMPtr<nsISHEntry> deathGripOldOSHE;
+ if (aOSHE.isSome()) {
+ deathGripOldOSHE = SetHistoryEntry(&mOSHE, aOSHE.value());
+ MOZ_ASSERT(mOSHE.get() == aOSHE.value());
+ }
+}
+
+already_AddRefed<nsISHEntry> nsDocShell::SetHistoryEntry(
+ nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) {
+ // We need to sync up the docshell and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the root docshell, which will then recursively sync up all docshells
+ // to their corresponding entries in the new session history tree.
+ // If we don't do this, then we can cache a content viewer on the wrong
+ // cloned entry, and subsequently restore it at the wrong time.
+ RefPtr<BrowsingContext> topBC = mBrowsingContext->Top();
+ if (topBC->IsDiscarded()) {
+ topBC = nullptr;
+ }
+ RefPtr<BrowsingContext> currBC =
+ mBrowsingContext->IsDiscarded() ? nullptr : mBrowsingContext;
+ if (topBC && *aPtr) {
+ (*aPtr)->SyncTreesForSubframeNavigation(aEntry, topBC, currBC);
+ }
+ nsCOMPtr<nsISHEntry> entry(aEntry);
+ entry.swap(*aPtr);
+ return entry.forget();
+}
+
+already_AddRefed<ChildSHistory> nsDocShell::GetRootSessionHistory() {
+ RefPtr<ChildSHistory> childSHistory =
+ mBrowsingContext->Top()->GetChildSessionHistory();
+ return childSHistory.forget();
+}
+
+nsresult nsDocShell::GetHttpChannel(nsIChannel* aChannel,
+ nsIHttpChannel** aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+ if (!aChannel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel));
+ if (multiPartChannel) {
+ nsCOMPtr<nsIChannel> baseChannel;
+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel));
+ *aReturn = httpChannel;
+ NS_IF_ADDREF(*aReturn);
+ }
+ return NS_OK;
+}
+
+bool nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) {
+ // By default layout State will be saved.
+ if (!aChannel) {
+ return false;
+ }
+
+ // figure out if SH should be saving layout state
+ bool noStore = false;
+ Unused << aChannel->IsNoStoreResponse(&noStore);
+ return noStore;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditor(nsIEditor** aEditor) {
+ NS_ENSURE_ARG_POINTER(aEditor);
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorInternal();
+ htmlEditor.forget(aEditor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetEditor(nsIEditor* aEditor) {
+ HTMLEditor* htmlEditor = aEditor ? aEditor->AsHTMLEditor() : nullptr;
+ // If TextEditor comes, throw an error.
+ if (aEditor && !htmlEditor) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return SetHTMLEditorInternal(htmlEditor);
+}
+
+HTMLEditor* nsDocShell::GetHTMLEditorInternal() {
+ return mEditorData ? mEditorData->GetHTMLEditor() : nullptr;
+}
+
+nsresult nsDocShell::SetHTMLEditorInternal(HTMLEditor* aHTMLEditor) {
+ if (!aHTMLEditor && !mEditorData) {
+ return NS_OK;
+ }
+
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->SetHTMLEditor(aHTMLEditor);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditable(bool* aEditable) {
+ NS_ENSURE_ARG_POINTER(aEditable);
+ *aEditable = mEditorData && mEditorData->GetEditable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHasEditingSession(bool* aHasEditingSession) {
+ NS_ENSURE_ARG_POINTER(aHasEditingSession);
+
+ if (mEditorData) {
+ *aHasEditingSession = !!mEditorData->GetEditingSession();
+ } else {
+ *aHasEditingSession = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::MakeEditable(bool aInWaitForUriLoad) {
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->MakeEditable(aInWaitForUriLoad);
+}
+
+/* static */ bool nsDocShell::ShouldAddURIVisit(nsIChannel* aChannel) {
+ bool needToAddURIVisit = true;
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (props) {
+ mozilla::Unused << props->GetPropertyAsBool(
+ u"docshell.needToAddURIVisit"_ns, &needToAddURIVisit);
+ }
+
+ return needToAddURIVisit;
+}
+
+/* static */ void nsDocShell::ExtractLastVisit(
+ nsIChannel* aChannel, nsIURI** aURI, uint32_t* aChannelRedirectFlags) {
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props) {
+ return;
+ }
+
+ nsresult rv = props->GetPropertyAsInterface(u"docshell.previousURI"_ns,
+ NS_GET_IID(nsIURI),
+ reinterpret_cast<void**>(aURI));
+
+ if (NS_FAILED(rv)) {
+ // There is no last visit for this channel, so this must be the first
+ // link. Link the visit to the referrer of this request, if any.
+ // Treat referrer as null if there is an error getting it.
+ (void)NS_GetReferrerFromChannel(aChannel, aURI);
+ } else {
+ rv = props->GetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Could not fetch previous flags, URI will be treated like referrer");
+ }
+}
+
+void nsDocShell::SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags) {
+ nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props || !aURI) {
+ return;
+ }
+
+ props->SetPropertyAsInterface(u"docshell.previousURI"_ns, aURI);
+ props->SetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+}
+
+/* static */ void nsDocShell::InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType) {
+ MOZ_ASSERT(aURI, "Visited URI is null!");
+ MOZ_ASSERT(aLoadType != LOAD_ERROR_PAGE && aLoadType != LOAD_BYPASS_HISTORY,
+ "Do not add error or bypass pages to global history");
+
+ bool usePrivateBrowsing = false;
+ aBrowsingContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+
+ // Only content-type docshells save URI visits. Also don't do
+ // anything here if we're not supposed to use global history.
+ if (!aBrowsingContext->IsContent() ||
+ !aBrowsingContext->GetUseGlobalHistory() || usePrivateBrowsing) {
+ return;
+ }
+
+ nsCOMPtr<IHistory> history = services::GetHistory();
+
+ if (history) {
+ uint32_t visitURIFlags = 0;
+
+ if (aBrowsingContext->IsTop()) {
+ visitURIFlags |= IHistory::TOP_LEVEL;
+ }
+
+ if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+ visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
+ } else if (aChannelRedirectFlags &
+ nsIChannelEventSink::REDIRECT_PERMANENT) {
+ visitURIFlags |= IHistory::REDIRECT_PERMANENT;
+ } else {
+ MOZ_ASSERT(!aChannelRedirectFlags,
+ "One of REDIRECT_TEMPORARY or REDIRECT_PERMANENT must be set "
+ "if any flags in aChannelRedirectFlags is set.");
+ }
+
+ if (aResponseStatus >= 300 && aResponseStatus < 400) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE;
+ if (aResponseStatus == 301 || aResponseStatus == 308) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE_PERMANENT;
+ }
+ }
+ // Errors 400-501 and 505 are considered unrecoverable, in the sense a
+ // simple retry attempt by the user is unlikely to solve them.
+ // 408 is special cased, since may actually indicate a temporary
+ // connection problem.
+ else if (aResponseStatus != 408 &&
+ ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
+ aResponseStatus == 505)) {
+ visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
+ }
+
+ mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI,
+ visitURIFlags);
+ }
+}
+
+void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus) {
+ nsPIDOMWindowOuter* outer = GetWindow();
+ nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+
+ InternalAddURIVisit(aURI, aPreviousURI, aChannelRedirectFlags,
+ aResponseStatus, mBrowsingContext, widget, mLoadType);
+}
+
+//*****************************************************************************
+// nsDocShell: Helper Routines
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mLoadType;
+ return NS_OK;
+}
+
+nsresult nsDocShell::ConfirmRepost(bool* aRepost) {
+ if (StaticPrefs::dom_confirm_repost_testing_always_accept()) {
+ *aRepost = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPromptCollection> prompter =
+ do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
+ if (!prompter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return prompter->ConfirmRepost(mBrowsingContext, aRepost);
+}
+
+nsresult nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle) {
+ NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt),
+ NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(
+ stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsIScrollableFrame* nsDocShell::GetRootScrollFrame() {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, nullptr);
+
+ return presShell->GetRootScrollFrameAsScrollable();
+}
+
+nsresult nsDocShell::EnsureScriptEnvironment() {
+ if (mScriptGlobal) {
+ return NS_OK;
+ }
+
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef DEBUG
+ NS_ASSERTION(!mInEnsureScriptEnv,
+ "Infinite loop! Calling EnsureScriptEnvironment() from "
+ "within EnsureScriptEnvironment()!");
+
+ // Yeah, this isn't re-entrant safe, but that's ok since if we
+ // re-enter this method, we'll infinitely loop...
+ AutoRestore<bool> boolSetter(mInEnsureScriptEnv);
+ mInEnsureScriptEnv = true;
+#endif
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE);
+
+ uint32_t chromeFlags;
+ browserChrome->GetChromeFlags(&chromeFlags);
+
+ // If our window is modal and we're not opened as chrome, make
+ // this window a modal content window.
+ mScriptGlobal = nsGlobalWindowOuter::Create(this, mItemType == typeChrome);
+ MOZ_ASSERT(mScriptGlobal);
+
+ // Ensure the script object is set up to run script.
+ return mScriptGlobal->EnsureScriptEnvironment();
+}
+
+nsresult nsDocShell::EnsureEditorData() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor();
+ if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) {
+ // We shouldn't recreate the editor data if it already exists, or
+ // we're shutting down, or we already have a detached editor data
+ // stored in the session history. We should only have one editordata
+ // per docshell.
+ mEditorData = MakeUnique<nsDocShellEditorData>(this);
+ }
+
+ return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsDocShell::EnsureFind() {
+ if (!mFind) {
+ mFind = new nsWebBrowserFind();
+ }
+
+ // we promise that the nsIWebBrowserFind that we return has been set
+ // up to point to the focused, or content window, so we have to
+ // set that up each time.
+
+ nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject();
+ NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED);
+
+ // default to our window
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO);
+ nsCOMPtr<nsPIDOMWindowOuter> windowToSearch;
+ nsFocusManager::GetFocusedDescendant(ourWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(windowToSearch));
+
+ nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind);
+ if (!findInFrames) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsresult rv = findInFrames->SetRootSearchFrame(ourWindow);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = findInFrames->SetCurrentSearchFrame(windowToSearch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::IsBeingDestroyed(bool* aDoomed) {
+ NS_ENSURE_ARG(aDoomed);
+ *aDoomed = mIsBeingDestroyed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = mIsExecutingOnLoadHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> state;
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ state = mActiveEntry->GetLayoutHistoryState();
+ }
+ } else {
+ if (mOSHE) {
+ state = mOSHE->GetLayoutHistoryState();
+ }
+ }
+ state.forget(aLayoutHistoryState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) {
+ if (mOSHE) {
+ mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ if (mActiveEntry) {
+ mActiveEntry->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ return NS_OK;
+}
+
+nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
+ nsIInterfaceRequestor* aRequestor) {
+ if (aRequestor) {
+ mWeakPtr = do_GetWeakReference(aRequestor);
+ }
+}
+
+nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() {
+ mWeakPtr = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID,
+ void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+ nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr);
+ if (ifReq) {
+ return ifReq->GetInterface(aIID, aSink);
+ }
+ *aSink = nullptr;
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIAuthPromptProvider
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
+ void** aResult) {
+ // a priority prompt request will override a false mAllowAuth setting
+ bool priorityPrompt = (aPromptReason == PROMPT_PROXY);
+
+ if (!mAllowAuth && !priorityPrompt) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // we're either allowing auth, or it's a proxy request
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnsureScriptEnvironment();
+ 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.
+
+ return wwatch->GetPrompt(mScriptGlobal, aIID,
+ reinterpret_cast<void**>(aResult));
+}
+
+//*****************************************************************************
+// nsDocShell::nsILoadContext
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) {
+ CallGetInterface(this, aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) {
+ return mBrowsingContext->GetTopWindow(aWindow);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopFrameElement(Element** aElement) {
+ return mBrowsingContext->GetTopFrameElement(aElement);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseTrackingProtection(bool* aUseTrackingProtection) {
+ return mBrowsingContext->GetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetUseTrackingProtection(bool aUseTrackingProtection) {
+ return mBrowsingContext->SetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsContent(bool* aIsContent) {
+ *aIsContent = (mItemType == typeContent);
+ return NS_OK;
+}
+
+bool nsDocShell::IsOKToLoadURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have a URI!");
+
+ if (!mFiredUnloadEvent) {
+ return true;
+ }
+
+ if (!mLoadingURI) {
+ return false;
+ }
+
+ bool isPrivateWin = false;
+ Document* doc = GetDocument();
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ return secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI(
+ aURI, mLoadingURI, false, isPrivateWin));
+}
+
+//
+// Routines for selection and clipboard
+//
+nsresult nsDocShell::GetControllerForCommand(const char* aCommand,
+ nsIController** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllerForCommand(aCommand, false /* for any window */,
+ aResult);
+}
+
+NS_IMETHODIMP
+nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->IsCommandEnabled(aCommand, aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommand(const char* aCommand) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->DoCommand(aCommand);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommandWithParams(const char* aCommand,
+ nsICommandParams* aParams) {
+ nsCOMPtr<nsIController> controller;
+ nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICommandController> commandController =
+ do_QueryInterface(controller, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return commandController->DoCommandWithParams(aCommand, aParams);
+}
+
+nsresult nsDocShell::EnsureCommandHandler() {
+ if (!mCommandManager) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow()) {
+ mCommandManager = new nsCommandManager(domWindow);
+ }
+ }
+ return mCommandManager ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// link handling
+
+class OnLinkClickEvent : public Runnable {
+ public:
+ OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState, bool aNoOpenerImplied,
+ bool aIsTrusted, nsIPrincipal* aTriggeringPrincipal);
+
+ NS_IMETHOD Run() override {
+ AutoPopupStatePusher popupStatePusher(mPopupState);
+
+ // We need to set up an AutoJSAPI here for the following reason: When we
+ // do OnLinkClickSync we'll eventually end up in
+ // nsGlobalWindow::OpenInternal which only does popup blocking if
+ // !LegacyIsCallerChromeOrNativeCode(). So we need to fake things so that
+ // we don't look like native code as far as LegacyIsCallerNativeCode() is
+ // concerned.
+ AutoJSAPI jsapi;
+ if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) {
+ mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
+ mTriggeringPrincipal);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsDocShell> mHandler;
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ PopupBlocker::PopupControlState mPopupState;
+ bool mNoOpenerImplied;
+ bool mIsTrusted;
+};
+
+OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal)
+ : mozilla::Runnable("OnLinkClickEvent"),
+ mHandler(aHandler),
+ mContent(aContent),
+ mLoadState(aLoadState),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPopupState(PopupBlocker::GetPopupControlState()),
+ mNoOpenerImplied(aNoOpenerImplied),
+ mIsTrusted(aIsTrusted) {}
+
+nsresult nsDocShell::OnLinkClick(
+ nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
+ const nsAString& aFileName, nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream, bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp) {
+#ifndef ANDROID
+ MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal");
+#endif
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
+ return NS_OK;
+ }
+
+ // On history navigation through Back/Forward buttons, don't execute
+ // automatic JavaScript redirection such as |anchorElement.click()| or
+ // |formElement.submit()|.
+ //
+ // XXX |formElement.submit()| bypasses this checkpoint because it calls
+ // nsDocShell::OnLinkClickSync(...) instead.
+ if (ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsAutoString target;
+
+ nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
+ bool noOpenerImplied = false;
+ if (browserChrome3) {
+ rv = browserChrome3->OnBeforeLinkTraversal(aTargetSpec, aURI, aContent,
+ mIsAppTab, target);
+ if (!aTargetSpec.Equals(target)) {
+ noOpenerImplied = true;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ target = aTargetSpec;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetTarget(target);
+ loadState->SetFileName(aFileName);
+ loadState->SetPostDataStream(aPostDataStream);
+ loadState->SetHeadersStream(aHeadersDataStream);
+ loadState->SetFirstParty(true);
+ loadState->SetTriggeringPrincipal(
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal());
+ loadState->SetPrincipalToInherit(aContent->NodePrincipal());
+ loadState->SetCsp(aCsp ? aCsp : aContent->GetCsp());
+
+ nsCOMPtr<nsIRunnable> ev =
+ new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
+ aIsTrusted, aTriggeringPrincipal);
+ return Dispatch(TaskCategory::UI, ev.forget());
+}
+
+static bool IsElementAnchorOrArea(nsIContent* aContent) {
+ // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+ // or XHTML namespace.
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
+}
+
+nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal) {
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aLoadState->URI())) {
+ return NS_OK;
+ }
+
+ // XXX When the linking node was HTMLFormElement, it is synchronous event.
+ // That is, the caller of this method is not |OnLinkClickEvent::Run()|
+ // but |HTMLFormElement::SubmitSubmission(...)|.
+ if (aContent->IsHTMLElement(nsGkAtoms::form) &&
+ ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ // if the triggeringPrincipal is not passed explicitly, then we
+ // fall back to using doc->NodePrincipal() as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal();
+
+ {
+ // defer to an external protocol handler if necessary...
+ nsCOMPtr<nsIExternalProtocolService> extProtService =
+ do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
+ if (extProtService) {
+ nsAutoCString scheme;
+ aLoadState->URI()->GetScheme(scheme);
+ if (!scheme.IsEmpty()) {
+ // if the URL scheme does not correspond to an exposed protocol, then
+ // we need to hand this link click over to the external protocol
+ // handler.
+ bool isExposed;
+ nsresult rv =
+ extProtService->IsExposedProtocol(scheme.get(), &isExposed);
+ if (NS_SUCCEEDED(rv) && !isExposed) {
+ return extProtService->LoadURI(aLoadState->URI(), triggeringPrincipal,
+ mBrowsingContext);
+ }
+ }
+ }
+ }
+ uint32_t triggeringSandboxFlags = 0;
+ if (mBrowsingContext) {
+ triggeringSandboxFlags = mBrowsingContext->GetSandboxFlags();
+ }
+
+ uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
+ bool isElementAnchorOrArea = IsElementAnchorOrArea(aContent);
+ bool triggeringPrincipalIsSystemPrincipal =
+ aLoadState->TriggeringPrincipal()->IsSystemPrincipal();
+ if (isElementAnchorOrArea) {
+ MOZ_ASSERT(aContent->IsHTMLElement());
+ nsAutoString relString;
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel,
+ relString);
+ nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
+ relString);
+
+ bool targetBlank = aLoadState->Target().LowerCaseEqualsLiteral("_blank");
+ bool explicitOpenerSet = false;
+
+ // The opener behaviour follows a hierarchy, such that if a higher
+ // priority behaviour is specified, it always takes priority. That
+ // priority is currently: norefrerer > noopener > opener > default
+
+ while (tok.hasMoreTokens()) {
+ const nsAString& token = tok.nextToken();
+ if (token.LowerCaseEqualsLiteral("noreferrer")) {
+ flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
+ INTERNAL_LOAD_FLAGS_NO_OPENER;
+ // noreferrer cannot be overwritten by a 'rel=opener'.
+ explicitOpenerSet = true;
+ break;
+ }
+
+ if (token.LowerCaseEqualsLiteral("noopener")) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ explicitOpenerSet = true;
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ token.LowerCaseEqualsLiteral("opener") && !explicitOpenerSet) {
+ explicitOpenerSet = true;
+ }
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ !explicitOpenerSet && !triggeringPrincipalIsSystemPrincipal) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+
+ if (aNoOpenerImplied) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+ }
+
+ // Get the owner document of the link that was clicked, this will be
+ // the document that the link is in, or the last document that the
+ // link was in. From that document, we'll get the URI to use as the
+ // referrer, since the current URI in this docshell may be a
+ // new document that we're in the process of loading.
+ RefPtr<Document> referrerDoc = aContent->OwnerDoc();
+
+ // Now check that the referrerDoc's inner window is the current inner
+ // window for mScriptGlobal. If it's not, then we don't want to
+ // follow this link.
+ nsPIDOMWindowInner* referrerInner = referrerDoc->GetInnerWindow();
+ NS_ENSURE_TRUE(referrerInner, NS_ERROR_UNEXPECTED);
+ if (!mScriptGlobal ||
+ mScriptGlobal->GetCurrentInnerWindow() != referrerInner) {
+ // We're no longer the current inner window
+ return NS_OK;
+ }
+
+ // referrer could be null here in some odd cases, but that's ok,
+ // we'll just load the link w/o sending a referrer in those cases.
+
+ // If this is an anchor element, grab its type property to use as a hint
+ nsAutoString typeHint;
+ RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(aContent);
+ if (anchor) {
+ anchor->GetType(typeHint);
+ NS_ConvertUTF16toUTF8 utf8Hint(typeHint);
+ nsAutoCString type, dummy;
+ NS_ParseRequestContentType(utf8Hint, type, dummy);
+ CopyUTF8toUTF16(type, typeHint);
+ }
+
+ // Link click (or form submission) can be triggered inside an onload
+ // handler, and we don't want to add history entry in this case.
+ bool inOnLoadHandler = false;
+ GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+ uint32_t loadType = inOnLoadHandler ? LOAD_NORMAL_REPLACE : LOAD_LINK;
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ isElementAnchorOrArea ? new ReferrerInfo(*aContent->AsElement())
+ : new ReferrerInfo(*referrerDoc);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ aLoadState->SetTriggeringSandboxFlags(triggeringSandboxFlags);
+ aLoadState->SetReferrerInfo(referrerInfo);
+ aLoadState->SetLoadFlags(flags);
+ aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint));
+ aLoadState->SetLoadType(loadType);
+ aLoadState->SetSourceBrowsingContext(mBrowsingContext);
+ aLoadState->SetAllowFocusMove(true);
+ aLoadState->SetHasValidUserGestureActivation(
+ context && context->HasValidTransientUserGestureActivation());
+
+ nsresult rv = InternalLoad(aLoadState);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPingListener::DispatchPings(this, aContent, aLoadState->URI(),
+ referrerInfo);
+ }
+
+ return rv;
+}
+
+nsresult nsDocShell::OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec) {
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(mTreeOwner);
+ if (!browserChrome) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(aURI);
+ nsAutoCString spec;
+ rv = exposableURI->GetDisplaySpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 uStr(spec);
+
+ PredictorPredict(aURI, mCurrentURI, nsINetworkPredictor::PREDICT_LINK,
+ aContent->NodePrincipal()->OriginAttributesRef(), nullptr);
+
+ rv = browserChrome->SetLinkStatus(uStr);
+ return rv;
+}
+
+nsresult nsDocShell::OnLeaveLink() {
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (browserChrome) {
+ rv = browserChrome->SetLinkStatus(u""_ns);
+ }
+ return rv;
+}
+
+bool nsDocShell::ShouldBlockLoadingForBackButton() {
+ if (!(mLoadType & LOAD_CMD_HISTORY) ||
+ UserActivation::IsHandlingUserInput() ||
+ !Preferences::GetBool("accessibility.blockjsredirection")) {
+ return false;
+ }
+
+ bool canGoForward = false;
+ GetCanGoForward(&canGoForward);
+ return canGoForward;
+}
+
+bool nsDocShell::PluginsAllowedInCurrentDoc() {
+ if (!mContentViewer) {
+ return false;
+ }
+
+ Document* doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return false;
+ }
+
+ return doc->GetAllowPlugins();
+}
+
+//----------------------------------------------------------------------
+// Web Shell Services API
+
+// This functions is only called when a new charset is detected in loading a
+// document.
+nsresult nsDocShell::CharsetChangeReloadDocument(const char* aCharset,
+ int32_t aSource) {
+ // XXX hack. keep the aCharset and aSource wait to pick it up
+ nsCOMPtr<nsIContentViewer> cv;
+ NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE);
+ if (cv) {
+ int32_t hint;
+ cv->GetHintCharacterSetSource(&hint);
+ if (aSource > hint) {
+ nsCString charset(aCharset);
+ cv->SetHintCharacterSet(charset);
+ cv->SetHintCharacterSetSource(aSource);
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ mCharsetReloadState = eCharsetReloadRequested;
+ switch (mLoadType) {
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE |
+ LOAD_FLAGS_BYPASS_PROXY);
+ case LOAD_RELOAD_BYPASS_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE);
+ default:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE);
+ }
+ }
+ }
+ }
+ // return failure if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+nsresult nsDocShell::CharsetChangeStopDocumentLoad() {
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ Stop(nsIWebNavigation::STOP_ALL);
+ return NS_OK;
+ }
+ // return failer if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+NS_IMETHODIMP nsDocShell::ExitPrintPreview() {
+#if NS_PRINT_PREVIEW
+ nsCOMPtr<nsIWebBrowserPrint> viewer = do_QueryInterface(mContentViewer);
+ return viewer->ExitPrintPreview();
+#else
+ return NS_OK;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCanExecuteScripts(bool* aResult) {
+ *aResult = mCanExecuteScripts;
+ return NS_OK;
+}
+
+/* [infallible] */
+NS_IMETHODIMP nsDocShell::GetIsTopLevelContentDocShell(
+ bool* aIsTopLevelContentDocShell) {
+ *aIsTopLevelContentDocShell = false;
+
+ if (mItemType == typeContent) {
+ *aIsTopLevelContentDocShell = mBrowsingContext->IsTopContent();
+ }
+
+ return NS_OK;
+}
+
+// Implements nsILoadContext.originAttributes
+NS_IMETHODIMP
+nsDocShell::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+// Implements nsIDocShell.GetOriginAttributes()
+NS_IMETHODIMP
+nsDocShell::GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+bool nsDocShell::ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI) {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aURI);
+
+ if (UsePrivateBrowsing() || mBrowsingContext->GetSandboxFlags()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ GetInProcessSameTypeParent(getter_AddRefs(parent));
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+
+ StorageAccess storage =
+ StorageAllowedForNewWindow(aPrincipal, aURI, parentInner);
+
+ return storage == StorageAccess::eAllow;
+}
+
+nsresult nsDocShell::SetOriginAttributes(const OriginAttributes& aAttrs) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+ return mBrowsingContext->SetOriginAttributes(aAttrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) {
+ RefPtr<nsDocShell> self = this;
+ RefPtr<ChildProcessChannelListener> cpcl =
+ ChildProcessChannelListener::GetSingleton();
+
+ // Call into InternalLoad with the pending channel when it is received.
+ cpcl->RegisterCallback(
+ aIdentifier, [self, aHistoryIndex](
+ nsDocShellLoadState* aLoadState,
+ nsTArray<Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming) {
+ MOZ_ASSERT(aLoadState->GetPendingRedirectedChannel());
+ if (NS_WARN_IF(self->mIsBeingDestroyed)) {
+ aLoadState->GetPendingRedirectedChannel()->Cancel(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ self->mLoadType = aLoadState->LoadType();
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+ ExtractLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ getter_AddRefs(previousURI), &previousFlags);
+ self->SaveLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ previousURI, previousFlags);
+
+ if (aTiming) {
+ self->mTiming = new nsDOMNavigationTiming(self, aTiming);
+ self->mBlankTiming = false;
+ }
+
+ // If we're performing a history load, locate the correct history entry,
+ // and set the relevant bits on our loadState.
+ if (aHistoryIndex >= 0 && self->GetSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<nsISHistory> legacySHistory =
+ self->GetSessionHistory()->LegacySHistory();
+
+ nsCOMPtr<nsISHEntry> entry;
+ nsresult rv = legacySHistory->GetEntryAtIndex(aHistoryIndex,
+ getter_AddRefs(entry));
+ if (NS_SUCCEEDED(rv)) {
+ legacySHistory->InternalSetRequestedIndex(aHistoryIndex);
+ aLoadState->SetLoadType(LOAD_HISTORY);
+ aLoadState->SetSHEntry(entry);
+ }
+ }
+
+ self->InternalLoad(aLoadState);
+
+ if (aLoadState->GetOriginalURIString().isSome()) {
+ // Save URI string in case it's needed later when
+ // sending to search engine service in EndPageLoad()
+ self->mOriginalUriString = *aLoadState->GetOriginalURIString();
+ }
+
+ for (auto& endpoint : aStreamFilterEndpoints) {
+ extensions::StreamFilterParent::Attach(
+ aLoadState->GetPendingRedirectedChannel(), std::move(endpoint));
+ }
+
+ // If the channel isn't pending, then it means that InternalLoad
+ // never connected it, and we shouldn't try to continue. This
+ // can happen even if InternalLoad returned NS_OK.
+ bool pending = false;
+ aLoadState->GetPendingRedirectedChannel()->IsPending(&pending);
+ NS_ASSERTION(pending, "We should have connected the pending channel!");
+ if (!pending) {
+ return NS_BINDING_ABORTED;
+ }
+ return NS_OK;
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return SetOriginAttributes(attrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) {
+ if (PresShell* presShell = GetPresShell()) {
+ *aOut = presShell->AsyncPanZoomEnabled();
+ return NS_OK;
+ }
+
+ // If we don't have a presShell, fall back to the default platform value of
+ // whether or not APZ is enabled.
+ *aOut = gfxPlatform::AsyncPanZoomEnabled();
+ return NS_OK;
+}
+
+bool nsDocShell::HasUnloadedParent() {
+ for (WindowContext* wc = GetBrowsingContext()->GetParentWindowContext(); wc;
+ wc = wc->GetParentWindowContext()) {
+ if (wc->IsCached() || wc->IsDiscarded() ||
+ wc->GetBrowsingContext()->IsDiscarded()) {
+ // If a parent is OOP and the parent WindowContext is no
+ // longer current, we can assume the parent was unloaded.
+ return true;
+ }
+
+ if (wc->GetBrowsingContext()->IsInProcess() &&
+ (!wc->GetBrowsingContext()->GetDocShell() ||
+ wc->GetBrowsingContext()->GetDocShell()->GetIsInUnload())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsDocShell::ShouldUpdateGlobalHistory(uint32_t aLoadType) {
+ return !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE ||
+ aLoadType & LOAD_CMD_HISTORY);
+}
+
+void nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI) {
+ if (!mBrowsingContext->GetUseGlobalHistory() || UsePrivateBrowsing()) {
+ return;
+ }
+
+ // Global history is interested into sub-frame visits only for link-coloring
+ // purposes, thus title updates are skipped for those.
+ //
+ // Moreover, some iframe documents (such as the ones created via
+ // document.open()) inherit the document uri of the caller, which would cause
+ // us to override a previously set page title with one from the subframe.
+ if (IsFrame()) {
+ return;
+ }
+
+ if (nsCOMPtr<IHistory> history = services::GetHistory()) {
+ history->SetURITitle(aURI, mTitle);
+ }
+}
+
+bool nsDocShell::IsInvisible() { return mInvisible; }
+
+void nsDocShell::SetInvisible(bool aInvisible) { mInvisible = aInvisible; }
+
+// The caller owns |aAsyncCause| here.
+void nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
+ const nsAString& aFunctionName,
+ const nsAString& aFilename,
+ const uint32_t aLineNumber,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) {
+ // If first start, mark interval start.
+ if (mJSRunToCompletionDepth == 0) {
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (timelines && timelines->HasConsumer(this)) {
+ timelines->AddMarkerForDocShell(
+ this, mozilla::MakeUnique<JavascriptTimelineMarker>(
+ aReason, aFunctionName, aFilename, aLineNumber,
+ MarkerTracingType::START, aAsyncStack, aAsyncCause));
+ }
+ }
+
+ mJSRunToCompletionDepth++;
+}
+
+void nsDocShell::NotifyJSRunToCompletionStop() {
+ mJSRunToCompletionDepth--;
+
+ // If last stop, mark interval end.
+ if (mJSRunToCompletionDepth == 0) {
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (timelines && timelines->HasConsumer(this)) {
+ timelines->AddMarkerForDocShell(this, "Javascript",
+ MarkerTracingType::END);
+ }
+ }
+}
+
+/* static */
+void nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword) {
+ if (aProvider.IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsISearchService> searchSvc =
+ do_GetService("@mozilla.org/browser/search-service;1");
+ if (searchSvc) {
+ nsCOMPtr<nsISearchEngine> searchEngine;
+ searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
+ if (searchEngine) {
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ // Note that "keyword-search" refers to a search via the url
+ // bar, not a bookmarks keyword search.
+ obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+}
+
+NS_IMETHODIMP
+nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ return mInterceptController->ChannelIntercepted(aChannel);
+}
+
+bool nsDocShell::InFrameSwap() {
+ RefPtr<nsDocShell> shell = this;
+ do {
+ if (shell->mInFrameSwap) {
+ return true;
+ }
+ shell = shell->GetInProcessParentDocshell();
+ } while (shell);
+ return false;
+}
+
+UniquePtr<ClientSource> nsDocShell::TakeInitialClientSource() {
+ return std::move(mInitialClientSource);
+}
+
+NS_IMETHODIMP
+nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError) {
+ if (mContentViewer) {
+ RefPtr<Document> doc = mContentViewer->GetDocument();
+ if (doc) {
+ doc->WarnOnceAbout(DeprecatedOperations(aWarning), aAsError);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) {
+ if (!NS_SUCCEEDED(EnsureEditorData())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aEditSession = do_AddRef(mEditorData->GetEditingSession()).take();
+ return *aEditSession ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetScriptableBrowserChild(nsIBrowserChild** aBrowserChild) {
+ *aBrowserChild = GetBrowserChild().take();
+ return *aBrowserChild ? NS_OK : NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsIBrowserChild> nsDocShell::GetBrowserChild() {
+ nsCOMPtr<nsIBrowserChild> tc = do_QueryReferent(mBrowserChild);
+ return tc.forget();
+}
+
+nsCommandManager* nsDocShell::GetCommandManager() {
+ NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
+ return mCommandManager;
+}
+
+NS_IMETHODIMP_(void)
+nsDocShell::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) {
+ mBrowsingContext->GetOriginAttributes(aAttrs);
+}
+
+HTMLEditor* nsIDocShell::GetHTMLEditor() {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->GetHTMLEditorInternal();
+}
+
+nsresult nsIDocShell::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->SetHTMLEditorInternal(aHTMLEditor);
+}
+
+#define MATRIX_LENGTH 20
+
+NS_IMETHODIMP
+nsDocShell::SetColorMatrix(const nsTArray<float>& aMatrix) {
+ if (aMatrix.Length() == MATRIX_LENGTH) {
+ mColorMatrix.reset(new gfx::Matrix5x4());
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(mColorMatrix->components, aMatrix.Elements(),
+ sizeof(mColorMatrix->components));
+ } else if (aMatrix.Length() == 0) {
+ mColorMatrix.reset();
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ frame->SchedulePaint();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetColorMatrix(nsTArray<float>& aMatrix) {
+ if (mColorMatrix) {
+ aMatrix.SetLength(MATRIX_LENGTH);
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(aMatrix.Elements(), mColorMatrix->components,
+ MATRIX_LENGTH * sizeof(float));
+ }
+
+ return NS_OK;
+}
+
+#undef MATRIX_LENGTH
+
+NS_IMETHODIMP
+nsDocShell::GetIsForceReloading(bool* aForceReload) {
+ *aForceReload = IsForceReloading();
+ return NS_OK;
+}
+
+bool nsDocShell::IsForceReloading() { return IsForceReloadType(mLoadType); }
+
+NS_IMETHODIMP
+nsDocShell::GetBrowsingContextXPCOM(BrowsingContext** aBrowsingContext) {
+ *aBrowsingContext = do_AddRef(mBrowsingContext).take();
+ return NS_OK;
+}
+
+BrowsingContext* nsDocShell::GetBrowsingContext() { return mBrowsingContext; }
+
+bool nsDocShell::GetIsAttemptingToNavigate() {
+ // XXXbz the document.open spec says to abort even if there's just a
+ // queued navigation task, sort of. It's not clear whether browsers
+ // actually do that, and we didn't use to do it, so for now let's
+ // not do that.
+ // https://github.com/whatwg/html/issues/3447 tracks the spec side of this.
+ if (mDocumentRequest) {
+ // There's definitely a navigation in progress.
+ return true;
+ }
+
+ // javascript: channels have slightly weird behavior: they're LOAD_BACKGROUND
+ // until the script runs, which means they're not sending loadgroup
+ // notifications and hence not getting set as mDocumentRequest. Look through
+ // our loadgroup for document-level javascript: loads.
+ if (!mLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ bool hasMore = false;
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+ nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(elem));
+ if (!scriptChannel) {
+ continue;
+ }
+
+ if (scriptChannel->GetIsDocumentLoad()) {
+ // This is a javascript: load that might lead to a new document,
+ // hence a navigation.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsDocShell::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo) {
+ // FIXME Would like to assert this, but can't yet.
+ // MOZ_ASSERT(!mLoadingEntry);
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Setting the loading entry on nsDocShell %p to %s", this,
+ aLoadingInfo.mInfo.GetURI()->GetSpecOrDefault().get()));
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(aLoadingInfo);
+}
+
+void nsDocShell::MoveLoadingToActiveEntry(bool aPersist) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p MoveLoadingToActiveEntry", this));
+
+ bool hadActiveEntry = !!mActiveEntry;
+ mActiveEntry = nullptr;
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> loadingEntry;
+ mActiveEntryIsLoadingFromSessionHistory =
+ mLoadingEntry && mLoadingEntry->mLoadIsFromSessionHistory;
+ if (mLoadingEntry) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p "
+ "to %s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ mLoadingEntry.swap(loadingEntry);
+ }
+
+ if (mActiveEntry) {
+ MOZ_ASSERT(loadingEntry);
+ uint32_t loadType =
+ mLoadType == LOAD_ERROR_PAGE ? mFailedLoadType : mLoadType;
+ mBrowsingContext->SessionHistoryCommit(*loadingEntry, loadType,
+ hadActiveEntry, aPersist, false);
+ }
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
new file mode 100644
index 0000000000..9e0d878a7e
--- /dev/null
+++ b/docshell/base/nsDocShell.h
@@ -0,0 +1,1281 @@
+/* -*- 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 nsDocShell_h__
+#define nsDocShell_h__
+
+#include "Units.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "nsCOMPtr.h"
+#include "nsCharsetSource.h"
+#include "nsDocLoader.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIBaseWindow.h"
+#include "nsIDeprecationWarner.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIRefreshURI.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsIWebProgressListener.h"
+#include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+// Interfaces Needed
+
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class ObservedDocShell;
+enum class TaskCategory;
+namespace dom {
+class ClientInfo;
+class ClientSource;
+class EventTarget;
+class SessionHistoryInfo;
+struct LoadingSessionHistoryInfo;
+} // namespace dom
+namespace net {
+class LoadInfo;
+class DocumentLoadListener;
+} // namespace net
+} // namespace mozilla
+
+class nsIContentViewer;
+class nsIController;
+class nsIDocShellTreeOwner;
+class nsIHttpChannel;
+class nsIMutableArray;
+class nsIPrompt;
+class nsIScrollableFrame;
+class nsIStringBundle;
+class nsIURIFixup;
+class nsIURIFixupInfo;
+class nsIURILoader;
+class nsIWebBrowserFind;
+class nsIWidget;
+class nsIReferrerInfo;
+
+class nsCommandManager;
+class nsDocShellEditorData;
+class nsDOMNavigationTiming;
+class nsDSURIContentListener;
+class nsGlobalWindowOuter;
+
+class FramingChecker;
+class OnLinkClickEvent;
+
+/* internally used ViewMode types */
+enum ViewMode { viewNormal = 0x0, viewSource = 0x1 };
+
+enum eCharsetReloadState {
+ eCharsetReloadInit,
+ eCharsetReloadRequested,
+ eCharsetReloadStopOrigional
+};
+
+class nsDocShell final : public nsDocLoader,
+ public nsIDocShell,
+ public nsIWebNavigation,
+ public nsIBaseWindow,
+ public nsIRefreshURI,
+ public nsIWebProgressListener,
+ public nsIWebPageDescriptor,
+ public nsIAuthPromptProvider,
+ public nsILoadContext,
+ public nsINetworkInterceptController,
+ public nsIDeprecationWarner,
+ public mozilla::SupportsWeakPtr {
+ public:
+ enum InternalLoad : uint32_t {
+ INTERNAL_LOAD_FLAGS_NONE = 0x0,
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL = 0x1,
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER = 0x2,
+ INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x4,
+
+ // This flag marks the first load in this object
+ // @see nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD
+ INTERNAL_LOAD_FLAGS_FIRST_LOAD = 0x8,
+
+ // The set of flags that should not be set before calling into
+ // nsDocShell::LoadURI and other nsDocShell loading functions.
+ INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS = 0xf,
+
+ INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10,
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20,
+
+ // Whether the load should be treated as srcdoc load, rather than a URI one.
+ INTERNAL_LOAD_FLAGS_IS_SRCDOC = 0x40,
+
+ // Whether this is the load of a frame's original src attribute
+ INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC = 0x80,
+
+ INTERNAL_LOAD_FLAGS_NO_OPENER = 0x100,
+
+ // Whether a top-level data URI navigation is allowed for that load
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x200,
+
+ // Whether the load should go through LoadURIDelegate.
+ INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000,
+ };
+
+ // Event type dispatched by RestorePresentation
+ class RestorePresentationEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit RestorePresentationEvent(nsDocShell* aDs)
+ : mozilla::Runnable("nsDocShell::RestorePresentationEvent"),
+ mDocShell(aDs) {}
+ void Revoke() { mDocShell = nullptr; }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ };
+
+ class InterfaceRequestorProxy : public nsIInterfaceRequestor {
+ public:
+ explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor);
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ private:
+ virtual ~InterfaceRequestorProxy();
+ InterfaceRequestorProxy() = default;
+ nsWeakPtr mWeakPtr;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocShell, nsDocLoader)
+ NS_DECL_NSIDOCSHELL
+ NS_DECL_NSIDOCSHELLTREEITEM
+ NS_DECL_NSIWEBNAVIGATION
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIREFRESHURI
+ NS_DECL_NSIWEBPAGEDESCRIPTOR
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+ NS_DECL_NSIDEPRECATIONWARNER
+
+ // Create a new nsDocShell object.
+ static already_AddRefed<nsDocShell> Create(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID = 0);
+
+ bool Initialize();
+
+ NS_IMETHOD Stop() override {
+ // Need this here because otherwise nsIWebNavigation::Stop
+ // overrides the docloader's Stop()
+ return nsDocLoader::Stop();
+ }
+
+ mozilla::ScrollbarPreference ScrollbarPreference() const {
+ return mScrollbarPref;
+ }
+ void SetScrollbarPreference(mozilla::ScrollbarPreference);
+
+ /*
+ * The size, in CSS pixels, of the margins for the <body> of an HTML document
+ * in this docshell; used to implement the marginwidth attribute on HTML
+ * <frame>/<iframe> elements. A value smaller than zero indicates that the
+ * attribute was not set.
+ */
+ const mozilla::CSSIntSize& GetFrameMargins() const { return mFrameMargins; }
+
+ bool UpdateFrameMargins(const mozilla::CSSIntSize& aMargins) {
+ if (mFrameMargins == aMargins) {
+ return false;
+ }
+ mFrameMargins = aMargins;
+ return true;
+ }
+
+ /**
+ * Process a click on a link.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aURI a URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (may be an empty
+ * string)
+ * @param aFileName non-null when the link should be downloaded as the given
+ * file
+ * @param aPostDataStream the POST data to send
+ * @param aHeadersDataStream ??? (only used for plugins)
+ * @param aIsTrusted false if the triggerer is an untrusted DOM event.
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ * @param aCsp, the CSP to be used for the load, that is the CSP of the
+ * entity responsible for causing the load to occur. Most likely
+ * this is the CSP of the document that started the load. In case
+ * aCsp was not passed explicitly we fall back to using
+ * aContent's document's CSP if that document holds any.
+ */
+ nsresult OnLinkClick(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec, const nsAString& aFileName,
+ nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream,
+ bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp);
+ /**
+ * Process a click on a link.
+ *
+ * Works the same as OnLinkClick() except it happens immediately rather than
+ * through an event.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aDocShellLoadState the extended load info for this load.
+ * @param aNoOpenerImplied if the link implies "noopener"
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ */
+ nsresult OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal);
+
+ /**
+ * Process a mouse-over a link.
+ *
+ * @param aContent the linked content.
+ * @param aURI an URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (it may be an empty
+ * string)
+ */
+ nsresult OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec);
+ /**
+ * Process the mouse leaving a link.
+ */
+ nsresult OnLeaveLink();
+
+ // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods
+ // are shared with nsIDocShell and can't be declared twice.
+ NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopFrameElement(mozilla::dom::Element**) override;
+ NS_IMETHOD GetIsContent(bool*) override;
+ NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
+ NS_IMETHOD SetUsePrivateBrowsing(bool) override;
+ NS_IMETHOD SetPrivateBrowsing(bool) override;
+ NS_IMETHOD GetUseRemoteTabs(bool*) override;
+ NS_IMETHOD SetRemoteTabs(bool) override;
+ NS_IMETHOD GetUseRemoteSubframes(bool*) override;
+ NS_IMETHOD SetRemoteSubframes(bool) override;
+ NS_IMETHOD GetScriptableOriginAttributes(
+ JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD_(void)
+ GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
+
+ // Restores a cached presentation from history (mLSHE).
+ // This method swaps out the content viewer and simulates loads for
+ // subframes. It then simulates the completion of the toplevel load.
+ nsresult RestoreFromHistory();
+
+ // Perform a URI load from a refresh timer. This is just like the
+ // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
+ // the timer involved out of mRefreshURIList if it's there.
+ // aTimer must not be null.
+ nsresult ForceRefreshURIFromTimer(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ int32_t aDelay, bool aMetaRefresh,
+ nsITimer* aTimer);
+
+ // We need dummy OnLocationChange in some cases to update the UI without
+ // updating security info.
+ void FireDummyOnLocationChange() {
+ FireOnLocationChange(this, nullptr, mCurrentURI,
+ LOCATION_CHANGE_SAME_DOCUMENT);
+ }
+
+ // This function is created exclusively for dom.background_loading_iframe is
+ // set. As soon as the current DocShell knows itself can be treated as
+ // background loading, it triggers the parent docshell to see if the parent
+ // document can fire load event earlier.
+ void TriggerParentCheckDocShellIsEmpty();
+
+ nsresult HistoryEntryRemoved(int32_t aIndex);
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // has started being applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStarted();
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // is no longer applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStopped();
+
+ void SetInFrameSwap(bool aInSwap) { mInFrameSwap = aInSwap; }
+ bool InFrameSwap();
+
+ const mozilla::Encoding* GetForcedCharset() { return mForcedCharset; }
+
+ bool GetForcedAutodetection() { return mForcedAutodetection; }
+
+ mozilla::HTMLEditor* GetHTMLEditorInternal();
+ nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
+
+ // Handle page navigation due to charset changes
+ nsresult CharsetChangeReloadDocument(const char* aCharset = nullptr,
+ int32_t aSource = kCharsetUninitialized);
+ nsresult CharsetChangeStopDocumentLoad();
+
+ nsDOMNavigationTiming* GetNavigationTiming() const;
+
+ nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+
+ const mozilla::OriginAttributes& GetOriginAttributes() {
+ return mBrowsingContext->OriginAttributesRef();
+ }
+
+ // Determine whether this docshell corresponds to the given history entry,
+ // via having a pointer to it in mOSHE or mLSHE.
+ bool HasHistoryEntry(nsISHEntry* aEntry) const {
+ return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+ }
+
+ // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ bool GetCreatedDynamically() const {
+ return mBrowsingContext && mBrowsingContext->CreatedDynamically();
+ }
+
+ mozilla::gfx::Matrix5x4* GetColorMatrix() { return mColorMatrix.get(); }
+
+ static bool SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags);
+
+ // Tell the favicon service that aNewURI has the same favicon as aOldURI.
+ static void CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing);
+
+ static nsDocShell* Cast(nsIDocShell* aDocShell) {
+ return static_cast<nsDocShell*>(aDocShell);
+ }
+
+ static bool CanLoadInParentProcess(nsIURI* aURI);
+
+ // Returns true if the current load is a force reload (started by holding
+ // shift while triggering reload)
+ bool IsForceReloading();
+
+ mozilla::dom::WindowProxyHolder GetWindowProxy() {
+ EnsureScriptEnvironment();
+ return mozilla::dom::WindowProxyHolder(mBrowsingContext);
+ }
+
+ /**
+ * Loads the given URI. See comments on nsDocShellLoadState members for more
+ * information on information used.
+ * `aCacheKey` gets passed to DoURILoad call.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult InternalLoad(
+ nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey = mozilla::Nothing());
+
+ // Clear the document's storage access flag if needed.
+ void MaybeClearStorageAccessFlag();
+
+ void MaybeRestoreWindowName();
+
+ void StoreWindowNameToSHEntries();
+
+ void SetWillChangeProcess() { mWillChangeProcess = true; }
+ bool WillChangeProcess() { return mWillChangeProcess; }
+
+ // Create a content viewer within this nsDocShell for the given
+ // `WindowGlobalChild` actor.
+ nsresult CreateContentViewerForActor(
+ mozilla::dom::WindowGlobalChild* aWindowActor);
+
+ // Creates a real network channel (not a DocumentChannel) using the specified
+ // parameters.
+ // Used by nsDocShell when not using DocumentChannel, by DocumentLoadListener
+ // (parent-process DocumentChannel), and by DocumentChannelChild/ContentChild
+ // to transfer the resulting channel into the final process.
+ static nsresult CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI);
+
+ // Creates a real (not DocumentChannel) channel, and configures it using the
+ // supplied nsDocShellLoadState.
+ // Configuration options here are ones that should be applied to only the
+ // real channel, especially ones that need to QI to channel subclasses.
+ static bool CreateAndConfigureRealChannelForLoadState(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState, mozilla::net::LoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsDocShell* aDocShell,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& rv,
+ nsIChannel** aChannel);
+
+ // This is used to deal with errors resulting from a failed page load.
+ // Errors are handled as follows:
+ // 1. Check to see if it's a file not found error or bad content
+ // encoding error.
+ // 2. Send the URI to a keyword server (if enabled)
+ // 3. If the error was DNS failure, then add www and .com to the URI
+ // (if appropriate).
+ // 4. If the www .com additions don't work, try those with an HTTPS scheme
+ // (if appropriate).
+ static already_AddRefed<nsIURI> AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
+ bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
+ bool aNotifyKeywordSearchLoading = false,
+ nsIInputStream** aNewPostData = nullptr);
+
+ static already_AddRefed<nsIURI> MaybeFixBadCertDomainErrorURI(
+ nsIChannel* aChannel, nsIURI* aUrl);
+
+ // Takes aStatus and filters out results that should not display
+ // an error page.
+ // If this returns a failed result, then we should display an error
+ // page with that result.
+ // aSkippedUnknownProtocolNavigation will be set to true if we chose
+ // to skip displaying an error page for an NS_ERROR_UNKNOWN_PROTOCOL
+ // navigation.
+ static nsresult FilterStatusForErrorPage(
+ nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType,
+ bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument,
+ bool* aSkippedUnknownProtocolNavigation = nullptr);
+
+ // Notify consumers of a search being loaded through the observer service:
+ static void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword);
+
+ nsDocShell* GetInProcessChildAt(int32_t aIndex);
+
+ static bool ShouldAddURIVisit(nsIChannel* aChannel);
+
+ /**
+ * Helper function that finds the last URI and its transition flags for a
+ * channel.
+ *
+ * This method first checks the channel's property bag to see if previous
+ * info has been saved. If not, it gives back the referrer of the channel.
+ *
+ * @param aChannel
+ * The channel we are transitioning to
+ * @param aURI
+ * Output parameter with the previous URI, not addref'd
+ * @param aChannelRedirectFlags
+ * If a redirect, output parameter with the previous redirect flags
+ * from nsIChannelEventSink
+ */
+ static void ExtractLastVisit(nsIChannel* aChannel, nsIURI** aURI,
+ uint32_t* aChannelRedirectFlags);
+
+ bool HasContentViewer() const { return !!mContentViewer; }
+
+ static uint32_t ComputeURILoaderFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext, uint32_t aLoadType);
+
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo);
+
+ already_AddRefed<nsIInputStream> GetPostDataFromCurrentEntry() const;
+ mozilla::Maybe<uint32_t> GetCacheKeyFromCurrentEntry() const;
+
+ // Loading and/or active entries are only set when pref
+ // fission.sessionHistoryInParent is on.
+ bool FillLoadStateFromCurrentEntry(nsDocShellLoadState& aLoadState);
+
+ static bool ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel);
+
+ bool IsOSHE(nsISHEntry* aEntry) const { return mOSHE == aEntry; }
+
+ mozilla::dom::ChildSHistory* GetSessionHistory() {
+ return mBrowsingContext->GetChildSessionHistory();
+ }
+
+ // This returns true only when using session history in parent.
+ bool IsLoadingFromSessionHistory();
+
+ private: // member functions
+ friend class nsDSURIContentListener;
+ friend class FramingChecker;
+ friend class OnLinkClickEvent;
+ friend class nsIDocShell;
+ friend class mozilla::dom::BrowsingContext;
+ friend class mozilla::net::DocumentLoadListener;
+ friend class nsGlobalWindowOuter;
+
+ // It is necessary to allow adding a timeline marker wherever a docshell
+ // instance is available. This operation happens frequently and needs to
+ // be very fast, so instead of using a Map or having to search for some
+ // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
+ // is stored on docshells directly.
+ friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*);
+ friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, const char*, MarkerTracingType, MarkerStackRequest);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, const char*, const TimeStamp&, MarkerTracingType,
+ MarkerStackRequest);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, UniquePtr<AbstractTimelineMarker>&&);
+ friend void mozilla::TimelineConsumers::PopMarkers(
+ nsDocShell*, JSContext*, nsTArray<dom::ProfileTimelineMarker>&);
+
+ nsDocShell(mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID);
+
+ // Security check to prevent frameset spoofing. See comments at
+ // implementation site.
+ static bool ValidateOrigin(mozilla::dom::BrowsingContext* aOrigin,
+ mozilla::dom::BrowsingContext* aTarget);
+
+ static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) {
+ return uint32_t(aTimeUsec / PR_USEC_PER_SEC);
+ }
+
+ virtual ~nsDocShell();
+
+ //
+ // nsDocLoader
+ //
+
+ virtual void DestroyChildren() override;
+
+ // Overridden from nsDocLoader, this provides more information than the
+ // normal OnStateChange with flags STATE_REDIRECTING
+ virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) override;
+
+ // Override the parent setter from nsDocLoader
+ virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
+
+ //
+ // Content Viewer Management
+ //
+
+ nsresult EnsureContentViewer();
+
+ // aPrincipal can be passed in if the caller wants. If null is
+ // passed in, the about:blank principal will end up being used.
+ // aCSP, if any, will be used for the new about:blank load.
+ nsresult CreateAboutBlankContentViewer(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI,
+ const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP =
+ mozilla::Nothing(),
+ bool aTryToSaveOldPresentation = true, bool aCheckPermitUnload = true,
+ mozilla::dom::WindowGlobalChild* aActor = nullptr);
+
+ nsresult CreateContentViewer(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler);
+
+ nsresult NewContentViewerObj(const nsACString& aContentType,
+ nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
+ nsIStreamListener** aContentHandler,
+ nsIContentViewer** aViewer);
+
+ already_AddRefed<nsILoadURIDelegate> GetLoadURIDelegate();
+
+ nsresult SetupNewViewer(
+ nsIContentViewer* aNewViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor = nullptr);
+
+ //
+ // Session History
+ //
+
+ // Either aChannel or aOwner must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If aCloneChildren is true, then our current session history's
+ // children will be cloned onto the new entry. This should be
+ // used when we aren't actually changing the document while adding
+ // the new session history entry.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aCloneChildren, nsISHEntry** aNewEntry);
+
+ void UpdateActiveEntry(
+ bool aReplace, const mozilla::Maybe<nsPoint>& aPreviousScrollPos,
+ nsIURI* aURI, nsIURI* aOriginalURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp, const nsAString& aTitle,
+ bool aScrollRestorationIsManual, nsIStructuredCloneContainer* aData,
+ bool aURIWasModified);
+
+ nsresult AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+ int32_t aChildOffset, uint32_t aLoadType,
+ bool aCloneChildren);
+
+ nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
+ bool aCloneChildren);
+
+ // Call this method to swap in a new history entry to m[OL]SHE, rather than
+ // setting it directly. This completes the navigation in all docshells
+ // in the case of a subframe navigation.
+ // Returns old mOSHE/mLSHE.
+ already_AddRefed<nsISHEntry> SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr,
+ nsISHEntry* aEntry);
+
+ // This method calls SetHistoryEntry and updates mOSHE and mLSHE in BC to be
+ // the same as in docshell
+ void SetHistoryEntryAndUpdateBC(const mozilla::Maybe<nsISHEntry*>& aLSHE,
+ const mozilla::Maybe<nsISHEntry*>& aOSHE);
+
+ // If aNotifiedBeforeUnloadListeners is true, "beforeunload" event listeners
+ // were notified by the caller and given the chance to abort the navigation,
+ // and should not be notified again.
+ static nsresult ReloadDocument(
+ nsDocShell* aDocShell, mozilla::dom::Document* aDocument,
+ uint32_t aLoadType, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIURI* aCurrentURI, nsIReferrerInfo* aReferrerInfo,
+ bool aNotifiedBeforeUnloadListeners = false);
+
+ //
+ // URI Load
+ //
+
+ // Actually open a channel and perform a URI load. Callers need to pass a
+ // non-null aLoadState->TriggeringPrincipal() which initiated the URI load.
+ // Please note that the TriggeringPrincipal will be used for performing
+ // security checks. If aLoadState->URI() is provided by the web, then please
+ // do not pass a SystemPrincipal as the triggeringPrincipal. If
+ // aLoadState()->PrincipalToInherit is null, then no inheritance of any sort
+ // will happen and the load will get a principal based on the URI being
+ // loaded. If the Srcdoc flag is set (INTERNAL_LOAD_FLAGS_IS_SRCDOC), the load
+ // will be considered as a srcdoc load, and the contents of Srcdoc will be
+ // loaded instead of the URI. aLoadState->OriginalURI() will be set as the
+ // originalURI on the channel that does the load. If OriginalURI is null, URI
+ // will be set as the originalURI. If LoadReplace is true, LOAD_REPLACE flag
+ // will be set on the nsIChannel.
+ // If `aCacheKey` is supplied, use it for the session history entry.
+ nsresult DoURILoad(nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey, nsIRequest** aRequest);
+
+ static nsresult AddHeadersToChannel(nsIInputStream* aHeadersData,
+ nsIChannel* aChannel);
+
+ nsresult OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags);
+ nsresult OpenRedirectedChannel(nsDocShellLoadState* aLoadState);
+
+ void UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType);
+
+ private:
+ // Returns true if would have called FireOnLocationChange,
+ // but did not because aFireOnLocationChange was false on entry.
+ // In this case it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ // Either aChannel or aTriggeringPrincipal must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If OnNewURI calls AddToSessionHistory, it will pass its
+ // aCloneSHChildren argument as aCloneChildren.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ bool OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInehrit,
+ nsIContentSecurityPolicy* aCsp, bool aFireOnLocationChange,
+ bool aAddToGlobalHistory, bool aCloneSHChildren);
+
+ public:
+ // Helper method that is called when a new document (including any
+ // sub-documents - ie. frames) has been completely loaded.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult EndPageLoad(nsIWebProgress* aProgress, nsIChannel* aChannel,
+ nsresult aResult);
+
+ // Builds an error page URI (e.g. about:neterror?etc) for the given aURI
+ // and displays it via the LoadErrorPage() overload below.
+ nsresult LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
+ const char* aErrorPage, const char* aErrorType,
+ const char16_t* aDescription, const char* aCSSClass,
+ nsIChannel* aFailedChannel);
+
+ // This method directly loads aErrorURI as an error page. aFailedURI and
+ // aFailedChannel come from DisplayLoadError() or the LoadErrorPage() overload
+ // above.
+ nsresult LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
+ nsIChannel* aFailedChannel);
+
+ bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
+ nsIChannel* aFailedChannel) {
+ bool didDisplayLoadError = false;
+ DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
+ return didDisplayLoadError;
+ }
+
+ //
+ // Uncategorized
+ //
+
+ // Get the principal that we'll set on the channel if we're inheriting. If
+ // aConsiderCurrentDocument is true, we try to use the current document if
+ // at all possible. If that fails, we fall back on the parent document.
+ // If that fails too, we force creation of a content viewer and use the
+ // resulting principal. If aConsiderCurrentDocument is false, we just look
+ // at the parent.
+ // If aConsiderPartitionedPrincipal is true, we consider the partitioned
+ // principal instead of the node principal.
+ nsIPrincipal* GetInheritedPrincipal(
+ bool aConsiderCurrentDocument,
+ bool aConsiderPartitionedPrincipal = false);
+
+ /**
+ * Helper function that caches a URI and a transition for saving later.
+ *
+ * @param aChannel
+ * Channel that will have these properties saved
+ * @param aURI
+ * The URI to save for later
+ * @param aChannelRedirectFlags
+ * The nsIChannelEventSink redirect flags to save for later
+ */
+ static void SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags);
+
+ /**
+ * Helper function for adding a URI visit using IHistory.
+ *
+ * The IHistory API maintains chains of visits, tracking both HTTP referrers
+ * and redirects for a user session. VisitURI requires the current URI and
+ * the previous URI in the chain.
+ *
+ * Visits can be saved either during a redirect or when the request has
+ * reached its final destination. The previous URI in the visit may be
+ * from another redirect.
+ *
+ * @pre aURI is not null.
+ *
+ * @param aURI
+ * The URI that was just visited
+ * @param aPreviousURI
+ * The previous URI of this visit
+ * @param aChannelRedirectFlags
+ * For redirects, the redirect flags from nsIChannelEventSink
+ * (0 otherwise)
+ * @param aResponseStatus
+ * For HTTP channels, the response code (0 otherwise).
+ */
+ void AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus = 0);
+
+ /**
+ * Internal helper funtion
+ */
+ static void InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType);
+
+ static already_AddRefed<nsIURIFixupInfo> KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext);
+
+ // Sets the current document's current state object to the given SHEntry's
+ // state object. The current state object is eventually given to the page
+ // in the PopState event.
+ void SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ mozilla::dom::SessionHistoryInfo* aInfo);
+
+ // Returns true if would have called FireOnLocationChange,
+ // but did not because aFireOnLocationChange was false on entry.
+ // In this case it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ bool SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange, uint32_t aLocationFlags);
+
+ // The following methods deal with saving and restoring content viewers
+ // in session history.
+
+ // mContentViewer points to the current content viewer associated with
+ // this docshell. When loading a new document, the content viewer is
+ // either destroyed or stored into a session history entry. To make sure
+ // that destruction happens in a controlled fashion, a given content viewer
+ // is always owned in exactly one of these ways:
+ // 1) The content viewer is active and owned by a docshell's
+ // mContentViewer.
+ // 2) The content viewer is still being displayed while we begin loading
+ // a new document. The content viewer is owned by the _new_
+ // content viewer's mPreviousViewer, and has a pointer to the
+ // nsISHEntry where it will eventually be stored. The content viewer
+ // has been close()d by the docshell, which detaches the document from
+ // the window object.
+ // 3) The content viewer is cached in session history. The nsISHEntry
+ // has the only owning reference to the content viewer. The viewer
+ // has released its nsISHEntry pointer to prevent circular ownership.
+ //
+ // When restoring a content viewer from session history, open() is called
+ // to reattach the document to the window object. The content viewer is
+ // then placed into mContentViewer and removed from the history entry.
+ // (mContentViewer is put into session history as described above, if
+ // applicable).
+
+ // Determines whether we can safely cache the current mContentViewer in
+ // session history. This checks a number of factors such as cache policy,
+ // pending requests, and unload handlers.
+ // |aLoadType| should be the load type that will replace the current
+ // presentation. |aNewRequest| should be the request for the document to
+ // be loaded in place of the current document, or null if such a request
+ // has not been created yet. |aNewDocument| should be the document that will
+ // replace the current document.
+ bool CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest,
+ mozilla::dom::Document* aNewDocument);
+
+ void ReportBFCacheComboTelemetry(uint16_t aCombo);
+
+ // Captures the state of the supporting elements of the presentation
+ // (the "window" object, docshell tree, meta-refresh loads, and security
+ // state) and stores them on |mOSHE|.
+ nsresult CaptureState();
+
+ // Begin the toplevel restore process for |aSHEntry|.
+ // This simulates a channel open, and defers the real work until
+ // RestoreFromHistory is called from a PLEvent.
+ nsresult RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring);
+
+ // Call BeginRestore(nullptr, false) for each child of this shell.
+ nsresult BeginRestoreChildren();
+
+ // Method to get our current position and size without flushing
+ void DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight);
+
+ // Call this when a URI load is handed to us (via OnLinkClick or
+ // InternalLoad). This makes sure that we're not inside unload, or that if
+ // we are it's still OK to load this URI.
+ bool IsOKToLoadURI(nsIURI* aURI);
+
+ // helpers for executing commands
+ nsresult GetControllerForCommand(const char* aCommand,
+ nsIController** aResult);
+
+ // Possibly create a ClientSource object to represent an initial about:blank
+ // window that has not been allocated yet. Normally we try not to create
+ // this about:blank window until something calls GetDocument(). We still need
+ // the ClientSource to exist for this conceptual window, though.
+ //
+ // The ClientSource is created with the given principal if specified. If
+ // the principal is not provided we will attempt to inherit it when we
+ // are sure it will match what the real about:blank window principal
+ // would have been. There are some corner cases where we cannot easily
+ // determine the correct principal and will not create the ClientSource.
+ // In these cases the initial about:blank will appear to not exist until
+ // its real document and window are created.
+ void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
+
+ // Determine if a service worker is allowed to control a window in this
+ // docshell with the given URL. If there are any reasons it should not,
+ // this will return false. If true is returned then the window *may* be
+ // controlled. The caller must still consult either the parent controller
+ // or the ServiceWorkerManager to determine if a service worker should
+ // actually control the window.
+ bool ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI);
+
+ // Return the ClientInfo for the initial about:blank window, if it exists
+ // or we have speculatively created a ClientSource in
+ // MaybeCreateInitialClientSource(). This can return a ClientInfo object
+ // even if GetExtantDoc() returns nullptr.
+ mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
+
+ /**
+ * Initializes mTiming if it isn't yet.
+ * After calling this, mTiming is non-null. This method returns true if the
+ * initialization of the Timing can be reset (basically this is true if a new
+ * Timing object is created).
+ * In case the loading is aborted, MaybeResetInitTiming() can be called
+ * passing the return value of MaybeInitTiming(): if it's possible to reset
+ * the Timing, this method will do it.
+ */
+ [[nodiscard]] bool MaybeInitTiming();
+ void MaybeResetInitTiming(bool aReset);
+
+ // Convenience method for getting our parent docshell. Can return null
+ already_AddRefed<nsDocShell> GetInProcessParentDocshell();
+
+ // Internal implementation of nsIDocShell::FirePageHideNotification.
+ // If aSkipCheckingDynEntries is true, it will not try to remove dynamic
+ // subframe entries. This is to avoid redundant RemoveDynEntries calls in all
+ // children docshells.
+ void FirePageHideNotificationInternal(bool aIsUnload,
+ bool aSkipCheckingDynEntries);
+
+ nsresult Dispatch(mozilla::TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void SetupReferrerInfoFromChannel(nsIChannel* aChannel);
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
+ void ReattachEditorToWindow(nsISHEntry* aSHEntry);
+ void RecomputeCanExecuteScripts();
+ void ClearFrameHistory(nsISHEntry* aEntry);
+ // Determine if this type of load should update history.
+ static bool ShouldUpdateGlobalHistory(uint32_t aLoadType);
+ void UpdateGlobalHistoryTitle(nsIURI* aURI);
+ bool IsFrame() { return mBrowsingContext->IsFrame(); }
+ bool CanSetOriginAttributes();
+ bool ShouldBlockLoadingForBackButton();
+ bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
+ bool HasUnloadedParent();
+ bool JustStartedNetworkLoad();
+ bool NavigationBlockedByPrinting(bool aDisplayErrorDialog = true);
+ bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true,
+ bool aCheckIfUnloadFired = true);
+ nsIScrollableFrame* GetRootScrollFrame();
+ nsIChannel* GetCurrentDocChannel();
+ nsresult EnsureScriptEnvironment();
+ nsresult EnsureEditorData();
+ nsresult EnsureTransferableHookData();
+ nsresult EnsureFind();
+ nsresult EnsureCommandHandler();
+ nsresult RefreshURIFromQueue();
+ nsresult Embed(nsIContentViewer* aContentViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist);
+ nsPresContext* GetEldestPresContext();
+ nsresult CheckLoadingPermissions();
+ nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType);
+ nsresult LoadHistoryEntry(
+ const mozilla::dom::LoadingSessionHistoryInfo& aEntry,
+ uint32_t aLoadType);
+ nsresult LoadHistoryEntry(nsDocShellLoadState* aLoadState, uint32_t aLoadType,
+ bool aReloadingActiveEntry);
+ nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn);
+ nsresult ConfirmRepost(bool* aRepost);
+ nsresult GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle);
+ nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
+ int32_t aCurVerticalPos);
+ nsPoint GetCurScrollPos();
+
+ already_AddRefed<mozilla::dom::ChildSHistory> GetRootSessionHistory();
+
+ bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
+
+ // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
+ // load is requested in a subframe of the current DocShell, the subframe
+ // loadType may need to reflect the loadType of the parent document, or in
+ // some cases (like reloads), the history load may need to be cancelled. See
+ // function comments for in-depth logic descriptions.
+ // Returns true if the method itself deals with the load.
+ bool MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState,
+ bool aContinueHandlingSubframeHistory);
+
+ // If we are passed a named target during InternalLoad, this method handles
+ // moving the load to the browsing context the target name resolves to.
+ nsresult PerformRetargeting(nsDocShellLoadState* aLoadState);
+
+ // Returns one of nsIContentPolicy::TYPE_DOCUMENT,
+ // nsIContentPolicy::TYPE_INTERNAL_IFRAME, or
+ // nsIContentPolicy::TYPE_INTERNAL_FRAME depending on who is responsible for
+ // this docshell.
+ nsContentPolicyType DetermineContentType();
+
+ // If this is an iframe, and the embedder is OOP, then notifes the
+ // embedder that loading has finished and we shouldn't be blocking
+ // load of the embedder. Only called when we fail to load, as we wait
+ // for the load event of our Document before notifying success.
+ //
+ // If aFireFrameErrorEvent is true, then fires an error event at the
+ // embedder element, for both in-process and OOP embedders.
+ void UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent = false);
+
+ struct SameDocumentNavigationState {
+ nsAutoCString mCurrentHash;
+ nsAutoCString mNewHash;
+ bool mCurrentURIHasRef = false;
+ bool mNewURIHasRef = false;
+ bool mSameExceptHashes = false;
+ bool mHistoryNavBetweenSameDoc = false;
+ };
+
+ // Check to see if we're loading a prior history entry or doing a fragment
+ // navigation in the same document.
+ bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState);
+
+ // ... If so, handle the scrolling or other action required instead of
+ // continuing with new document navigation.
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState);
+
+ // Called when the Private Browsing state of a nsDocShell changes.
+ void NotifyPrivateBrowsingChanged();
+
+ // Internal helpers for BrowsingContext to pass update values to nsIDocShell's
+ // LoadGroup.
+ void SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags);
+
+ void SetTitleOnHistoryEntry();
+
+ void SetScrollRestorationIsManualOnHistoryEntry(nsISHEntry* aSHEntry,
+ bool aIsManual);
+
+ void SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry, uint32_t aCacheKey);
+
+ nsresult LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating,
+ bool aContinueHandlingSubframeHistory);
+
+ // Sets the active entry to the current loading entry. aPersist is used in the
+ // case a new session history entry is added to the session history.
+ void MoveLoadingToActiveEntry(bool aPersist);
+
+ void ActivenessMaybeChanged();
+
+ private: // data members
+ nsString mTitle;
+ nsCString mOriginalUriString;
+ nsTObserverArray<nsWeakPtr> mPrivacyObservers;
+ nsTObserverArray<nsWeakPtr> mReflowObservers;
+ nsTObserverArray<nsWeakPtr> mScrollObservers;
+ mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ RefPtr<nsDSURIContentListener> mContentListener;
+ RefPtr<nsGlobalWindowOuter> mScriptGlobal;
+ nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
+ uint64_t mContentWindowID;
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
+ nsCOMPtr<nsIWebBrowserFind> mFind;
+ RefPtr<nsCommandManager> mCommandManager;
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ // Weak reference to our BrowserChild actor.
+ nsWeakPtr mBrowserChild;
+
+ // Dimensions of the docshell
+ nsIntRect mBounds;
+
+ /**
+ * Content-Type Hint of the most-recently initiated load. Used for
+ * session history entries.
+ */
+ nsCString mContentTypeHint;
+
+ // An observed docshell wrapper is created when recording markers is enabled.
+ mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
+
+ // mCurrentURI should be marked immutable on set if possible.
+ nsCOMPtr<nsIURI> mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+#ifdef DEBUG
+ // We're counting the number of |nsDocShells| to help find leaks
+ static unsigned long gNumberOfDocShells;
+
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+ // Reference to the SHEntry for this docshell until the page is destroyed.
+ // Somebody give me better name
+ nsCOMPtr<nsISHEntry> mOSHE;
+
+ // Reference to the SHEntry for this docshell until the page is loaded
+ // Somebody give me better name.
+ // If mLSHE is non-null, non-pushState subframe loads don't create separate
+ // root history entries. That is, frames loaded during the parent page
+ // load don't generate history entries the way frame navigation after the
+ // parent has loaded does. (This isn't the only purpose of mLSHE.)
+ nsCOMPtr<nsISHEntry> mLSHE;
+
+ // These are only set when fission.sessionHistoryInParent is set.
+ mozilla::UniquePtr<mozilla::dom::SessionHistoryInfo> mActiveEntry;
+ bool mActiveEntryIsLoadingFromSessionHistory = false;
+ // mLoadingEntry is set when we're about to start loading.
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> mLoadingEntry;
+
+ // Holds a weak pointer to a RestorePresentationEvent object if any that
+ // holds a weak pointer back to us. We use this pointer to possibly revoke
+ // the event whenever necessary.
+ nsRevocableEventPtr<RestorePresentationEvent> mRestorePresentationEvent;
+
+ // Editor data, if this document is designMode or contentEditable.
+ mozilla::UniquePtr<nsDocShellEditorData> mEditorData;
+
+ // The URI we're currently loading. This is only relevant during the
+ // firing of a pagehide/unload. The caller of FirePageHideNotification()
+ // is responsible for setting it and unsetting it. It may be null if the
+ // pagehide/unload is happening for some reason other than just loading a
+ // new URI.
+ nsCOMPtr<nsIURI> mLoadingURI;
+
+ // Set in LoadErrorPage from the method argument and used later
+ // in CreateContentViewer. We have to delay an shistory entry creation
+ // for which these objects are needed.
+ nsCOMPtr<nsIURI> mFailedURI;
+ nsCOMPtr<nsIChannel> mFailedChannel;
+
+ // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or
+ // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set.
+ // Checked in nsMixedContentBlocker, to see if the channels match.
+ nsCOMPtr<nsIChannel> mMixedContentChannel;
+
+ mozilla::UniquePtr<mozilla::gfx::Matrix5x4> mColorMatrix;
+
+ const mozilla::Encoding* mForcedCharset;
+ const mozilla::Encoding* mParentCharset;
+
+ // WEAK REFERENCES BELOW HERE.
+ // Note these are intentionally not addrefd. Doing so will create a cycle.
+ // For that reasons don't use nsCOMPtr.
+
+ nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
+
+ RefPtr<mozilla::dom::EventTarget> mChromeEventHandler;
+
+ mozilla::ScrollbarPreference mScrollbarPref; // persistent across doc loads
+
+ eCharsetReloadState mCharsetReloadState;
+
+ int32_t mParentCharsetSource;
+ mozilla::CSSIntSize mFrameMargins;
+
+ // This can either be a content docshell or a chrome docshell.
+ const int32_t mItemType;
+
+ // Index into the nsISHEntry array, indicating the previous and current
+ // entry at the time that this DocShell begins to load. Consequently
+ // root docshell's indices can differ from child docshells'.
+ int32_t mPreviousEntryIndex;
+ int32_t mLoadedEntryIndex;
+
+ // Offset in the parent's child list.
+ // -1 if the docshell is added dynamically to the parent shell.
+ int32_t mChildOffset;
+
+ BusyFlags mBusyFlags;
+ AppType mAppType;
+ uint32_t mLoadType;
+ uint32_t mFailedLoadType;
+
+ // A depth count of how many times NotifyRunToCompletionStart
+ // has been called without a matching NotifyRunToCompletionStop.
+ uint32_t mJSRunToCompletionDepth;
+
+ // Whether or not handling of the <meta name="viewport"> tag is overridden.
+ // Possible values are defined as constants in nsIDocShell.idl.
+ MetaViewportOverride mMetaViewportOverride;
+
+ // The following two fields cannot be declared as bit fields
+ // because of uses with AutoRestore.
+ bool mCreatingDocument; // (should be) debugging only
+#ifdef DEBUG
+ bool mInEnsureScriptEnv;
+ uint64_t mDocShellID = 0;
+#endif
+
+ bool mInitialized : 1;
+ bool mAllowSubframes : 1;
+ bool mAllowJavascript : 1;
+ bool mAllowMetaRedirects : 1;
+ bool mAllowImages : 1;
+ bool mAllowMedia : 1;
+ bool mAllowDNSPrefetch : 1;
+ bool mAllowWindowControl : 1;
+ bool mCSSErrorReportingEnabled : 1;
+ bool mAllowAuth : 1;
+ bool mAllowKeywordFixup : 1;
+ bool mIsOffScreenBrowser : 1;
+ bool mDisableMetaRefreshWhenInactive : 1;
+ bool mIsAppTab : 1;
+ bool mDeviceSizeIsPageSize : 1;
+ bool mWindowDraggingAllowed : 1;
+ bool mInFrameSwap : 1;
+
+ // Because scriptability depends on the mAllowJavascript values of our
+ // ancestors, we cache the effective scriptability and recompute it when
+ // it might have changed;
+ bool mCanExecuteScripts : 1;
+
+ // This boolean is set to true right before we fire pagehide and generally
+ // unset when we embed a new content viewer. While it's true no navigation
+ // is allowed in this docshell.
+ bool mFiredUnloadEvent : 1;
+
+ // this flag is for bug #21358. a docshell may load many urls
+ // which don't result in new documents being created (i.e. a new
+ // content viewer) we want to make sure we don't call a on load
+ // event more than once for a given content viewer.
+ bool mEODForCurrentDocument : 1;
+ bool mURIResultedInDocument : 1;
+
+ bool mIsBeingDestroyed : 1;
+
+ bool mIsExecutingOnLoadHandler : 1;
+
+ // Indicates to CreateContentViewer() that it is safe to cache the old
+ // presentation of the page, and to SetupNewViewer() that the old viewer
+ // should be passed a SHEntry to save itself into.
+ bool mSavingOldViewer : 1;
+
+ bool mAffectPrivateSessionLifetime : 1;
+ bool mInvisible : 1;
+ bool mHasLoadedNonBlankURI : 1;
+
+ // This flag means that mTiming has been initialized but nulled out.
+ // We will check the innerWin's timing before creating a new one
+ // in MaybeInitTiming()
+ bool mBlankTiming : 1;
+
+ // This flag indicates when the title is valid for the current URI.
+ bool mTitleValidForCurrentURI : 1;
+
+ // If mWillChangeProcess is set to true, then when the docshell is destroyed,
+ // we prepare the browsing context to change process.
+ bool mWillChangeProcess : 1;
+
+ // This flag indicates whether or not the DocShell is currently executing an
+ // nsIWebNavigation navigation method.
+ bool mIsNavigating : 1;
+
+ // This flag indicates whether the media in this docshell should be suspended
+ // when the docshell is inactive.
+ bool mSuspendMediaWhenInactive : 1;
+
+ // Whether we have a pending encoding autodetection request from the
+ // menu for all encodings.
+ bool mForcedAutodetection : 1;
+};
+
+#endif /* nsDocShell_h__ */
diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp
new file mode 100644
index 0000000000..890191aaf4
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.cpp
@@ -0,0 +1,139 @@
+/* -*- 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 "nsDocShellEditorData.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/HTMLEditor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsEditingSession.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDocShellEditorData::nsDocShellEditorData(nsIDocShell* aOwningDocShell)
+ : mDocShell(aOwningDocShell),
+ mDetachedEditingState(Document::EditingState::eOff),
+ mMakeEditable(false),
+ mIsDetached(false),
+ mDetachedMakeEditable(false) {
+ NS_ASSERTION(mDocShell, "Where is my docShell?");
+}
+
+nsDocShellEditorData::~nsDocShellEditorData() { TearDownEditor(); }
+
+void nsDocShellEditorData::TearDownEditor() {
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy(false);
+ }
+ mEditingSession = nullptr;
+ mIsDetached = false;
+}
+
+nsresult nsDocShellEditorData::MakeEditable(bool aInWaitForUriLoad) {
+ if (mMakeEditable) {
+ return NS_OK;
+ }
+
+ // if we are already editable, and are getting turned off,
+ // nuke the editor.
+ if (mHTMLEditor) {
+ NS_WARNING("Destroying existing editor on frame");
+
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy(false);
+ }
+
+ if (aInWaitForUriLoad) {
+ mMakeEditable = true;
+ }
+ return NS_OK;
+}
+
+bool nsDocShellEditorData::GetEditable() {
+ return mMakeEditable || (mHTMLEditor != nullptr);
+}
+
+nsEditingSession* nsDocShellEditorData::GetEditingSession() {
+ EnsureEditingSession();
+
+ return mEditingSession.get();
+}
+
+nsresult nsDocShellEditorData::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ // destroy any editor that we have. Checks for equality are
+ // necessary to ensure that assigment into the nsCOMPtr does
+ // not temporarily reduce the refCount of the editor to zero
+ if (mHTMLEditor == aHTMLEditor) {
+ return NS_OK;
+ }
+
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy(false);
+ MOZ_ASSERT(!mHTMLEditor,
+ "Nested call of nsDocShellEditorData::SetHTMLEditor() detected");
+ }
+
+ mHTMLEditor = aHTMLEditor; // owning addref
+ if (!mHTMLEditor) {
+ mMakeEditable = false;
+ }
+
+ return NS_OK;
+}
+
+// This creates the editing session on the content docShell that owns 'this'.
+void nsDocShellEditorData::EnsureEditingSession() {
+ NS_ASSERTION(mDocShell, "Should have docShell here");
+ NS_ASSERTION(!mIsDetached, "This will stomp editing session!");
+
+ if (!mEditingSession) {
+ mEditingSession = new nsEditingSession();
+ }
+}
+
+nsresult nsDocShellEditorData::DetachFromWindow() {
+ NS_ASSERTION(mEditingSession,
+ "Can't detach when we don't have a session to detach!");
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->DetachFromWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = true;
+ mDetachedMakeEditable = mMakeEditable;
+ mMakeEditable = false;
+
+ nsCOMPtr<dom::Document> doc = domWindow->GetDoc();
+ mDetachedEditingState = doc->GetEditingState();
+
+ mDocShell = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEditorData::ReattachToWindow(nsIDocShell* aDocShell) {
+ mDocShell = aDocShell;
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->ReattachToWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = false;
+ mMakeEditable = mDetachedMakeEditable;
+
+ RefPtr<dom::Document> doc = domWindow->GetDoc();
+ doc->SetEditingState(mDetachedEditingState);
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEditorData.h b/docshell/base/nsDocShellEditorData.h
new file mode 100644
index 0000000000..27f840675b
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.h
@@ -0,0 +1,66 @@
+/* -*- 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 nsDocShellEditorData_h__
+#define nsDocShellEditorData_h__
+
+#ifndef nsCOMPtr_h___
+# include "nsCOMPtr.h"
+#endif
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Document.h"
+
+class nsIDocShell;
+class nsEditingSession;
+
+namespace mozilla {
+class HTMLEditor;
+}
+
+class nsDocShellEditorData {
+ public:
+ explicit nsDocShellEditorData(nsIDocShell* aOwningDocShell);
+ ~nsDocShellEditorData();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult MakeEditable(bool aWaitForUriLoad);
+ bool GetEditable();
+ nsEditingSession* GetEditingSession();
+ mozilla::HTMLEditor* GetHTMLEditor() const { return mHTMLEditor; }
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TearDownEditor();
+ nsresult DetachFromWindow();
+ nsresult ReattachToWindow(nsIDocShell* aDocShell);
+ bool WaitingForLoad() const { return mMakeEditable; }
+
+ protected:
+ void EnsureEditingSession();
+
+ // The doc shell that owns us. Weak ref, since it always outlives us.
+ nsIDocShell* mDocShell;
+
+ // Only present for the content root docShell. Session is owned here.
+ RefPtr<nsEditingSession> mEditingSession;
+
+ // If this frame is editable, store HTML editor here. It's owned here.
+ RefPtr<mozilla::HTMLEditor> mHTMLEditor;
+
+ // Backup for the corresponding HTMLDocument's editing state while
+ // the editor is detached.
+ mozilla::dom::Document::EditingState mDetachedEditingState;
+
+ // Indicates whether to make an editor after a url load.
+ bool mMakeEditable;
+
+ // Denotes if the editor is detached from its window. The editor is detached
+ // while it's stored in the session history bfcache.
+ bool mIsDetached;
+
+ // Backup for mMakeEditable while the editor is detached.
+ bool mDetachedMakeEditable;
+};
+
+#endif // nsDocShellEditorData_h__
diff --git a/docshell/base/nsDocShellEnumerator.cpp b/docshell/base/nsDocShellEnumerator.cpp
new file mode 100644
index 0000000000..5ad0ad35e6
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "nsDocShellEnumerator.h"
+
+#include "nsDocShell.h"
+
+using namespace mozilla;
+
+nsDocShellEnumerator::nsDocShellEnumerator(
+ nsDocShellEnumerator::EnumerationDirection aDirection,
+ int32_t aDocShellType, nsDocShell& aRootItem)
+ : mRootItem(&aRootItem),
+ mDocShellType(aDocShellType),
+ mDirection(aDirection) {}
+
+nsresult nsDocShellEnumerator::BuildDocShellArray(
+ nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ MOZ_ASSERT(mRootItem);
+
+ aItemArray.Clear();
+
+ if (mDirection == EnumerationDirection::Forwards) {
+ return BuildArrayRecursiveForwards(mRootItem, aItemArray);
+ }
+ MOZ_ASSERT(mDirection == EnumerationDirection::Backwards);
+ return BuildArrayRecursiveBackwards(mRootItem, aItemArray);
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ int32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = 0; i < numChildren; ++i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveForwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ uint32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = numChildren - 1; i >= 0; --i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveBackwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEnumerator.h b/docshell/base/nsDocShellEnumerator.h
new file mode 100644
index 0000000000..668ddee7e9
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsDocShellEnumerator_h___
+#define nsDocShellEnumerator_h___
+
+#include "nsTArray.h"
+
+class nsDocShell;
+class nsIDocShell;
+
+class MOZ_STACK_CLASS nsDocShellEnumerator {
+ public:
+ enum class EnumerationDirection : uint8_t { Forwards, Backwards };
+
+ nsDocShellEnumerator(EnumerationDirection aDirection, int32_t aDocShellType,
+ nsDocShell& aRootItem);
+
+ public:
+ nsresult BuildDocShellArray(nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ nsresult BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+ nsresult BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ const RefPtr<nsDocShell> mRootItem;
+
+ const int32_t mDocShellType; // only want shells of this type
+
+ const EnumerationDirection mDirection;
+};
+
+#endif // nsDocShellEnumerator_h___
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
new file mode 100644
index 0000000000..b1409b3b52
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -0,0 +1,957 @@
+/* -*- 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 "nsDocShellLoadState.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+#include "nsISHEntry.h"
+#include "nsIURIFixup.h"
+#include "nsIWebNavigation.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ReferrerInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/PContent.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Global reference to the URI fixup service.
+static mozilla::StaticRefPtr<nsIURIFixup> sURIFixup;
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI)
+ : nsDocShellLoadState(aURI, nsContentUtils::GenerateLoadIdentifier()) {}
+
+nsDocShellLoadState::nsDocShellLoadState(
+ const DocShellLoadStateInit& aLoadState)
+ : mNotifiedBeforeUnloadListeners(false),
+ mLoadIdentifier(aLoadState.LoadIdentifier()) {
+ MOZ_ASSERT(aLoadState.URI(), "Cannot create a LoadState with a null URI!");
+ mResultPrincipalURI = aLoadState.ResultPrincipalURI();
+ mResultPrincipalURIIsSome = aLoadState.ResultPrincipalURIIsSome();
+ mKeepResultPrincipalURIIfSet = aLoadState.KeepResultPrincipalURIIfSet();
+ mLoadReplace = aLoadState.LoadReplace();
+ mInheritPrincipal = aLoadState.InheritPrincipal();
+ mPrincipalIsExplicit = aLoadState.PrincipalIsExplicit();
+ mForceAllowDataURI = aLoadState.ForceAllowDataURI();
+ mOriginalFrameSrc = aLoadState.OriginalFrameSrc();
+ mIsFormSubmission = aLoadState.IsFormSubmission();
+ mLoadType = aLoadState.LoadType();
+ mTarget = aLoadState.Target();
+ mTargetBrowsingContext = aLoadState.TargetBrowsingContext();
+ mLoadFlags = aLoadState.LoadFlags();
+ mFirstParty = aLoadState.FirstParty();
+ mHasValidUserGestureActivation = aLoadState.HasValidUserGestureActivation();
+ mAllowFocusMove = aLoadState.AllowFocusMove();
+ mTypeHint = aLoadState.TypeHint();
+ mFileName = aLoadState.FileName();
+ mIsFromProcessingFrameAttributes =
+ aLoadState.IsFromProcessingFrameAttributes();
+ mReferrerInfo = aLoadState.ReferrerInfo();
+ mURI = aLoadState.URI();
+ mOriginalURI = aLoadState.OriginalURI();
+ mSourceBrowsingContext = aLoadState.SourceBrowsingContext();
+ mBaseURI = aLoadState.BaseURI();
+ mTriggeringPrincipal = aLoadState.TriggeringPrincipal();
+ mPrincipalToInherit = aLoadState.PrincipalToInherit();
+ mPartitionedPrincipalToInherit = aLoadState.PartitionedPrincipalToInherit();
+ mTriggeringSandboxFlags = aLoadState.TriggeringSandboxFlags();
+ mCsp = aLoadState.Csp();
+ mOriginalURIString = aLoadState.OriginalURIString();
+ mCancelContentJSEpoch = aLoadState.CancelContentJSEpoch();
+ mPostDataStream = aLoadState.PostDataStream();
+ mHeadersStream = aLoadState.HeadersStream();
+ mSrcdocData = aLoadState.SrcdocData();
+ mChannelInitialized = aLoadState.ChannelInitialized();
+ if (aLoadState.loadingSessionHistoryInfo().isSome()) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ aLoadState.loadingSessionHistoryInfo().ref());
+ }
+}
+
+nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
+ : mReferrerInfo(aOther.mReferrerInfo),
+ mURI(aOther.mURI),
+ mOriginalURI(aOther.mOriginalURI),
+ mResultPrincipalURI(aOther.mResultPrincipalURI),
+ mResultPrincipalURIIsSome(aOther.mResultPrincipalURIIsSome),
+ mTriggeringPrincipal(aOther.mTriggeringPrincipal),
+ mTriggeringSandboxFlags(aOther.mTriggeringSandboxFlags),
+ mCsp(aOther.mCsp),
+ mKeepResultPrincipalURIIfSet(aOther.mKeepResultPrincipalURIIfSet),
+ mLoadReplace(aOther.mLoadReplace),
+ mInheritPrincipal(aOther.mInheritPrincipal),
+ mPrincipalIsExplicit(aOther.mPrincipalIsExplicit),
+ mNotifiedBeforeUnloadListeners(aOther.mNotifiedBeforeUnloadListeners),
+ mPrincipalToInherit(aOther.mPrincipalToInherit),
+ mPartitionedPrincipalToInherit(aOther.mPartitionedPrincipalToInherit),
+ mForceAllowDataURI(aOther.mForceAllowDataURI),
+ mOriginalFrameSrc(aOther.mOriginalFrameSrc),
+ mIsFormSubmission(aOther.mIsFormSubmission),
+ mLoadType(aOther.mLoadType),
+ mSHEntry(aOther.mSHEntry),
+ mTarget(aOther.mTarget),
+ mTargetBrowsingContext(aOther.mTargetBrowsingContext),
+ mPostDataStream(aOther.mPostDataStream),
+ mHeadersStream(aOther.mHeadersStream),
+ mSrcdocData(aOther.mSrcdocData),
+ mSourceBrowsingContext(aOther.mSourceBrowsingContext),
+ mBaseURI(aOther.mBaseURI),
+ mLoadFlags(aOther.mLoadFlags),
+ mFirstParty(aOther.mFirstParty),
+ mHasValidUserGestureActivation(aOther.mHasValidUserGestureActivation),
+ mAllowFocusMove(aOther.mAllowFocusMove),
+ mTypeHint(aOther.mTypeHint),
+ mFileName(aOther.mFileName),
+ mIsFromProcessingFrameAttributes(aOther.mIsFromProcessingFrameAttributes),
+ mPendingRedirectedChannel(aOther.mPendingRedirectedChannel),
+ mOriginalURIString(aOther.mOriginalURIString),
+ mCancelContentJSEpoch(aOther.mCancelContentJSEpoch),
+ mLoadIdentifier(aOther.mLoadIdentifier),
+ mChannelInitialized(aOther.mChannelInitialized) {
+ if (aOther.mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ *aOther.mLoadingSessionHistoryInfo);
+ }
+}
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier)
+ : mURI(aURI),
+ mResultPrincipalURIIsSome(false),
+ mTriggeringSandboxFlags(0),
+ mKeepResultPrincipalURIIfSet(false),
+ mLoadReplace(false),
+ mInheritPrincipal(false),
+ mPrincipalIsExplicit(false),
+ mNotifiedBeforeUnloadListeners(false),
+ mForceAllowDataURI(false),
+ mOriginalFrameSrc(false),
+ mIsFormSubmission(false),
+ mLoadType(LOAD_NORMAL),
+ mTarget(),
+ mSrcdocData(VoidString()),
+ mLoadFlags(0),
+ mFirstParty(false),
+ mHasValidUserGestureActivation(false),
+ mAllowFocusMove(false),
+ mTypeHint(VoidCString()),
+ mFileName(VoidString()),
+ mIsFromProcessingFrameAttributes(false),
+ mLoadIdentifier(aLoadIdentifier),
+ mChannelInitialized(false) {
+ MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
+}
+
+nsDocShellLoadState::~nsDocShellLoadState() {}
+
+nsresult nsDocShellLoadState::CreateFromPendingChannel(
+ nsIChannel* aPendingChannel, uint64_t aLoadIdentifier,
+ uint64_t aRegistrarId, nsDocShellLoadState** aResult) {
+ // Create the nsDocShellLoadState object with default state pulled from the
+ // passed-in channel.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPendingChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(uri, aLoadIdentifier);
+ loadState->mPendingRedirectedChannel = aPendingChannel;
+ loadState->mChannelRegistrarId = aRegistrarId;
+
+ // Pull relevant state from the channel, and store it on the
+ // nsDocShellLoadState.
+ nsCOMPtr<nsIURI> originalUri;
+ rv = aPendingChannel->GetOriginalURI(getter_AddRefs(originalUri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ loadState->SetOriginalURI(originalUri);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aPendingChannel->LoadInfo();
+ loadState->SetTriggeringPrincipal(loadInfo->TriggeringPrincipal());
+
+ // Return the newly created loadState.
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+static uint32_t WebNavigationFlagsToFixupFlags(nsIURI* aURI,
+ const nsACString& aURIString,
+ uint32_t aNavigationFlags) {
+ if (aURI) {
+ aNavigationFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ uint32_t fixupFlags = nsIURIFixup::FIXUP_FLAG_NONE;
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ }
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ }
+ return fixupFlags;
+};
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const LoadURIOptions& aLoadURIOptions, nsDocShellLoadState** aResult) {
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+
+ NS_ASSERTION(
+ (loadFlags & nsDocShell::INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
+ "Unexpected flags");
+
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIInputStream> postData(aLoadURIOptions.mPostData);
+ nsresult rv = NS_OK;
+
+ NS_ConvertUTF16toUTF8 uriString(aURI);
+ // Cleanup the empty spaces that might be on each end.
+ uriString.Trim(" ");
+ // Eliminate embedded newlines, which single-line text fields now allow:
+ uriString.StripCRLF();
+ NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
+
+ // Just create a URI and see what happens...
+ rv = NS_NewURI(getter_AddRefs(uri), uriString);
+ bool fixup = true;
+ if (NS_SUCCEEDED(rv) && uri &&
+ (uri->SchemeIs("about") || uri->SchemeIs("chrome"))) {
+ // Avoid third party fixup as a performance optimization.
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ fixup = false;
+ } else if (!sURIFixup && !XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ sURIFixup = uriFixup;
+ ClearOnShutdown(&sURIFixup);
+ } else {
+ fixup = false;
+ }
+ }
+
+ nsAutoString searchProvider, keyword;
+ bool didFixup = false;
+ if (fixup) {
+ uint32_t fixupFlags =
+ WebNavigationFlagsToFixupFlags(uri, uriString, loadFlags);
+
+ // If we don't allow keyword lookups for this URL string, make sure to
+ // update loadFlags to indicate this as well.
+ if (!(fixupFlags & nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ // Ensure URIFixup will use the right search engine in Private Browsing.
+ if (aBrowsingContext->UsePrivateBrowsing()) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+
+ RefPtr<nsIInputStream> fixupStream;
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
+ getter_AddRefs(fixupInfo));
+ if (fixupInfo) {
+ // We could fix the uri, clear NS_ERROR_MALFORMED_URI.
+ rv = NS_OK;
+ fixupInfo->GetPreferredURI(getter_AddRefs(uri));
+ fixupInfo->SetConsumer(aBrowsingContext);
+ fixupInfo->GetKeywordProviderName(searchProvider);
+ fixupInfo->GetKeywordAsSent(keyword);
+ fixupInfo->GetPostData(getter_AddRefs(fixupStream));
+ didFixup = true;
+
+ if (fixupInfo &&
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ serv->NotifyObservers(fixupInfo, "keyword-uri-fixup",
+ PromiseFlatString(aURI).get());
+ }
+ }
+ }
+ }
+
+ if (fixupStream) {
+ // GetFixupURIInfo only returns a post data stream if it succeeded
+ // and changed the URI, in which case we should override the
+ // passed-in post data.
+ postData = fixupStream;
+ }
+ }
+
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ MOZ_ASSERT(!uri);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) || !uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t available;
+ if (postData) {
+ rv = postData->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (aLoadURIOptions.mHeaders) {
+ rv = aLoadURIOptions.mHeaders->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ bool forceAllowDataURI =
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+
+ // Don't pass certain flags that aren't needed and end up confusing
+ // ConvertLoadTypeToDocShellInfoLoadType. We do need to ensure that they are
+ // passed to LoadURI though, since it uses them.
+ uint32_t extraFlags = (loadFlags & EXTRA_LOAD_FLAGS);
+ loadFlags &= ~EXTRA_LOAD_FLAGS;
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(uri);
+ loadState->SetReferrerInfo(aLoadURIOptions.mReferrerInfo);
+
+ /*
+ * If the user "Disables Protection on This Page", we have to make sure to
+ * remember the users decision when opening links in child tabs [Bug 906190]
+ */
+ if (loadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
+ loadState->SetLoadType(
+ MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, loadFlags));
+ } else {
+ loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags));
+ }
+
+ loadState->SetLoadFlags(extraFlags);
+ loadState->SetFirstParty(true);
+ loadState->SetHasValidUserGestureActivation(
+ aLoadURIOptions.mHasValidUserGestureActivation);
+ loadState->SetTriggeringSandboxFlags(aLoadURIOptions.mTriggeringSandboxFlags);
+ loadState->SetPostDataStream(postData);
+ loadState->SetHeadersStream(aLoadURIOptions.mHeaders);
+ loadState->SetBaseURI(aLoadURIOptions.mBaseURI);
+ loadState->SetTriggeringPrincipal(aLoadURIOptions.mTriggeringPrincipal);
+ loadState->SetCsp(aLoadURIOptions.mCsp);
+ loadState->SetForceAllowDataURI(forceAllowDataURI);
+ loadState->SetOriginalURIString(uriString);
+ if (aLoadURIOptions.mCancelContentJSEpoch) {
+ loadState->SetCancelContentJSEpoch(aLoadURIOptions.mCancelContentJSEpoch);
+ }
+
+ if (didFixup) {
+ nsDocShell::MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
+ }
+
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+nsIReferrerInfo* nsDocShellLoadState::GetReferrerInfo() const {
+ return mReferrerInfo;
+}
+
+void nsDocShellLoadState::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+}
+
+nsIURI* nsDocShellLoadState::URI() const { return mURI; }
+
+void nsDocShellLoadState::SetURI(nsIURI* aURI) { mURI = aURI; }
+
+nsIURI* nsDocShellLoadState::OriginalURI() const { return mOriginalURI; }
+
+void nsDocShellLoadState::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+}
+
+nsIURI* nsDocShellLoadState::ResultPrincipalURI() const {
+ return mResultPrincipalURI;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+}
+
+bool nsDocShellLoadState::ResultPrincipalURIIsSome() const {
+ return mResultPrincipalURIIsSome;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURIIsSome(bool aIsSome) {
+ mResultPrincipalURIIsSome = aIsSome;
+}
+
+bool nsDocShellLoadState::KeepResultPrincipalURIIfSet() const {
+ return mKeepResultPrincipalURIIfSet;
+}
+
+void nsDocShellLoadState::SetKeepResultPrincipalURIIfSet(bool aKeep) {
+ mKeepResultPrincipalURIIfSet = aKeep;
+}
+
+bool nsDocShellLoadState::LoadReplace() const { return mLoadReplace; }
+
+void nsDocShellLoadState::SetLoadReplace(bool aLoadReplace) {
+ mLoadReplace = aLoadReplace;
+}
+
+nsIPrincipal* nsDocShellLoadState::TriggeringPrincipal() const {
+ return mTriggeringPrincipal;
+}
+
+void nsDocShellLoadState::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ mTriggeringPrincipal = aTriggeringPrincipal;
+}
+
+nsIPrincipal* nsDocShellLoadState::PrincipalToInherit() const {
+ return mPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPrincipalToInherit(
+ nsIPrincipal* aPrincipalToInherit) {
+ mPrincipalToInherit = aPrincipalToInherit;
+}
+
+nsIPrincipal* nsDocShellLoadState::PartitionedPrincipalToInherit() const {
+ return mPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mCsp = aCsp;
+}
+
+nsIContentSecurityPolicy* nsDocShellLoadState::Csp() const { return mCsp; }
+
+void nsDocShellLoadState::SetTriggeringSandboxFlags(uint32_t flags) {
+ mTriggeringSandboxFlags = flags;
+}
+
+uint32_t nsDocShellLoadState::TriggeringSandboxFlags() const {
+ return mTriggeringSandboxFlags;
+}
+
+bool nsDocShellLoadState::InheritPrincipal() const { return mInheritPrincipal; }
+
+void nsDocShellLoadState::SetInheritPrincipal(bool aInheritPrincipal) {
+ mInheritPrincipal = aInheritPrincipal;
+}
+
+bool nsDocShellLoadState::PrincipalIsExplicit() const {
+ return mPrincipalIsExplicit;
+}
+
+void nsDocShellLoadState::SetPrincipalIsExplicit(bool aPrincipalIsExplicit) {
+ mPrincipalIsExplicit = aPrincipalIsExplicit;
+}
+
+bool nsDocShellLoadState::NotifiedBeforeUnloadListeners() const {
+ return mNotifiedBeforeUnloadListeners;
+}
+
+void nsDocShellLoadState::SetNotifiedBeforeUnloadListeners(
+ bool aNotifiedBeforeUnloadListeners) {
+ mNotifiedBeforeUnloadListeners = aNotifiedBeforeUnloadListeners;
+}
+
+bool nsDocShellLoadState::ForceAllowDataURI() const {
+ return mForceAllowDataURI;
+}
+
+void nsDocShellLoadState::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ mForceAllowDataURI = aForceAllowDataURI;
+}
+
+bool nsDocShellLoadState::OriginalFrameSrc() const { return mOriginalFrameSrc; }
+
+void nsDocShellLoadState::SetOriginalFrameSrc(bool aOriginalFrameSrc) {
+ mOriginalFrameSrc = aOriginalFrameSrc;
+}
+
+bool nsDocShellLoadState::IsFormSubmission() const { return mIsFormSubmission; }
+
+void nsDocShellLoadState::SetIsFormSubmission(bool aIsFormSubmission) {
+ mIsFormSubmission = aIsFormSubmission;
+}
+
+uint32_t nsDocShellLoadState::LoadType() const { return mLoadType; }
+
+void nsDocShellLoadState::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+}
+
+nsISHEntry* nsDocShellLoadState::SHEntry() const { return mSHEntry; }
+
+void nsDocShellLoadState::SetSHEntry(nsISHEntry* aSHEntry) {
+ mSHEntry = aSHEntry;
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aSHEntry);
+ if (she) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(she);
+ } else {
+ mLoadingSessionHistoryInfo = nullptr;
+ }
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo) {
+ SetLoadingSessionHistoryInfo(
+ MakeUnique<mozilla::dom::LoadingSessionHistoryInfo>(aLoadingInfo));
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo) {
+ mLoadingSessionHistoryInfo = std::move(aLoadingInfo);
+}
+
+const mozilla::dom::LoadingSessionHistoryInfo*
+nsDocShellLoadState::GetLoadingSessionHistoryInfo() const {
+ return mLoadingSessionHistoryInfo.get();
+}
+
+void nsDocShellLoadState::SetLoadIsFromSessionHistory(
+ int32_t aRequestedIndex, int32_t aSessionHistoryLength,
+ bool aLoadingFromActiveEntry) {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = true;
+ mLoadingSessionHistoryInfo->mRequestedIndex = aRequestedIndex;
+ mLoadingSessionHistoryInfo->mSessionHistoryLength = aSessionHistoryLength;
+ mLoadingSessionHistoryInfo->mLoadingCurrentActiveEntry =
+ aLoadingFromActiveEntry;
+ }
+}
+
+void nsDocShellLoadState::ClearLoadIsFromSessionHistory() {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = false;
+ }
+ mSHEntry = nullptr;
+}
+
+bool nsDocShellLoadState::LoadIsFromSessionHistory() const {
+ return mLoadingSessionHistoryInfo
+ ? mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory
+ : !!mSHEntry;
+}
+
+const nsString& nsDocShellLoadState::Target() const { return mTarget; }
+
+void nsDocShellLoadState::SetTarget(const nsAString& aTarget) {
+ mTarget = aTarget;
+}
+
+nsIInputStream* nsDocShellLoadState::PostDataStream() const {
+ return mPostDataStream;
+}
+
+void nsDocShellLoadState::SetPostDataStream(nsIInputStream* aStream) {
+ mPostDataStream = aStream;
+}
+
+nsIInputStream* nsDocShellLoadState::HeadersStream() const {
+ return mHeadersStream;
+}
+
+void nsDocShellLoadState::SetHeadersStream(nsIInputStream* aHeadersStream) {
+ mHeadersStream = aHeadersStream;
+}
+
+const nsString& nsDocShellLoadState::SrcdocData() const { return mSrcdocData; }
+
+void nsDocShellLoadState::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+}
+
+void nsDocShellLoadState::SetSourceBrowsingContext(
+ BrowsingContext* aSourceBrowsingContext) {
+ mSourceBrowsingContext = aSourceBrowsingContext;
+}
+
+void nsDocShellLoadState::SetTargetBrowsingContext(
+ BrowsingContext* aTargetBrowsingContext) {
+ mTargetBrowsingContext = aTargetBrowsingContext;
+}
+
+nsIURI* nsDocShellLoadState::BaseURI() const { return mBaseURI; }
+
+void nsDocShellLoadState::SetBaseURI(nsIURI* aBaseURI) { mBaseURI = aBaseURI; }
+
+void nsDocShellLoadState::GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const {
+ bool isSome = ResultPrincipalURIIsSome();
+ aRPURI.reset();
+
+ if (!isSome) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResultPrincipalURI();
+ aRPURI.emplace(std::move(uri));
+}
+
+void nsDocShellLoadState::SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI) {
+ SetResultPrincipalURI(aRPURI.refOr(nullptr));
+ SetResultPrincipalURIIsSome(aRPURI.isSome());
+}
+
+uint32_t nsDocShellLoadState::LoadFlags() const { return mLoadFlags; }
+
+void nsDocShellLoadState::SetLoadFlags(uint32_t aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+}
+
+void nsDocShellLoadState::SetLoadFlag(uint32_t aFlag) { mLoadFlags |= aFlag; }
+
+void nsDocShellLoadState::UnsetLoadFlag(uint32_t aFlag) {
+ mLoadFlags &= ~aFlag;
+}
+
+bool nsDocShellLoadState::HasLoadFlags(uint32_t aFlags) {
+ return (mLoadFlags & aFlags) == aFlags;
+}
+
+bool nsDocShellLoadState::FirstParty() const { return mFirstParty; }
+
+void nsDocShellLoadState::SetFirstParty(bool aFirstParty) {
+ mFirstParty = aFirstParty;
+}
+
+bool nsDocShellLoadState::HasValidUserGestureActivation() const {
+ return mHasValidUserGestureActivation;
+}
+
+void nsDocShellLoadState::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ mHasValidUserGestureActivation = aHasValidUserGestureActivation;
+}
+
+const nsCString& nsDocShellLoadState::TypeHint() const { return mTypeHint; }
+
+void nsDocShellLoadState::SetTypeHint(const nsCString& aTypeHint) {
+ mTypeHint = aTypeHint;
+}
+
+const nsString& nsDocShellLoadState::FileName() const { return mFileName; }
+
+void nsDocShellLoadState::SetFileName(const nsAString& aFileName) {
+ MOZ_DIAGNOSTIC_ASSERT(aFileName.FindChar(char16_t(0)) == kNotFound,
+ "The filename should never contain null characters");
+ mFileName = aFileName;
+}
+
+nsresult nsDocShellLoadState::SetupInheritingPrincipal(
+ BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // We need a principalToInherit.
+ //
+ // If principalIsExplicit is not set there are 4 possibilities:
+ // (1) If the system principal or an expanded principal was passed
+ // in and we're a typeContent docshell, inherit the principal
+ // from the current document instead.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // or if we're being called from system code (eg chrome JS or pure
+ // C++) then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we don't pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ //
+ // If principalIsExplicit *is* set, there are 4 possibilities
+ // (1) If the system principal or an expanded principal was passed in
+ // and we're a typeContent docshell, return an error.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we dont' pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ mPrincipalToInherit = mTriggeringPrincipal;
+ if (mPrincipalToInherit && aType != BrowsingContext::Type::Chrome) {
+ if (mPrincipalToInherit->IsSystemPrincipal()) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ mPrincipalToInherit = nullptr;
+ mInheritPrincipal = true;
+ } else if (nsContentUtils::IsExpandedPrincipal(mPrincipalToInherit)) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ // Don't inherit from the current page. Just do the safe thing
+ // and pretend that we were loaded by a nullprincipal.
+ //
+ // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
+ // have origin attributes.
+ mPrincipalToInherit = NullPrincipal::CreateWithInheritedAttributes(
+ aOriginAttributes, false);
+ mInheritPrincipal = false;
+ }
+ }
+
+ if (!mPrincipalToInherit && !mInheritPrincipal && !mPrincipalIsExplicit) {
+ // See if there's system or chrome JS code running
+ mInheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
+ mInheritPrincipal = false;
+ // If mFirstParty is true and the pref 'privacy.firstparty.isolate' is
+ // enabled, we will set firstPartyDomain on the origin attributes.
+ mPrincipalToInherit = NullPrincipal::CreateWithInheritedAttributes(
+ aOriginAttributes, mFirstParty);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellLoadState::SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // If the triggeringPrincipal is not set, we first try to create a principal
+ // from the referrer, since the referrer URI reflects the web origin that
+ // triggered the load. If there is no referrer URI, we fall back to using the
+ // SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
+ // and no referrer simulate a load that was triggered by the system. It's
+ // important to note that this block of code needs to appear *after* the block
+ // where we munge the principalToInherit, because otherwise we would never
+ // enter code blocks checking if the principalToInherit is null and we will
+ // end up with a wrong inheritPrincipal flag.
+ if (!mTriggeringPrincipal) {
+ if (mReferrerInfo) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ mTriggeringPrincipal =
+ BasePrincipal::CreateContentPrincipal(referrer, aOriginAttributes);
+
+ if (!mTriggeringPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ mTriggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+ }
+ return NS_OK;
+}
+
+void nsDocShellLoadState::CalculateLoadURIFlags() {
+ uint32_t oldLoadFlags = mLoadFlags;
+ mLoadFlags = 0;
+
+ if (mInheritPrincipal) {
+ MOZ_ASSERT(
+ !mPrincipalToInherit || !mPrincipalToInherit->IsSystemPrincipal(),
+ "Should not inherit SystemPrincipal");
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
+ }
+
+ if (mReferrerInfo && !mReferrerInfo->GetSendReferrer()) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
+ }
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
+ }
+
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
+ }
+
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
+ }
+
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE;
+ }
+
+ if (!mSrcdocData.IsVoid()) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ }
+
+ if (mForceAllowDataURI) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+ }
+
+ if (mOriginalFrameSrc) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC;
+ }
+}
+
+nsLoadFlags nsDocShellLoadState::CalculateChannelLoadFlags(
+ BrowsingContext* aBrowsingContext, Maybe<bool> aUriModified,
+ Maybe<bool> aIsXFOError) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ nsLoadFlags loadFlags = aBrowsingContext->GetDefaultLoadFlags();
+
+ if (FirstParty()) {
+ // tag first party URL loads
+ loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
+ }
+
+ const uint32_t loadType = LoadType();
+
+ // These values aren't available for loads initiated in the Parent process.
+ MOZ_ASSERT_IF(loadType == LOAD_HISTORY, aUriModified.isSome());
+ MOZ_ASSERT_IF(loadType == LOAD_ERROR_PAGE, aIsXFOError.isSome());
+
+ if (loadType == LOAD_ERROR_PAGE) {
+ // Error pages are LOAD_BACKGROUND, unless it's an
+ // XFO error for which we want an error page to load
+ // but additionally want the onload() event to fire.
+ if (!*aIsXFOError) {
+ loadFlags |= nsIChannel::LOAD_BACKGROUND;
+ }
+ }
+
+ // Mark the channel as being a document URI and allow content sniffing...
+ loadFlags |=
+ nsIChannel::LOAD_DOCUMENT_URI | nsIChannel::LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (nsDocShell::SandboxFlagsImplyCookies(
+ aBrowsingContext->GetSandboxFlags())) {
+ loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE;
+ }
+
+ // Load attributes depend on load type...
+ switch (loadType) {
+ case LOAD_HISTORY: {
+ // Only send VALIDATE_NEVER if mLSHE's URI was never changed via
+ // push/replaceState (bug 669671).
+ if (!*aUriModified) {
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ }
+ break;
+ }
+
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ [[fallthrough]];
+
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_REFRESH:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ break;
+
+ case LOAD_NORMAL:
+ case LOAD_LINK:
+ // Set cache checking flags
+ switch (StaticPrefs::browser_cache_check_doc_frequency()) {
+ case 0:
+ loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION;
+ break;
+ case 1:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+ case 2:
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ break;
+ }
+ break;
+ }
+
+ if (HasLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_URL_CLASSIFIER;
+ }
+
+ // If the user pressed shift-reload, then do not allow ServiceWorker
+ // interception to occur. See step 12.1 of the SW HandleFetch algorithm.
+ if (IsForceReloadType(loadType)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ }
+
+ return loadFlags;
+}
+
+DocShellLoadStateInit nsDocShellLoadState::Serialize() {
+ DocShellLoadStateInit loadState;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.ResultPrincipalURIIsSome() = mResultPrincipalURIIsSome;
+ loadState.KeepResultPrincipalURIIfSet() = mKeepResultPrincipalURIIfSet;
+ loadState.LoadReplace() = mLoadReplace;
+ loadState.InheritPrincipal() = mInheritPrincipal;
+ loadState.PrincipalIsExplicit() = mPrincipalIsExplicit;
+ loadState.ForceAllowDataURI() = mForceAllowDataURI;
+ loadState.OriginalFrameSrc() = mOriginalFrameSrc;
+ loadState.IsFormSubmission() = mIsFormSubmission;
+ loadState.LoadType() = mLoadType;
+ loadState.Target() = mTarget;
+ loadState.TargetBrowsingContext() = mTargetBrowsingContext;
+ loadState.LoadFlags() = mLoadFlags;
+ loadState.FirstParty() = mFirstParty;
+ loadState.HasValidUserGestureActivation() = mHasValidUserGestureActivation;
+ loadState.AllowFocusMove() = mAllowFocusMove;
+ loadState.TypeHint() = mTypeHint;
+ loadState.FileName() = mFileName;
+ loadState.IsFromProcessingFrameAttributes() =
+ mIsFromProcessingFrameAttributes;
+ loadState.URI() = mURI;
+ loadState.OriginalURI() = mOriginalURI;
+ loadState.SourceBrowsingContext() = mSourceBrowsingContext;
+ loadState.BaseURI() = mBaseURI;
+ loadState.TriggeringPrincipal() = mTriggeringPrincipal;
+ loadState.PrincipalToInherit() = mPrincipalToInherit;
+ loadState.PartitionedPrincipalToInherit() = mPartitionedPrincipalToInherit;
+ loadState.TriggeringSandboxFlags() = mTriggeringSandboxFlags;
+ loadState.Csp() = mCsp;
+ loadState.OriginalURIString() = mOriginalURIString;
+ loadState.CancelContentJSEpoch() = mCancelContentJSEpoch;
+ loadState.ReferrerInfo() = mReferrerInfo;
+ loadState.PostDataStream() = mPostDataStream;
+ loadState.HeadersStream() = mHeadersStream;
+ loadState.SrcdocData() = mSrcdocData;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.LoadIdentifier() = mLoadIdentifier;
+ loadState.ChannelInitialized() = mChannelInitialized;
+ if (mLoadingSessionHistoryInfo) {
+ loadState.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
+ }
+ return loadState;
+}
diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h
new file mode 100644
index 0000000000..fa4957fdec
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.h
@@ -0,0 +1,492 @@
+/* -*- 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 nsDocShellLoadState_h__
+#define nsDocShellLoadState_h__
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+
+// Helper Classes
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsIContentSecurityPolicy;
+class nsIInputStream;
+class nsISHEntry;
+class nsIURI;
+class nsIDocShell;
+class nsIChannel;
+class nsIReferrerInfo;
+class OriginAttibutes;
+namespace mozilla {
+template <typename, class>
+class UniquePtr;
+namespace dom {
+class DocShellLoadStateInit;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsDocShellLoadState contains setup information used in a nsIDocShell::loadURI
+ * call.
+ */
+class nsDocShellLoadState final {
+ using BrowsingContext = mozilla::dom::BrowsingContext;
+ template <typename T>
+ using MaybeDiscarded = mozilla::dom::MaybeDiscarded<T>;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsDocShellLoadState);
+
+ explicit nsDocShellLoadState(nsIURI* aURI);
+ explicit nsDocShellLoadState(
+ const mozilla::dom::DocShellLoadStateInit& aLoadState);
+ explicit nsDocShellLoadState(const nsDocShellLoadState& aOther);
+ nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier);
+
+ static nsresult CreateFromPendingChannel(nsIChannel* aPendingChannel,
+ uint64_t aLoadIdentifier,
+ uint64_t aRegistarId,
+ nsDocShellLoadState** aResult);
+
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ nsDocShellLoadState** aResult);
+
+ // Getters and Setters
+
+ nsIReferrerInfo* GetReferrerInfo() const;
+
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
+
+ nsIURI* URI() const;
+
+ void SetURI(nsIURI* aURI);
+
+ nsIURI* OriginalURI() const;
+
+ void SetOriginalURI(nsIURI* aOriginalURI);
+
+ nsIURI* ResultPrincipalURI() const;
+
+ void SetResultPrincipalURI(nsIURI* aResultPrincipalURI);
+
+ bool ResultPrincipalURIIsSome() const;
+
+ void SetResultPrincipalURIIsSome(bool aIsSome);
+
+ bool KeepResultPrincipalURIIfSet() const;
+
+ void SetKeepResultPrincipalURIIfSet(bool aKeep);
+
+ nsIPrincipal* PrincipalToInherit() const;
+
+ void SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit);
+
+ nsIPrincipal* PartitionedPrincipalToInherit() const;
+
+ void SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit);
+
+ bool LoadReplace() const;
+
+ void SetLoadReplace(bool aLoadReplace);
+
+ nsIPrincipal* TriggeringPrincipal() const;
+
+ void SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal);
+
+ uint32_t TriggeringSandboxFlags() const;
+
+ void SetTriggeringSandboxFlags(uint32_t aTriggeringSandboxFlags);
+
+ nsIContentSecurityPolicy* Csp() const;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+
+ bool InheritPrincipal() const;
+
+ void SetInheritPrincipal(bool aInheritPrincipal);
+
+ bool PrincipalIsExplicit() const;
+
+ void SetPrincipalIsExplicit(bool aPrincipalIsExplicit);
+
+ // If true, "beforeunload" event listeners were notified by the creater of the
+ // LoadState and given the chance to abort the navigation, and should not be
+ // notified again.
+ bool NotifiedBeforeUnloadListeners() const;
+
+ void SetNotifiedBeforeUnloadListeners(bool aNotifiedBeforeUnloadListeners);
+
+ bool ForceAllowDataURI() const;
+
+ void SetForceAllowDataURI(bool aForceAllowDataURI);
+
+ bool OriginalFrameSrc() const;
+
+ void SetOriginalFrameSrc(bool aOriginalFrameSrc);
+
+ bool IsFormSubmission() const;
+
+ void SetIsFormSubmission(bool aIsFormSubmission);
+
+ uint32_t LoadType() const;
+
+ void SetLoadType(uint32_t aLoadType);
+
+ nsISHEntry* SHEntry() const;
+
+ void SetSHEntry(nsISHEntry* aSHEntry);
+
+ const mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo()
+ const;
+
+ // Copies aLoadingInfo and stores the copy in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo);
+
+ // Stores aLoadingInfo in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo);
+
+ bool LoadIsFromSessionHistory() const;
+
+ const nsString& Target() const;
+
+ void SetTarget(const nsAString& aTarget);
+
+ nsIInputStream* PostDataStream() const;
+
+ void SetPostDataStream(nsIInputStream* aStream);
+
+ nsIInputStream* HeadersStream() const;
+
+ void SetHeadersStream(nsIInputStream* aHeadersStream);
+
+ bool IsSrcdocLoad() const;
+
+ const nsString& SrcdocData() const;
+
+ void SetSrcdocData(const nsAString& aSrcdocData);
+
+ const MaybeDiscarded<BrowsingContext>& SourceBrowsingContext() const {
+ return mSourceBrowsingContext;
+ }
+
+ void SetSourceBrowsingContext(BrowsingContext*);
+
+ void SetAllowFocusMove(bool aAllow) { mAllowFocusMove = aAllow; }
+
+ bool AllowFocusMove() const { return mAllowFocusMove; }
+
+ const MaybeDiscarded<BrowsingContext>& TargetBrowsingContext() const {
+ return mTargetBrowsingContext;
+ }
+
+ void SetTargetBrowsingContext(BrowsingContext* aTargetBrowsingContext);
+
+ nsIURI* BaseURI() const;
+
+ void SetBaseURI(nsIURI* aBaseURI);
+
+ // Helper function allowing convenient work with mozilla::Maybe in C++, hiding
+ // resultPrincipalURI and resultPrincipalURIIsSome attributes from the
+ // consumer.
+ void GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const;
+
+ void SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI);
+
+ uint32_t LoadFlags() const;
+
+ void SetLoadFlags(uint32_t aFlags);
+
+ void SetLoadFlag(uint32_t aFlag);
+
+ void UnsetLoadFlag(uint32_t aFlag);
+
+ bool HasLoadFlags(uint32_t aFlag);
+
+ bool FirstParty() const;
+
+ void SetFirstParty(bool aFirstParty);
+
+ bool HasValidUserGestureActivation() const;
+
+ void SetHasValidUserGestureActivation(bool HasValidUserGestureActivation);
+
+ const nsCString& TypeHint() const;
+
+ void SetTypeHint(const nsCString& aTypeHint);
+
+ const nsString& FileName() const;
+
+ void SetFileName(const nsAString& aFileName);
+
+ // Give the type of DocShell we're loading into (chrome/content/etc) and
+ // origin attributes for the URI we're loading, figure out if we should
+ // inherit our principal from the document the load was requested from, or
+ // else if the principal should be set up later in the process (after loads).
+ // See comments in function for more info on principal selection algorithm
+ nsresult SetupInheritingPrincipal(
+ mozilla::dom::BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ // If no triggering principal exists at the moment, create one using referrer
+ // information and origin attributes.
+ nsresult SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ void SetIsFromProcessingFrameAttributes() {
+ mIsFromProcessingFrameAttributes = true;
+ }
+ bool GetIsFromProcessingFrameAttributes() const {
+ return mIsFromProcessingFrameAttributes;
+ }
+
+ nsIChannel* GetPendingRedirectedChannel() {
+ return mPendingRedirectedChannel;
+ }
+
+ uint64_t GetPendingRedirectChannelRegistrarId() const {
+ return mChannelRegistrarId;
+ }
+
+ void SetOriginalURIString(const nsCString& aOriginalURI) {
+ mOriginalURIString.emplace(aOriginalURI);
+ }
+ const mozilla::Maybe<nsCString>& GetOriginalURIString() const {
+ return mOriginalURIString;
+ }
+
+ void SetCancelContentJSEpoch(int32_t aCancelEpoch) {
+ mCancelContentJSEpoch.emplace(aCancelEpoch);
+ }
+ const mozilla::Maybe<int32_t>& GetCancelContentJSEpoch() const {
+ return mCancelContentJSEpoch;
+ }
+
+ uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
+
+ void SetChannelInitialized(bool aInitilized) {
+ mChannelInitialized = aInitilized;
+ }
+
+ bool GetChannelInitialized() const { return mChannelInitialized; }
+
+ // When loading a document through nsDocShell::LoadURI(), a special set of
+ // flags needs to be set based on other values in nsDocShellLoadState. This
+ // function calculates those flags, before the LoadState is passed to
+ // nsDocShell::InternalLoad.
+ void CalculateLoadURIFlags();
+
+ // Compute the load flags to be used by creating channel. aUriModified and
+ // aIsXFOError are expected to be Nothing when called from Parent process.
+ nsLoadFlags CalculateChannelLoadFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ mozilla::Maybe<bool> aUriModified, mozilla::Maybe<bool> aIsXFOError);
+
+ mozilla::dom::DocShellLoadStateInit Serialize();
+
+ void SetLoadIsFromSessionHistory(int32_t aRequestedIndex,
+ int32_t aSessionHistoryLength,
+ bool aLoadingFromActiveEntry);
+ void ClearLoadIsFromSessionHistory();
+
+ protected:
+ // Destructor can't be defaulted or inlined, as header doesn't have all type
+ // includes it needs to do so.
+ ~nsDocShellLoadState();
+
+ protected:
+ // This is the referrer for the load.
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+ // The URI we are navigating to. Will not be null once set.
+ nsCOMPtr<nsIURI> mURI;
+
+ // The URI to set as the originalURI on the channel that does the load. If
+ // null, aURI will be set as the originalURI.
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ // The URI to be set to loadInfo.resultPrincipalURI
+ // - When Nothing, there will be no change
+ // - When Some, the principal URI will overwrite even
+ // with a null value.
+ //
+ // Valid only if mResultPrincipalURIIsSome is true (has the same meaning as
+ // isSome() on mozilla::Maybe.)
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ bool mResultPrincipalURIIsSome;
+
+ // The principal of the load, that is, the entity responsible for causing the
+ // load to occur. In most cases the referrer and the triggeringPrincipal's URI
+ // will be identical.
+ //
+ // Please note that this is the principal that is used for security checks. If
+ // the argument aURI is provided by the web, then please do not pass a
+ // SystemPrincipal as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+
+ // The SandboxFlags of the load, that are, the SandboxFlags of the entity
+ // responsible for causing the load to occur. Most likely this are the
+ // SandboxFlags of the document that started the load.
+ uint32_t mTriggeringSandboxFlags;
+
+ // The CSP of the load, that is, the CSP of the entity responsible for causing
+ // the load to occur. Most likely this is the CSP of the document that started
+ // the load. In case the entity starting the load did not use a CSP, then mCsp
+ // can be null. Please note that this is also the CSP that will be applied to
+ // the load in case the load encounters a server side redirect.
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+
+ // If a refresh is caused by http-equiv="refresh" we want to set
+ // aResultPrincipalURI, but we do not want to overwrite the channel's
+ // ResultPrincipalURI, if it has already been set on the channel by a protocol
+ // handler.
+ bool mKeepResultPrincipalURIIfSet;
+
+ // If set LOAD_REPLACE flag will be set on the channel. If aOriginalURI is
+ // null, this argument is ignored.
+ bool mLoadReplace;
+
+ // If this attribute is true and no triggeringPrincipal is specified,
+ // copy the principal from the referring document.
+ bool mInheritPrincipal;
+
+ // If this attribute is true only ever use the principal specified
+ // by the triggeringPrincipal and inheritPrincipal attributes.
+ // If there are security reasons for why this is unsafe, such
+ // as trying to use a systemprincipal as the triggeringPrincipal
+ // for a content docshell the load fails.
+ bool mPrincipalIsExplicit;
+
+ bool mNotifiedBeforeUnloadListeners;
+
+ // Principal we're inheriting. If null, this means the principal should be
+ // inherited from the current document. If set to NullPrincipal, the channel
+ // will fill in principal information later in the load. See internal comments
+ // of SetupInheritingPrincipal for more info.
+ //
+ // When passed to InternalLoad, If this argument is null then
+ // principalToInherit is computed differently. See nsDocShell::InternalLoad
+ // for more comments.
+
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+
+ // If this attribute is true, then a top-level navigation
+ // to a data URI will be allowed.
+ bool mForceAllowDataURI;
+
+ // If this attribute is true, this load corresponds to a frame
+ // element loading its original src (or srcdoc) attribute.
+ bool mOriginalFrameSrc;
+
+ // If this attribute is true, then the load was initiated by a
+ // form submission. This is important to know for the CSP directive
+ // navigate-to.
+ bool mIsFormSubmission;
+
+ // Contains a load type as specified by the nsDocShellLoadTypes::load*
+ // constants
+ uint32_t mLoadType;
+
+ // Active Session History entry (if loading from SH)
+ nsCOMPtr<nsISHEntry> mSHEntry;
+
+ // Loading session history info for the load
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo>
+ mLoadingSessionHistoryInfo;
+
+ // Target for load, like _content, _blank etc.
+ nsString mTarget;
+
+ // When set, this is the Target Browsing Context for the navigation
+ // after retargeting.
+ MaybeDiscarded<BrowsingContext> mTargetBrowsingContext;
+
+ // Post data stream (if POSTing)
+ nsCOMPtr<nsIInputStream> mPostDataStream;
+
+ // Additional Headers
+ nsCOMPtr<nsIInputStream> mHeadersStream;
+
+ // When set, the load will be interpreted as a srcdoc load, where contents of
+ // this string will be loaded instead of the URI. Setting srcdocData sets
+ // isSrcdocLoad to true
+ nsString mSrcdocData;
+
+ // When set, this is the Source Browsing Context for the navigation.
+ MaybeDiscarded<BrowsingContext> mSourceBrowsingContext;
+
+ // Used for srcdoc loads to give view-source knowledge of the load's base URI
+ // as this information isn't embedded in the load's URI.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ // Set of Load Flags, taken from nsDocShellLoadTypes.h and nsIWebNavigation
+ uint32_t mLoadFlags;
+
+ // Is this a First Party Load?
+ bool mFirstParty;
+
+ // Is this load triggered by a user gesture?
+ bool mHasValidUserGestureActivation;
+
+ // Whether this load can steal the focus from the source browsing context.
+ bool mAllowFocusMove;
+
+ // A hint as to the content-type of the resulting data. If no hint, IsVoid()
+ // should return true.
+ nsCString mTypeHint;
+
+ // Non-void when the link should be downloaded as the given filename.
+ // mFileName being non-void but empty means that no filename hint was
+ // specified, but link should still trigger a download. If not a download,
+ // mFileName.IsVoid() should return true.
+ nsString mFileName;
+
+ // This will be true if this load is triggered by attribute changes.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes;
+
+ // If set, a pending cross-process redirected channel should be used to
+ // perform the load. The channel will be stored in this value.
+ nsCOMPtr<nsIChannel> mPendingRedirectedChannel;
+
+ // An optional string representation of mURI, before any
+ // fixups were applied, so that we can send it to a search
+ // engine service if needed.
+ mozilla::Maybe<nsCString> mOriginalURIString;
+
+ // An optional value to pass to nsIDocShell::setCancelJSEpoch
+ // when initiating the load.
+ mozilla::Maybe<int32_t> mCancelContentJSEpoch;
+
+ // If mPendingRedirectChannel is set, then this is the identifier
+ // that the parent-process equivalent channel has been registered
+ // with using RedirectChannelRegistrar.
+ uint64_t mChannelRegistrarId;
+
+ // An identifier to make it possible to examine if two loads are
+ // equal, and which browsing context they belong to (see
+ // BrowsingContext::{Get, Set}CurrentLoadIdentifier)
+ const uint64_t mLoadIdentifier;
+
+ // Optional value to indicate that a channel has been
+ // pre-initialized in the parent process.
+ bool mChannelInitialized;
+};
+
+#endif /* nsDocShellLoadState_h__ */
diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h
new file mode 100644
index 0000000000..e694843b2d
--- /dev/null
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -0,0 +1,211 @@
+/* -*- 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 nsDocShellLoadTypes_h_
+#define nsDocShellLoadTypes_h_
+
+#ifdef MOZILLA_INTERNAL_API
+
+# include "nsDOMNavigationTiming.h"
+# include "nsIDocShell.h"
+# include "nsIWebNavigation.h"
+
+/**
+ * Load flag for error pages. This uses one of the reserved flag
+ * values from nsIWebNavigation.
+ */
+# define LOAD_FLAGS_ERROR_PAGE 0x0001U
+
+# define MAKE_LOAD_TYPE(type, flags) ((type) | ((flags) << 16))
+# define LOAD_TYPE_HAS_FLAGS(type, flags) ((type) & ((flags) << 16))
+
+/**
+ * These are flags that confuse ConvertLoadTypeToDocShellLoadInfo and should
+ * not be passed to MAKE_LOAD_TYPE. In particular this includes all flags
+ * above 0xffff (e.g. LOAD_FLAGS_BYPASS_CLASSIFIER), since MAKE_LOAD_TYPE would
+ * just shift them out anyway.
+ */
+# define EXTRA_LOAD_FLAGS \
+ (nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD | \
+ nsIWebNavigation::LOAD_FLAGS_ALLOW_POPUPS | 0xffff0000)
+
+/* load types are legal combinations of load commands and flags
+ *
+ * NOTE:
+ * Remember to update the IsValidLoadType function below if you change this
+ * enum to ensure bad flag combinations will be rejected.
+ */
+enum LoadType : uint32_t {
+ LOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_NORMAL_EXTERNAL = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_FROM_EXTERNAL),
+ LOAD_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_HISTORY,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_NORMAL_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_NORMAL_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_NORMAL_ALLOW_MIXED_CONTENT =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_RELOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_RELOAD_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_RELOAD_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_ALLOW_MIXED_CONTENT =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_RELOAD_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_LINK = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_LINK),
+ LOAD_REFRESH = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH),
+ LOAD_RELOAD_CHARSET_CHANGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_BYPASS_HISTORY =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY),
+ LOAD_STOP_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT),
+ LOAD_STOP_CONTENT_AND_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT |
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_PUSHSTATE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_PUSHSTATE,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_REPLACE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ /**
+ * Load type for an error page. These loads are never triggered by users of
+ * Docshell. Instead, Docshell triggers the load itself when a
+ * consumer-triggered load failed.
+ */
+ LOAD_ERROR_PAGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, LOAD_FLAGS_ERROR_PAGE)
+
+ // NOTE: Adding a new value? Remember to update IsValidLoadType!
+};
+
+static inline bool IsForceReloadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ return true;
+ }
+ return false;
+}
+
+static inline bool IsValidLoadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_NORMAL_EXTERNAL:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+ case LOAD_HISTORY:
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ case LOAD_LINK:
+ case LOAD_REFRESH:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_STOP_CONTENT:
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_PUSHSTATE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ case LOAD_ERROR_PAGE:
+ return true;
+ }
+ return false;
+}
+
+static inline nsDOMNavigationTiming::Type ConvertLoadTypeToNavigationType(
+ uint32_t aLoadType) {
+ // Not initialized, assume it's normal load.
+ if (aLoadType == 0) {
+ aLoadType = LOAD_NORMAL;
+ }
+
+ auto result = nsDOMNavigationTiming::TYPE_RESERVED;
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_EXTERNAL:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+ case LOAD_LINK:
+ case LOAD_STOP_CONTENT:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ result = nsDOMNavigationTiming::TYPE_NAVIGATE;
+ break;
+ case LOAD_HISTORY:
+ result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
+ break;
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ result = nsDOMNavigationTiming::TYPE_RELOAD;
+ break;
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_REFRESH:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_ERROR_PAGE:
+ case LOAD_PUSHSTATE:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ default:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ }
+
+ return result;
+}
+
+#endif // MOZILLA_INTERNAL_API
+#endif
diff --git a/docshell/base/nsDocShellTelemetryUtils.cpp b/docshell/base/nsDocShellTelemetryUtils.cpp
new file mode 100644
index 0000000000..5d06009036
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.cpp
@@ -0,0 +1,210 @@
+/* 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 "nsDocShellTelemetryUtils.h"
+
+namespace {
+
+using ErrorLabel = mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR;
+
+struct LoadErrorTelemetryResult {
+ nsresult mValue;
+ ErrorLabel mLabel;
+};
+
+static const LoadErrorTelemetryResult sResult[] = {
+ {
+ NS_ERROR_UNKNOWN_PROTOCOL,
+ ErrorLabel::UNKNOWN_PROTOCOL,
+ },
+ {
+ NS_ERROR_FILE_NOT_FOUND,
+ ErrorLabel::FILE_NOT_FOUND,
+ },
+ {
+ NS_ERROR_FILE_ACCESS_DENIED,
+ ErrorLabel::FILE_ACCESS_DENIED,
+ },
+ {
+ NS_ERROR_UNKNOWN_HOST,
+ ErrorLabel::UNKNOWN_HOST,
+ },
+ {
+ NS_ERROR_CONNECTION_REFUSED,
+ ErrorLabel::CONNECTION_REFUSED,
+ },
+ {
+ NS_ERROR_PROXY_BAD_GATEWAY,
+ ErrorLabel::PROXY_BAD_GATEWAY,
+ },
+ {
+ NS_ERROR_NET_INTERRUPT,
+ ErrorLabel::NET_INTERRUPT,
+ },
+ {
+ NS_ERROR_NET_TIMEOUT,
+ ErrorLabel::NET_TIMEOUT,
+ },
+ {
+ NS_ERROR_PROXY_GATEWAY_TIMEOUT,
+ ErrorLabel::P_GATEWAY_TIMEOUT,
+ },
+ {
+ NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION,
+ ErrorLabel::CSP_FRAME_ANCEST,
+ },
+ {
+ NS_ERROR_CSP_FORM_ACTION_VIOLATION,
+ ErrorLabel::CSP_FORM_ACTION,
+ },
+ {
+ NS_ERROR_CSP_NAVIGATE_TO_VIOLATION,
+ ErrorLabel::CSP_NAVIGATE_TO,
+ },
+ {
+ NS_ERROR_XFO_VIOLATION,
+ ErrorLabel::XFO_VIOLATION,
+ },
+ {
+ NS_ERROR_PHISHING_URI,
+ ErrorLabel::PHISHING_URI,
+ },
+ {
+ NS_ERROR_MALWARE_URI,
+ ErrorLabel::MALWARE_URI,
+ },
+ {
+ NS_ERROR_UNWANTED_URI,
+ ErrorLabel::UNWANTED_URI,
+ },
+ {
+ NS_ERROR_HARMFUL_URI,
+ ErrorLabel::HARMFUL_URI,
+ },
+ {
+ NS_ERROR_CONTENT_CRASHED,
+ ErrorLabel::CONTENT_CRASHED,
+ },
+ {
+ NS_ERROR_FRAME_CRASHED,
+ ErrorLabel::FRAME_CRASHED,
+ },
+ {
+ NS_ERROR_BUILDID_MISMATCH,
+ ErrorLabel::BUILDID_MISMATCH,
+ },
+ {
+ NS_ERROR_NET_RESET,
+ ErrorLabel::NET_RESET,
+ },
+ {
+ NS_ERROR_MALFORMED_URI,
+ ErrorLabel::MALFORMED_URI,
+ },
+ {
+ NS_ERROR_REDIRECT_LOOP,
+ ErrorLabel::REDIRECT_LOOP,
+ },
+ {
+ NS_ERROR_UNKNOWN_SOCKET_TYPE,
+ ErrorLabel::UNKNOWN_SOCKET,
+ },
+ {
+ NS_ERROR_DOCUMENT_NOT_CACHED,
+ ErrorLabel::DOCUMENT_N_CACHED,
+ },
+ {
+ NS_ERROR_OFFLINE,
+ ErrorLabel::OFFLINE,
+ },
+ {
+ NS_ERROR_DOCUMENT_IS_PRINTMODE,
+ ErrorLabel::DOC_PRINTMODE,
+ },
+ {
+ NS_ERROR_PORT_ACCESS_NOT_ALLOWED,
+ ErrorLabel::PORT_ACCESS,
+ },
+ {
+ NS_ERROR_UNKNOWN_PROXY_HOST,
+ ErrorLabel::UNKNOWN_PROXY_HOST,
+ },
+ {
+ NS_ERROR_PROXY_CONNECTION_REFUSED,
+ ErrorLabel::PROXY_CONNECTION,
+ },
+ {
+ NS_ERROR_PROXY_FORBIDDEN,
+ ErrorLabel::PROXY_FORBIDDEN,
+ },
+ {
+ NS_ERROR_PROXY_NOT_IMPLEMENTED,
+ ErrorLabel::P_NOT_IMPLEMENTED,
+ },
+ {
+ NS_ERROR_PROXY_AUTHENTICATION_FAILED,
+ ErrorLabel::PROXY_AUTH,
+ },
+ {
+ NS_ERROR_PROXY_TOO_MANY_REQUESTS,
+ ErrorLabel::PROXY_TOO_MANY,
+ },
+ {
+ NS_ERROR_INVALID_CONTENT_ENCODING,
+ ErrorLabel::CONTENT_ENCODING,
+ },
+ {
+ NS_ERROR_REMOTE_XUL,
+ ErrorLabel::REMOTE_XUL,
+ },
+ {
+ NS_ERROR_UNSAFE_CONTENT_TYPE,
+ ErrorLabel::UNSAFE_CONTENT,
+ },
+ {
+ NS_ERROR_CORRUPTED_CONTENT,
+ ErrorLabel::CORRUPTED_CONTENT,
+ },
+ {
+ NS_ERROR_INTERCEPTION_FAILED,
+ ErrorLabel::INTERCEPTION_FAIL,
+ },
+ {
+ NS_ERROR_NET_INADEQUATE_SECURITY,
+ ErrorLabel::INADEQUATE_SEC,
+ },
+ {
+ NS_ERROR_BLOCKED_BY_POLICY,
+ ErrorLabel::BLOCKED_BY_POLICY,
+ },
+ {
+ NS_ERROR_NET_HTTP2_SENT_GOAWAY,
+ ErrorLabel::HTTP2_SENT_GOAWAY,
+ },
+ {
+ NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
+ ErrorLabel::HTTP3_PROTOCOL,
+ },
+ {
+ NS_BINDING_FAILED,
+ ErrorLabel::BINDING_FAILED,
+ },
+};
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(
+ nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_OK);
+
+ for (const auto& p : sResult) {
+ if (p.mValue == aRv) {
+ return p.mLabel;
+ }
+ }
+ return ErrorLabel::otherError;
+}
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/nsDocShellTelemetryUtils.h b/docshell/base/nsDocShellTelemetryUtils.h
new file mode 100644
index 0000000000..4e0097caec
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.h
@@ -0,0 +1,22 @@
+//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellTelemetryUtils_h__
+#define nsDocShellTelemetryUtils_h__
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace dom {
+/**
+ * Convert page load errors to telemetry labels
+ * Only select nsresults are converted, otherwise this function
+ * will return "errorOther", view the list of errors at
+ * docshell/base/nsDocShellTelemetryUtils.cpp.
+ */
+Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(nsresult aRv);
+} // namespace dom
+} // namespace mozilla
+#endif // nsDocShellTelemetryUtils_h__
diff --git a/docshell/base/nsDocShellTreeOwner.cpp b/docshell/base/nsDocShellTreeOwner.cpp
new file mode 100644
index 0000000000..ff2b4dca6c
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -0,0 +1,1304 @@
+/* -*- 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/. */
+
+// Local Includes
+#include "nsDocShellTreeOwner.h"
+#include "nsWebBrowser.h"
+
+// Helper Classes
+#include "nsContentUtils.h"
+#include "nsSize.h"
+#include "mozilla/ReflowInput.h"
+#include "mozilla/ScopeExit.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+// Interfaces needed to be included
+#include "nsPresContext.h"
+#include "nsITooltipListener.h"
+#include "nsINode.h"
+#include "Link.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/SVGTitleElement.h"
+#include "nsIFormControl.h"
+#include "nsIWebNavigation.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIRemoteTab.h"
+#include "nsIBrowserChild.h"
+#include "nsRect.h"
+#include "nsIWebBrowserChromeFocus.h"
+#include "nsIContent.h"
+#include "nsServiceManagerUtils.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/DragEvent.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/File.h" // for input type=file
+#include "mozilla/dom/FileList.h" // for input type=file
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// A helper routine that navigates the tricky path from a |nsWebBrowser| to
+// a |EventTarget| via the window root and chrome event handler.
+static nsresult GetDOMEventTarget(nsWebBrowser* aInBrowser,
+ EventTarget** aTarget) {
+ if (!aInBrowser) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ aInBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto* outerWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot();
+ NS_ENSURE_TRUE(rootWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<EventTarget> target = rootWindow->GetChromeEventHandler();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+ target.forget(aTarget);
+
+ return NS_OK;
+}
+
+nsDocShellTreeOwner::nsDocShellTreeOwner()
+ : mWebBrowser(nullptr),
+ mTreeOwner(nullptr),
+ mPrimaryContentShell(nullptr),
+ mWebBrowserChrome(nullptr),
+ mOwnerWin(nullptr),
+ mOwnerRequestor(nullptr) {}
+
+nsDocShellTreeOwner::~nsDocShellTreeOwner() { RemoveChromeListeners(); }
+
+NS_IMPL_ADDREF(nsDocShellTreeOwner)
+NS_IMPL_RELEASE(nsDocShellTreeOwner)
+
+NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) {
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) {
+ if (mWebBrowserChromeWeak != nullptr) {
+ return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
+ }
+ return mOwnerWin->QueryInterface(aIID, aSink);
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<nsIPrompt> prompt;
+ EnsurePrompter();
+ prompt = mPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ EnsureAuthPrompter();
+ prompt = mAuthPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> req = GetOwnerRequestor();
+ if (req) {
+ return req->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIDocShellTreeOwner
+//*****************************************************************************
+
+void nsDocShellTreeOwner::EnsurePrompter() {
+ if (mPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewPrompter(domWindow, getter_AddRefs(mPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureAuthPrompter() {
+ if (mAuthPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewAuthPrompter(domWindow, getter_AddRefs(mAuthPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::AddToWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ wwatch->AddWindow(domWindow, webBrowserChrome);
+ }
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::RemoveFromWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ wwatch->RemoveWindow(domWindow);
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureContentTreeOwner() {
+ if (mContentTreeOwner) {
+ return;
+ }
+
+ mContentTreeOwner = new nsDocShellTreeOwner();
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome();
+ if (browserChrome) {
+ mContentTreeOwner->SetWebBrowserChrome(browserChrome);
+ }
+
+ if (mWebBrowser) {
+ mContentTreeOwner->WebBrowser(mWebBrowser);
+ }
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary) {
+ if (mTreeOwner) return mTreeOwner->ContentShellAdded(aContentShell, aPrimary);
+
+ EnsureContentTreeOwner();
+ aContentShell->SetTreeOwner(mContentTreeOwner);
+
+ if (aPrimary) {
+ mPrimaryContentShell = aContentShell;
+ mPrimaryRemoteTab = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) {
+ if (mTreeOwner) {
+ return mTreeOwner->ContentShellRemoved(aContentShell);
+ }
+
+ if (mPrimaryContentShell == aContentShell) {
+ mPrimaryContentShell = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) {
+ NS_ENSURE_ARG_POINTER(aShell);
+
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryContentShell(aShell);
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> shell;
+ if (!mPrimaryRemoteTab) {
+ shell =
+ mPrimaryContentShell ? mPrimaryContentShell : mWebBrowser->mDocShell;
+ }
+ shell.forget(aShell);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabAdded(aTab, aPrimary);
+ }
+
+ if (aPrimary) {
+ mPrimaryRemoteTab = aTab;
+ mPrimaryContentShell = nullptr;
+ } else if (mPrimaryRemoteTab == aTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabRemoved(nsIRemoteTab* aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabRemoved(aTab);
+ }
+
+ if (aTab == mPrimaryRemoteTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryRemoteTab(nsIRemoteTab** aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryRemoteTab(aTab);
+ }
+
+ nsCOMPtr<nsIRemoteTab> tab = mPrimaryRemoteTab;
+ tab.forget(aTab);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetRootShellSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, int32_t aCX,
+ int32_t aCY) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+
+ NS_ENSURE_STATE(mTreeOwner || webBrowserChrome);
+
+ if (nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mTreeOwner) {
+ return treeOwner->SizeShellTo(aShellItem, aCX, aCY);
+ }
+
+ if (aShellItem == mWebBrowser->mDocShell) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ // The XUL window to resize is in the parent process, but there we
+ // won't be able to get aShellItem to do the hack in
+ // AppWindow::SizeShellTo, so let's send the width and height of
+ // aShellItem too.
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem));
+ NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+
+ int32_t width = 0;
+ int32_t height = 0;
+ shellAsWin->GetSize(&width, &height);
+ return browserChild->RemoteSizeShellTo(aCX, aCY, width, height);
+ }
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->SizeBrowserTo()) whose implementations all failed
+ // like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_ENSURE_TRUE(aShellItem, NS_ERROR_FAILURE);
+
+ RefPtr<Document> document = aShellItem->GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ NS_ENSURE_TRUE(document->GetDocumentElement(), NS_ERROR_FAILURE);
+
+ // Set the preferred Size
+ // XXX
+ NS_ERROR("Implement this");
+ /*
+ Set the preferred size on the aShellItem.
+ */
+
+ RefPtr<nsPresContext> presContext = mWebBrowser->mDocShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ RefPtr<PresShell> presShell = presContext->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(
+ presShell->ResizeReflow(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ NS_ERROR_FAILURE);
+
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->SizeBrowserTo()) whose implementations all failed like
+ // this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPersistence(bool aPersistPosition, bool aPersistSize,
+ bool aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPersistence(bool* aPersistPosition, bool* aPersistSize,
+ bool* aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTabCount(uint32_t* aResult) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetTabCount(aResult);
+ }
+
+ *aResult = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult) {
+ *aResult = mPrimaryRemoteTab || mPrimaryContentShell;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* aParentWidget, int32_t aX,
+ int32_t aY, int32_t aCX, int32_t aCY) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Destroy() {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->DestroyBrowserWindow()) whose implementations all
+ // failed like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) {
+ if (mWebBrowser) {
+ return mWebBrowser->GetUnscaledDevicePixelsPerCSSPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mWebBrowser) {
+ return mWebBrowser->GetDevicePixelsPerDesktopPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) {
+ if (mWebBrowser) {
+ nsresult rv = mWebBrowser->SetPositionDesktopPix(aX, aY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ double scale = 1.0;
+ GetDevicePixelsPerDesktopPixel(&scale);
+ return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPosition(int32_t aX, int32_t aY) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION,
+ aX, aY, 0, 0);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPosition(int32_t* aX, int32_t* aY) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION,
+ aX, aY, nullptr, nullptr);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER,
+ 0, 0, aCX, aCY);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER,
+ nullptr, nullptr, aCX, aCY);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX,
+ int32_t aCY, uint32_t aFlags) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetDimensions(
+ nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER |
+ nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION,
+ aX, aY, aCX, aCY);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aCX,
+ int32_t* aCY) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetDimensions(
+ nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER |
+ nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION,
+ aX, aY, aCX, aCY);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Repaint(bool aForce) { return NS_ERROR_NULL_POINTER; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentWidget(nsIWidget** aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentWidget(nsIWidget* aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetSiteWindow(aParentNativeWindow);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetNativeHandle(nsAString& aNativeHandle) {
+ // the nativeHandle should be accessed from nsIAppWindow
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetVisibility(bool* aVisibility) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetVisibility(aVisibility);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetVisibility(bool aVisibility) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetVisibility(aVisibility);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetEnabled(bool* aEnabled) {
+ NS_ENSURE_ARG_POINTER(aEnabled);
+ *aEnabled = true;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetEnabled(bool aEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetMainWidget(nsIWidget** aMainWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetFocus() {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetFocus();
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTitle(nsAString& aTitle) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTitle(const nsAString& aTitle) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ // In the absence of DOM document creation event, this method is the
+ // most convenient place to install the mouse listener on the
+ // DOM document.
+ return AddChromeListeners();
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aURI,
+ uint32_t aFlags) {
+ if (mChromeTooltipListener && aWebProgress &&
+ !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) &&
+ mChromeTooltipListener->WebProgressShowedTooltip(aWebProgress)) {
+ mChromeTooltipListener->HideTooltip();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner: Accessors
+//*****************************************************************************
+
+void nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) {
+ if (!aWebBrowser) {
+ RemoveChromeListeners();
+ }
+ if (aWebBrowser != mWebBrowser) {
+ mPrompter = nullptr;
+ mAuthPrompter = nullptr;
+ }
+
+ mWebBrowser = aWebBrowser;
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->WebBrowser(aWebBrowser);
+ if (!aWebBrowser) {
+ mContentTreeOwner = nullptr;
+ }
+ }
+}
+
+nsWebBrowser* nsDocShellTreeOwner::WebBrowser() { return mWebBrowser; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) {
+ if (aTreeOwner) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(aTreeOwner));
+ NS_ENSURE_TRUE(webBrowserChrome, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(webBrowserChrome),
+ NS_ERROR_INVALID_ARG);
+ mTreeOwner = aTreeOwner;
+ } else {
+ mTreeOwner = nullptr;
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(nullptr), NS_ERROR_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetWebBrowserChrome(
+ nsIWebBrowserChrome* aWebBrowserChrome) {
+ if (!aWebBrowserChrome) {
+ mWebBrowserChrome = nullptr;
+ mOwnerWin = nullptr;
+ mOwnerRequestor = nullptr;
+ mWebBrowserChromeWeak = nullptr;
+ } else {
+ nsCOMPtr<nsISupportsWeakReference> supportsweak =
+ do_QueryInterface(aWebBrowserChrome);
+ if (supportsweak) {
+ supportsweak->GetWeakReference(getter_AddRefs(mWebBrowserChromeWeak));
+ } else {
+ nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin(
+ do_QueryInterface(aWebBrowserChrome));
+ nsCOMPtr<nsIInterfaceRequestor> requestor(
+ do_QueryInterface(aWebBrowserChrome));
+
+ // it's ok for ownerWin or requestor to be null.
+ mWebBrowserChrome = aWebBrowserChrome;
+ mOwnerWin = ownerWin;
+ mOwnerRequestor = requestor;
+ }
+ }
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->SetWebBrowserChrome(aWebBrowserChrome);
+ }
+
+ return NS_OK;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+nsDocShellTreeOwner::AddChromeListeners() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // install tooltips
+ if (!mChromeTooltipListener) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(webBrowserChrome));
+ if (tooltipListener) {
+ mChromeTooltipListener =
+ new ChromeTooltipListener(mWebBrowser, webBrowserChrome);
+ rv = mChromeTooltipListener->AddChromeListeners();
+ }
+ }
+
+ nsCOMPtr<EventTarget> target;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(target));
+
+ // register dragover and drop event listeners with the listener manager
+ MOZ_ASSERT(target, "how does this happen? (see bug 1659758)");
+ if (target) {
+ if (EventListenerManager* elmP = target->GetOrCreateListenerManager()) {
+ elmP->AddEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->AddEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoveChromeListeners() {
+ if (mChromeTooltipListener) {
+ mChromeTooltipListener->RemoveChromeListeners();
+ mChromeTooltipListener = nullptr;
+ }
+
+ nsCOMPtr<EventTarget> piTarget;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(piTarget));
+ if (!piTarget) {
+ return NS_OK;
+ }
+
+ EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
+ if (elmP) {
+ elmP->RemoveEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->RemoveEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::HandleEvent(Event* aEvent) {
+ DragEvent* dragEvent = aEvent ? aEvent->AsDragEvent() : nullptr;
+ if (NS_WARN_IF(!dragEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dragEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDroppedLinkHandler> handler =
+ do_GetService("@mozilla.org/content/dropped-link-handler;1");
+ if (!handler) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("dragover")) {
+ bool canDropLink = false;
+ handler->CanDropLink(dragEvent, false, &canDropLink);
+ if (canDropLink) {
+ aEvent->PreventDefault();
+ }
+ } else if (eventType.EqualsLiteral("drop")) {
+ nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser);
+
+ nsTArray<RefPtr<nsIDroppedLinkItem>> links;
+ if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, links))) {
+ if (links.Length() >= 1) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ handler->GetTriggeringPrincipal(dragEvent,
+ getter_AddRefs(triggeringPrincipal));
+ if (triggeringPrincipal) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
+ GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ nsresult rv = browserChild->RemoteDropLinks(links);
+ return rv;
+ }
+ }
+ nsAutoString url;
+ if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
+ if (!url.IsEmpty()) {
+#ifndef ANDROID
+ MOZ_ASSERT(triggeringPrincipal,
+ "nsDocShellTreeOwner::HandleEvent: Need a valid "
+ "triggeringPrincipal");
+#endif
+ LoadURIOptions loadURIOptions;
+ loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ handler->GetCSP(dragEvent, getter_AddRefs(csp));
+ loadURIOptions.mCsp = csp;
+ webnav->LoadURI(url, loadURIOptions);
+ }
+ }
+ }
+ }
+ } else {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsDocShellTreeOwner::GetWebBrowserChrome() {
+ nsCOMPtr<nsIWebBrowserChrome> chrome;
+ if (mWebBrowserChromeWeak) {
+ chrome = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mWebBrowserChrome) {
+ chrome = mWebBrowserChrome;
+ }
+ return chrome.forget();
+}
+
+already_AddRefed<nsIEmbeddingSiteWindow> nsDocShellTreeOwner::GetOwnerWin() {
+ nsCOMPtr<nsIEmbeddingSiteWindow> win;
+ if (mWebBrowserChromeWeak) {
+ win = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerWin) {
+ win = mOwnerWin;
+ }
+ return win.forget();
+}
+
+already_AddRefed<nsIInterfaceRequestor>
+nsDocShellTreeOwner::GetOwnerRequestor() {
+ nsCOMPtr<nsIInterfaceRequestor> req;
+ if (mWebBrowserChromeWeak) {
+ req = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerRequestor) {
+ req = mOwnerRequestor;
+ }
+ return req.forget();
+}
+
+NS_IMPL_ISUPPORTS(ChromeTooltipListener, nsIDOMEventListener)
+
+ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome)
+ : mWebBrowser(aInBrowser),
+ mWebBrowserChrome(aInChrome),
+ mTooltipListenerInstalled(false),
+ mMouseClientX(0),
+ mMouseClientY(0),
+ mMouseScreenX(0),
+ mMouseScreenY(0),
+ mShowingTooltip(false),
+ mTooltipShownOnce(false) {}
+
+ChromeTooltipListener::~ChromeTooltipListener() {}
+
+nsITooltipTextProvider* ChromeTooltipListener::GetTooltipTextProvider() {
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider = do_GetService(NS_TOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider =
+ do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ return mTooltipTextProvider;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+ChromeTooltipListener::AddChromeListeners() {
+ if (!mEventTarget) {
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(mEventTarget));
+ }
+
+ // Register the appropriate events for tooltips, but only if
+ // the embedding chrome cares.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener && !mTooltipListenerInstalled) {
+ rv = AddTooltipListener();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+// Subscribe to the events that will allow us to track tooltips. We need "mouse"
+// for mouseExit, "mouse motion" for mouseMove, and "key" for keyDown. As we
+// add the listeners, keep track of how many succeed so we can clean up
+// correctly in Release().
+NS_IMETHODIMP
+ChromeTooltipListener::AddTooltipListener() {
+ if (mEventTarget) {
+ nsresult rv = NS_OK;
+#ifndef XP_WIN
+ rv =
+ mEventTarget->AddSystemEventListener(u"keydown"_ns, this, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ rv = mEventTarget->AddSystemEventListener(u"mousedown"_ns, this, false,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mEventTarget->AddSystemEventListener(u"mouseout"_ns, this, false,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mEventTarget->AddSystemEventListener(u"mousemove"_ns, this, false,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTooltipListenerInstalled = true;
+ }
+
+ return NS_OK;
+}
+
+// Unsubscribe from the various things we've hooked up to the window root.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveChromeListeners() {
+ HideTooltip();
+
+ if (mTooltipListenerInstalled) {
+ RemoveTooltipListener();
+ }
+
+ mEventTarget = nullptr;
+
+ // it really doesn't matter if these fail...
+ return NS_OK;
+}
+
+// Unsubscribe from all the various tooltip events that we were listening to.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveTooltipListener() {
+ if (mEventTarget) {
+#ifndef XP_WIN
+ mEventTarget->RemoveSystemEventListener(u"keydown"_ns, this, false);
+#endif
+ mEventTarget->RemoveSystemEventListener(u"mousedown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mouseout"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mousemove"_ns, this, false);
+ mTooltipListenerInstalled = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChromeTooltipListener::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("mousedown")) {
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("keydown")) {
+ WidgetKeyboardEvent* keyEvent = aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (!keyEvent->IsModifierKeyEvent()) {
+ return HideTooltip();
+ }
+
+ return NS_OK;
+ } else if (eventType.EqualsLiteral("mouseout")) {
+ // Reset flag so that tooltip will display on the next MouseMove
+ mTooltipShownOnce = false;
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("mousemove")) {
+ return MouseMove(aEvent);
+ }
+
+ NS_ERROR("Unexpected event type");
+ return NS_OK;
+}
+
+// If we're a tooltip, fire off a timer to see if a tooltip should be shown. If
+// the timer fires, we cache the node in |mPossibleTooltipNode|.
+nsresult ChromeTooltipListener::MouseMove(Event* aMouseEvent) {
+ MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_OK;
+ }
+
+ // stash the coordinates of the event so that we can still get back to it from
+ // within the timer callback. On win32, we'll get a MouseMove event even when
+ // a popup goes away -- even when the mouse doesn't change position! To get
+ // around this, we make sure the mouse has really moved before proceeding.
+ int32_t newMouseX = mouseEvent->ClientX();
+ int32_t newMouseY = mouseEvent->ClientY();
+ if (mMouseClientX == newMouseX && mMouseClientY == newMouseY) {
+ return NS_OK;
+ }
+
+ // Filter out minor mouse movements.
+ if (mShowingTooltip &&
+ (abs(mMouseClientX - newMouseX) <= kTooltipMouseMoveTolerance) &&
+ (abs(mMouseClientY - newMouseY) <= kTooltipMouseMoveTolerance)) {
+ return NS_OK;
+ }
+
+ mMouseClientX = newMouseX;
+ mMouseClientY = newMouseY;
+ mMouseScreenX = mouseEvent->ScreenX(CallerType::System);
+ mMouseScreenY = mouseEvent->ScreenY(CallerType::System);
+
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ }
+
+ if (!mShowingTooltip) {
+ nsIEventTarget* target = nullptr;
+ if (nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetComposedTarget()) {
+ mPossibleTooltipNode = do_QueryInterface(eventTarget);
+ nsCOMPtr<nsIGlobalObject> global(eventTarget->GetOwnerGlobal());
+ if (global) {
+ target = global->EventTargetFor(TaskCategory::UI);
+ }
+ }
+
+ if (mPossibleTooltipNode) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTooltipTimer), sTooltipCallback, this,
+ LookAndFeel::GetInt(LookAndFeel::IntID::TooltipDelay, 500),
+ nsITimer::TYPE_ONE_SHOT, "ChromeTooltipListener::MouseMove", target);
+ if (NS_FAILED(rv)) {
+ mPossibleTooltipNode = nullptr;
+ NS_WARNING("Could not create a timer for tooltip tracking");
+ }
+ }
+ } else {
+ mTooltipShownOnce = true;
+ return HideTooltip();
+ }
+
+ return NS_OK;
+}
+
+// Tell the registered chrome that they should show the tooltip.
+NS_IMETHODIMP
+ChromeTooltipListener::ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aTipDir) {
+ nsresult rv = NS_OK;
+
+ // do the work to call the client
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnShowTooltip(aInXCoords, aInYCoords, aInTipText,
+ aTipDir);
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = true;
+ }
+ }
+
+ return rv;
+}
+
+// Tell the registered chrome that they should rollup the tooltip
+// NOTE: This routine is safe to call even if the popup is already closed.
+NS_IMETHODIMP
+ChromeTooltipListener::HideTooltip() {
+ nsresult rv = NS_OK;
+
+ // shut down the relevant timers
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ // release tooltip target
+ mPossibleTooltipNode = nullptr;
+ mLastDocshell = nullptr;
+ }
+
+ // if we're showing the tip, tell the chrome to hide it
+ if (mShowingTooltip) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnHideTooltip();
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = false;
+ }
+ }
+ }
+
+ return rv;
+}
+
+bool ChromeTooltipListener::WebProgressShowedTooltip(
+ nsIWebProgress* aWebProgress) {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(aWebProgress);
+ nsCOMPtr<nsIDocShell> lastUsed = do_QueryReferent(mLastDocshell);
+ while (lastUsed) {
+ if (lastUsed == docshell) {
+ return true;
+ }
+ // We can't use the docshell hierarchy here, because when the parent
+ // docshell is navigated, the child docshell is disconnected (ie its
+ // references to the parent are nulled out) despite it still being
+ // alive here. So we use the document hierarchy instead:
+ Document* document = lastUsed->GetDocument();
+ if (document) {
+ document = document->GetInProcessParentDocument();
+ }
+ if (!document) {
+ break;
+ }
+ lastUsed = document->GetDocShell();
+ }
+ return false;
+}
+
+// A timer callback, fired when the mouse has hovered inside of a frame for the
+// appropriate amount of time. Getting to this point means that we should show
+// the tooltip, but only after we determine there is an appropriate TITLE
+// element.
+//
+// This relies on certain things being cached into the |aChromeTooltipListener|
+// object passed to us by the timer:
+// -- the x/y coordinates of the mouse (mMouseClientY, mMouseClientX)
+// -- the dom node the user hovered over (mPossibleTooltipNode)
+void ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
+ void* aChromeTooltipListener) {
+ auto self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
+ if (self && self->mPossibleTooltipNode) {
+ // release tooltip target once done, no matter what we do here.
+ auto cleanup = MakeScopeExit([&] { self->mPossibleTooltipNode = nullptr; });
+ if (!self->mPossibleTooltipNode->IsInComposedDoc()) {
+ return;
+ }
+ // Check that the document or its ancestors haven't been replaced.
+ Document* doc = self->mPossibleTooltipNode->OwnerDoc();
+ while (doc) {
+ if (!doc->IsCurrentActiveDocument()) {
+ return;
+ }
+ doc = doc->GetInProcessParentDocument();
+ }
+
+ // The actual coordinates we want to put the tooltip at are relative to the
+ // toplevel docshell of our mWebBrowser. We know what the screen
+ // coordinates of the mouse event were, which means we just need the screen
+ // coordinates of the docshell. Unfortunately, there is no good way to
+ // find those short of groveling for the presentation in that docshell and
+ // finding the screen coords of its toplevel widget...
+ nsCOMPtr<nsIDocShell> docShell =
+ do_GetInterface(static_cast<nsIWebBrowser*>(self->mWebBrowser));
+ RefPtr<PresShell> presShell = docShell ? docShell->GetPresShell() : nullptr;
+
+ nsIWidget* widget = nullptr;
+ if (presShell) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsView* view = vm->GetRootView();
+ if (view) {
+ nsPoint offset;
+ widget = view->GetNearestWidget(&offset);
+ }
+ }
+ }
+
+ if (!widget || !docShell || !docShell->GetBrowsingContext()->IsActive()) {
+ return;
+ }
+
+ // if there is text associated with the node, show the tip and fire
+ // off a timer to auto-hide it.
+ nsITooltipTextProvider* tooltipProvider = self->GetTooltipTextProvider();
+ if (tooltipProvider) {
+ nsString tooltipText;
+ nsString directionText;
+ bool textFound = false;
+ tooltipProvider->GetNodeText(self->mPossibleTooltipNode,
+ getter_Copies(tooltipText),
+ getter_Copies(directionText), &textFound);
+
+ if (textFound && (!self->mTooltipShownOnce ||
+ tooltipText != self->mLastShownTooltipText)) {
+ // ShowTooltip expects screen-relative position.
+ self->ShowTooltip(self->mMouseScreenX, self->mMouseScreenY, tooltipText,
+ directionText);
+ self->mLastShownTooltipText = std::move(tooltipText);
+ self->mLastDocshell = do_GetWeakReference(
+ self->mPossibleTooltipNode->OwnerDoc()->GetDocShell());
+ }
+ }
+ }
+}
diff --git a/docshell/base/nsDocShellTreeOwner.h b/docshell/base/nsDocShellTreeOwner.h
new file mode 100644
index 0000000000..eb7ad95288
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.h
@@ -0,0 +1,194 @@
+/* -*- 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 nsDocShellTreeOwner_h__
+#define nsDocShellTreeOwner_h__
+
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIDOMEventListener.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsITooltipTextProvider.h"
+#include "nsCTooltipTextProvider.h"
+
+namespace mozilla {
+namespace dom {
+class Event;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class nsIDocShellTreeItem;
+class nsWebBrowser;
+class ChromeTooltipListener;
+
+class nsDocShellTreeOwner final : public nsIDocShellTreeOwner,
+ public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIWebProgressListener,
+ public nsIDOMEventListener,
+ public nsSupportsWeakReference {
+ friend class nsWebBrowser;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEOWNER
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ protected:
+ nsDocShellTreeOwner();
+ virtual ~nsDocShellTreeOwner();
+
+ void WebBrowser(nsWebBrowser* aWebBrowser);
+
+ nsWebBrowser* WebBrowser();
+ NS_IMETHOD SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner);
+ NS_IMETHOD SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome);
+
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ void EnsurePrompter();
+ void EnsureAuthPrompter();
+
+ void AddToWatcher();
+ void RemoveFromWatcher();
+
+ void EnsureContentTreeOwner();
+
+ // These helper functions return the correct instances of the requested
+ // interfaces. If the object passed to SetWebBrowserChrome() implements
+ // nsISupportsWeakReference, then these functions call QueryReferent on
+ // that object. Otherwise, they return an addrefed pointer. If the
+ // WebBrowserChrome object doesn't exist, they return nullptr.
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+ already_AddRefed<nsIEmbeddingSiteWindow> GetOwnerWin();
+ already_AddRefed<nsIInterfaceRequestor> GetOwnerRequestor();
+
+ protected:
+ // Weak References
+ nsWebBrowser* mWebBrowser;
+ nsIDocShellTreeOwner* mTreeOwner;
+ nsIDocShellTreeItem* mPrimaryContentShell;
+
+ nsIWebBrowserChrome* mWebBrowserChrome;
+ nsIEmbeddingSiteWindow* mOwnerWin;
+ nsIInterfaceRequestor* mOwnerRequestor;
+
+ nsWeakPtr mWebBrowserChromeWeak; // nsIWebBrowserChrome
+
+ // the objects that listen for chrome events like context menus and tooltips.
+ // They are separate objects to avoid circular references between |this|
+ // and the DOM.
+ RefPtr<ChromeTooltipListener> mChromeTooltipListener;
+
+ RefPtr<nsDocShellTreeOwner> mContentTreeOwner;
+
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompter;
+ nsCOMPtr<nsIRemoteTab> mPrimaryRemoteTab;
+};
+
+// The class that listens to the chrome events and tells the embedding chrome to
+// show tooltips, as appropriate. Handles registering itself with the DOM with
+// AddChromeListeners() and removing itself with RemoveChromeListeners().
+class ChromeTooltipListener final : public nsIDOMEventListener {
+ protected:
+ virtual ~ChromeTooltipListener();
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome);
+
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_IMETHOD MouseMove(mozilla::dom::Event* aMouseEvent);
+
+ // Add/remove the relevant listeners, based on what interfaces the embedding
+ // chrome implements.
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ NS_IMETHOD HideTooltip();
+
+ bool WebProgressShowedTooltip(nsIWebProgress* aWebProgress);
+
+ private:
+ // various delays for tooltips
+ enum {
+ kTooltipAutoHideTime = 5000, // ms
+ kTooltipMouseMoveTolerance = 7 // pixel tolerance for mousemove event
+ };
+
+ NS_IMETHOD AddTooltipListener();
+ NS_IMETHOD RemoveTooltipListener();
+
+ NS_IMETHOD ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aDirText);
+ nsITooltipTextProvider* GetTooltipTextProvider();
+
+ nsWebBrowser* mWebBrowser;
+ nsCOMPtr<mozilla::dom::EventTarget> mEventTarget;
+ nsCOMPtr<nsITooltipTextProvider> mTooltipTextProvider;
+
+ // This must be a strong ref in order to make sure we can hide the tooltip if
+ // the window goes away while we're displaying one. If we don't hold a strong
+ // ref, the chrome might have been disposed of before we get a chance to tell
+ // it, and no one would ever tell us of that fact.
+ nsCOMPtr<nsIWebBrowserChrome> mWebBrowserChrome;
+
+ bool mTooltipListenerInstalled;
+
+ nsCOMPtr<nsITimer> mTooltipTimer;
+ static void sTooltipCallback(nsITimer* aTimer, void* aListener);
+
+ // Mouse coordinates for last mousemove event we saw
+ int32_t mMouseClientX;
+ int32_t mMouseClientY;
+
+ // Mouse coordinates for tooltip event
+ int32_t mMouseScreenX;
+ int32_t mMouseScreenY;
+
+ bool mShowingTooltip;
+
+ bool mTooltipShownOnce;
+
+ // The string of text that we last displayed.
+ nsString mLastShownTooltipText;
+
+ nsWeakPtr mLastDocshell;
+
+ // The node hovered over that fired the timer. This may turn into the node
+ // that triggered the tooltip, but only if the timer ever gets around to
+ // firing. This is a strong reference, because the tooltip content can be
+ // destroyed while we're waiting for the tooltip to pop up, and we need to
+ // detect that. It's set only when the tooltip timer is created and launched.
+ // The timer must either fire or be cancelled (or possibly released?), and we
+ // release this reference in each of those cases. So we don't leak.
+ nsCOMPtr<nsINode> mPossibleTooltipNode;
+};
+
+#endif /* nsDocShellTreeOwner_h__ */
diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl
new file mode 100644
index 0000000000..d1eddcc747
--- /dev/null
+++ b/docshell/base/nsIContentViewer.idl
@@ -0,0 +1,348 @@
+/* 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 "nsISupports.idl"
+
+interface nsIDocShell;
+interface nsISHEntry;
+interface nsIPrintSettings;
+webidl Document;
+webidl Node;
+
+%{ C++
+#include "nsTArray.h"
+#include "nsRect.h"
+
+class nsIWidget;
+class nsPresContext;
+class nsView;
+class nsDOMNavigationTiming;
+namespace mozilla {
+class Encoding;
+class PresShell;
+namespace dom {
+class WindowGlobalChild;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ptr] native nsIWidgetPtr(nsIWidget);
+[ref] native nsIntRectRef(nsIntRect);
+[ptr] native nsPresContextPtr(nsPresContext);
+[ptr] native nsViewPtr(nsView);
+[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
+[ptr] native Encoding(const mozilla::Encoding);
+[ptr] native PresShellPtr(mozilla::PresShell);
+[ptr] native WindowGlobalChildPtr(mozilla::dom::WindowGlobalChild);
+
+[scriptable, builtinclass, uuid(2da17016-7851-4a45-a7a8-00b360e01595)]
+interface nsIContentViewer : nsISupports
+{
+ [noscript] void init(in nsIWidgetPtr aParentWidget,
+ [const] in nsIntRectRef aBounds,
+ in WindowGlobalChildPtr aWindowActor);
+
+ attribute nsIDocShell container;
+
+ [noscript,notxpcom,nostdcall] void loadStart(in Document aDoc);
+ [can_run_script] void loadComplete(in nsresult aStatus);
+ [notxpcom,nostdcall] readonly attribute boolean loadCompleted;
+
+ [notxpcom,nostdcall] readonly attribute boolean isStopped;
+
+ /**
+ * aAction is passed to PermitUnload to indicate what action to take
+ * if a beforeunload handler wants to prompt the user.
+ *
+ * ePrompt: Prompt and return the user's choice (default).
+ * eDontPromptAndDontUnload: Don't prompt and return false (unload not permitted)
+ * if the document (or its children) asks us to prompt.
+ * eDontPromptAndUnload: Don't prompt and return true (unload permitted) no matter what.
+ *
+ * NOTE: Keep this in sync with PermitUnloadAction in WindowGlobalActors.webidl.
+ */
+ cenum PermitUnloadAction : 8 {
+ ePrompt = 0,
+ eDontPromptAndDontUnload = 1,
+ eDontPromptAndUnload = 2
+ };
+
+ /**
+ * The result of dispatching a "beforeunload" event. If `eAllowNavigation`,
+ * no "beforeunload" listener requested to prevent the navigation, or its
+ * request was ignored. If `eRequestBlockNavigation`, a listener did request
+ * to block the navigation, and the user should be prompted.
+ */
+ cenum PermitUnloadResult : 8 {
+ eAllowNavigation = 0,
+ eRequestBlockNavigation = 1,
+ };
+
+ /**
+ * Overload PermitUnload method for C++ consumers with no aPermitUnloadFlags
+ * argument.
+ */
+ %{C++
+ nsresult PermitUnload(bool* canUnload) {
+ return PermitUnload(ePrompt, canUnload);
+ }
+ %}
+
+ /**
+ * Checks if the document wants to prevent unloading by firing beforeunload on
+ * the document.
+ * The result is returned.
+ */
+ boolean permitUnload([optional] in nsIContentViewer_PermitUnloadAction aAction);
+
+ /**
+ * Exposes whether we're blocked in a call to permitUnload.
+ */
+ readonly attribute boolean inPermitUnload;
+
+ /**
+ * Dispatches the "beforeunload" event and returns the result, as documented
+ * in the `PermitUnloadResult` enum.
+ */
+ [noscript,nostdcall,notxpcom] nsIContentViewer_PermitUnloadResult dispatchBeforeUnload();
+
+ /**
+ * Exposes whether we're in the process of firing the beforeunload event.
+ * In this case, the corresponding docshell will not allow navigation.
+ */
+ readonly attribute boolean beforeUnloadFiring;
+
+ void pageHide(in boolean isUnload);
+
+ /**
+ * All users of a content viewer are responsible for calling both
+ * close() and destroy(), in that order.
+ *
+ * close() should be called when the load of a new page for the next
+ * content viewer begins, and destroy() should be called when the next
+ * content viewer replaces this one.
+ *
+ * |historyEntry| sets the session history entry for the content viewer. If
+ * this is null, then Destroy() will be called on the document by close().
+ * If it is non-null, the document will not be destroyed, and the following
+ * actions will happen when destroy() is called (*):
+ * - Sanitize() will be called on the viewer's document
+ * - The content viewer will set the contentViewer property on the
+ * history entry, and release its reference (ownership reversal).
+ * - hide() will be called, and no further destruction will happen.
+ *
+ * (*) unless the document is currently being printed, in which case
+ * it will never be saved in session history.
+ *
+ */
+ void close(in nsISHEntry historyEntry);
+ void destroy();
+
+ void stop();
+
+ /**
+ * Returns the same thing as getDocument(), but for use from script
+ * only. C++ consumers should use getDocument().
+ */
+ readonly attribute Document DOMDocument;
+
+ /**
+ * Returns DOMDocument without addrefing.
+ */
+ [noscript,notxpcom,nostdcall] Document getDocument();
+
+ /**
+ * Allows setting the document.
+ */
+ [noscript,nostdcall] void setDocument(in Document aDocument);
+
+ [noscript] void getBounds(in nsIntRectRef aBounds);
+ [noscript] void setBounds([const] in nsIntRectRef aBounds);
+ /**
+ * The 'aFlags' argument to setBoundsWithFlags is a set of these bits.
+ */
+ const unsigned long eDelayResize = 1;
+ [noscript] void setBoundsWithFlags([const] in nsIntRectRef aBounds,
+ in unsigned long aFlags);
+
+ /**
+ * The previous content viewer, which has been |close|d but not
+ * |destroy|ed.
+ */
+ [notxpcom,nostdcall] attribute nsIContentViewer previousViewer;
+
+ void move(in long aX, in long aY);
+
+ void show();
+ void hide();
+
+ attribute boolean sticky;
+
+ /**
+ * Attach the content viewer to its DOM window and docshell.
+ * @param aState A state object that might be useful in attaching the DOM
+ * window.
+ * @param aSHEntry The history entry that the content viewer was stored in.
+ * The entry must have the docshells for all of the child
+ * documents stored in its child shell list.
+ */
+ void open(in nsISupports aState, in nsISHEntry aSHEntry);
+
+ /**
+ * Clears the current history entry. This is used if we need to clear out
+ * the saved presentation state.
+ */
+ void clearHistoryEntry();
+
+ /**
+ * Change the layout to view the document with page layout (like print preview), but
+ * dynamic and editable (like Galley layout).
+ */
+ void setPageModeForTesting(in boolean aPageMode,
+ in nsIPrintSettings aPrintSettings);
+
+ /**
+ * Get the history entry that this viewer will save itself into when
+ * destroyed. Can return null
+ */
+ readonly attribute nsISHEntry historyEntry;
+
+ /**
+ * Indicates when we're in a state where content shouldn't be allowed to
+ * trigger a tab-modal prompt (as opposed to a window-modal prompt) because
+ * we're part way through some operation (eg beforeunload) that shouldn't be
+ * rentrant if the user closes the tab while the prompt is showing.
+ * See bug 613800.
+ */
+ readonly attribute boolean isTabModalPromptAllowed;
+
+ /**
+ * Returns whether this content viewer is in a hidden state.
+ *
+ * @note Only Gecko internal code should set the attribute!
+ */
+ attribute boolean isHidden;
+
+ // presShell can be null.
+ [notxpcom,nostdcall] readonly attribute PresShellPtr presShell;
+ // presContext can be null.
+ [notxpcom,nostdcall] readonly attribute nsPresContextPtr presContext;
+ // aDocument must not be null.
+ [noscript] void setDocumentInternal(in Document aDocument,
+ in boolean aForceReuseInnerWindow);
+ /**
+ * Find the view to use as the container view for MakeWindow. Returns
+ * null if this will be the root of a view manager hierarchy. In that
+ * case, if mParentWidget is null then this document should not even
+ * be displayed.
+ */
+ [noscript,notxpcom,nostdcall] nsViewPtr findContainerView();
+ /**
+ * Set collector for navigation timing data (load, unload events).
+ */
+ [noscript,notxpcom,nostdcall] void setNavigationTiming(in nsDOMNavigationTimingPtr aTiming);
+
+ /**
+ * The actual full zoom in effect, as modified by the device context.
+ * For a requested full zoom, the device context may choose a slightly
+ * different effectiveFullZoom to accomodate integer rounding of app units
+ * per dev pixel. This property returns the actual zoom amount in use,
+ * though it may not be good user experience to report that a requested zoom
+ * of 90% is actually 89.1%, for example. This value is provided primarily to
+ * support media queries of dppx values, because those queries are matched
+ * against the actual native device pixel ratio and the actual full zoom.
+ *
+ * You should only need this for testing.
+ */
+ readonly attribute float deviceFullZoomForTest;
+
+ /**
+ * The value used to override devicePixelRatio and media queries dppx.
+ * Default is 0.0, that means no overriding is done (only a positive value
+ * is applied).
+ */
+ attribute float overrideDPPX;
+
+ /**
+ * Disable entire author style level (including HTML presentation hints),
+ * for this viewer but not any child viewers.
+ */
+ attribute boolean authorStyleDisabled;
+
+ /**
+ * XXX comm-central only: bug 829543.
+ */
+ attribute ACString hintCharacterSet;
+
+ /**
+ * XXX comm-central only: bug 829543.
+ */
+ attribute int32_t hintCharacterSetSource;
+
+ /**
+ * Requests the size of the content to the container.
+ */
+ void getContentSize(out long width, out long height);
+
+ /**
+ * Returns the preferred width and height of the content, constrained to the
+ * given maximum values. If either maxWidth or maxHeight is less than zero,
+ * that dimension is not constrained.
+ *
+ * All input and output values are in device pixels, rather than CSS pixels.
+ */
+ void getContentSizeConstrained(in long maxWidth, in long maxHeight,
+ out long width, out long height);
+
+ /**
+ * Instruct the refresh driver to discontinue painting until further
+ * notice.
+ */
+ void pausePainting();
+
+ /**
+ * Instruct the refresh driver to resume painting after a previous call to
+ * pausePainting().
+ */
+ void resumePainting();
+
+ /*
+ * Render the document as if being viewed on a device with the specified
+ * media type. This will cause a reflow.
+ *
+ * @param mediaType The media type to be emulated
+ */
+ void emulateMedium(in AString aMediaType);
+
+ /*
+ * Restore the viewer's natural media type
+ */
+ void stopEmulatingMedium();
+
+ cenum PrefersColorScheme : 8 {
+ PREFERS_COLOR_SCHEME_LIGHT,
+ PREFERS_COLOR_SCHEME_DARK,
+ PREFERS_COLOR_SCHEME_NONE, /* This clears the override. */
+ };
+
+ /*
+ * Emulate or stop emulating the prefers color scheme on this page and
+ * subdocuments.
+ */
+ void emulatePrefersColorScheme(in nsIContentViewer_PrefersColorScheme aPrefersColorScheme);
+
+ [noscript, notxpcom] Encoding getHintCharset();
+ [noscript, notxpcom] void setHintCharset(in Encoding aEncoding);
+};
+
+%{C++
+namespace mozilla {
+namespace dom {
+
+using XPCOMPermitUnloadAction = nsIContentViewer::PermitUnloadAction;
+using PermitUnloadResult = nsIContentViewer::PermitUnloadResult;
+
+} // namespace dom
+} // namespace mozilla
+%}
diff --git a/docshell/base/nsIContentViewerEdit.idl b/docshell/base/nsIContentViewerEdit.idl
new file mode 100644
index 0000000000..01b6f47806
--- /dev/null
+++ b/docshell/base/nsIContentViewerEdit.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl Node;
+
+[scriptable, uuid(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)]
+interface nsIContentViewerEdit : nsISupports
+{
+ void clearSelection();
+ void selectAll();
+
+ void copySelection();
+ readonly attribute boolean copyable;
+
+ void copyLinkLocation();
+ readonly attribute boolean inLink;
+
+ const long COPY_IMAGE_TEXT = 0x0001;
+ const long COPY_IMAGE_HTML = 0x0002;
+ const long COPY_IMAGE_DATA = 0x0004;
+ const long COPY_IMAGE_ALL = -1;
+ void copyImage(in long aCopyFlags);
+ readonly attribute boolean inImage;
+
+ AString getContents(in string aMimeType, in boolean aSelectionOnly);
+ readonly attribute boolean canGetContents;
+
+ // Set the node that will be the subject of the editing commands above.
+ // Usually this will be the node that was context-clicked.
+ void setCommandNode(in Node aNode);
+};
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
new file mode 100644
index 0000000000..afa1eee3a6
--- /dev/null
+++ b/docshell/base/nsIDocShell.idl
@@ -0,0 +1,900 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+#include "nsIDocShellTreeItem.idl"
+#include "nsIRequest.idl"
+
+%{ C++
+#include "js/TypeDecls.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+class nsCommandManager;
+class nsPresContext;
+class nsDocShellLoadState;
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class PresShell;
+namespace dom {
+class BrowsingContext;
+class ClientSource;
+} // namespace dom
+}
+%}
+
+/**
+ * The nsIDocShell interface.
+ */
+
+[ptr] native nsPresContext(nsPresContext);
+[ptr] native nsCommandManager(nsCommandManager);
+[ptr] native PresShell(mozilla::PresShell);
+[ref] native MaybeURI(mozilla::Maybe<nsCOMPtr<nsIURI>>);
+[ref] native Encoding(const mozilla::Encoding*);
+ native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+
+interface nsIURI;
+interface nsIChannel;
+interface nsIContentViewer;
+interface nsIContentSecurityPolicy;
+interface nsIDocShellLoadInfo;
+interface nsIEditor;
+interface nsIEditingSession;
+interface nsIInputStream;
+interface nsIRequest;
+interface nsISHEntry;
+interface nsILayoutHistoryState;
+interface nsISecureBrowserUI;
+interface nsIScriptGlobalObject;
+interface nsIStructuredCloneContainer;
+interface nsIDOMStorage;
+interface nsIPrincipal;
+interface nsIWebBrowserPrint;
+interface nsIPrivacyTransitionObserver;
+interface nsIReflowObserver;
+interface nsIScrollObserver;
+interface nsIRemoteTab;
+interface nsIBrowserChild;
+interface nsICommandParams;
+interface nsILoadURIDelegate;
+native BrowserChildRef(already_AddRefed<nsIBrowserChild>);
+native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+
+webidl BrowsingContext;
+webidl ContentFrameMessageManager;
+webidl EventTarget;
+webidl Document;
+
+[scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
+interface nsIDocShell : nsIDocShellTreeItem
+{
+ void setCancelContentJSEpoch(in long aEpoch);
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URL dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aLoadState This is the extended load info for this load.
+ * @param aSetNavigating If we should set isNavigating to true while initiating
+ * the load.
+ */
+ [noscript]void loadURI(in nsDocShellLoadStatePtr aLoadState, in boolean aSetNavigating);
+
+ /**
+ * Do either a history.pushState() or history.replaceState() operation,
+ * depending on the value of aReplace.
+ */
+ [implicit_jscontext]
+ void addState(in jsval aData, in AString aTitle,
+ in AString aURL, in boolean aReplace);
+
+ /**
+ * Helper for addState and document.open that does just the
+ * history-manipulation guts.
+ *
+ * Arguments the spec defines:
+ *
+ * @param aDocument the document we're manipulating. This will get the new URI.
+ * @param aNewURI the new URI.
+ * @param aData The serialized state data. May be null.
+ * @param aTitle The new title. May be empty.
+ * @param aReplace whether this should replace the exising SHEntry.
+ *
+ * Arguments we need internally because deriving them from the
+ * others is a bit complicated:
+ *
+ * @param aCurrentURI the current URI we're working with. Might be null.
+ * @param aEqualURIs whether the two URIs involved are equal.
+ */
+ [nostdcall]
+ void updateURLAndHistory(in Document aDocument, in nsIURI aNewURI,
+ in nsIStructuredCloneContainer aData, in AString aTitle,
+ in boolean aReplace, in nsIURI aCurrentURI,
+ in boolean aEqualURIs);
+
+ /**
+ * 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().
+ */
+ void prepareForNewContentModel();
+
+ /**
+ * For editors and suchlike who wish to change the URI associated with the
+ * document. Note if you want to get the current URI, use the read-only
+ * property on nsIWebNavigation.
+ */
+ void setCurrentURI(in nsIURI aURI);
+
+ /**
+ * Notify the associated content viewer and all child docshells that they are
+ * about to be hidden. If |isUnload| is true, then the document is being
+ * unloaded and all dynamic subframe history entries are removed as well.
+ *
+ * @param isUnload
+ * True to fire the unload event in addition to the pagehide event,
+ * and remove all dynamic subframe history entries.
+ */
+ [noscript] void firePageHideNotification(in boolean isUnload);
+
+ /**
+ * Presentation context for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute nsPresContext presContext;
+
+ /**
+ * Presentation shell for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell presShell;
+
+ /**
+ * Presentation shell for the oldest document, if this docshell is
+ * currently transitioning between documents.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell eldestPresShell;
+
+ /**
+ * Content Viewer that is currently loaded for this DocShell. This may
+ * change as the underlying content changes.
+ */
+ [infallible] readonly attribute nsIContentViewer contentViewer;
+
+ /**
+ * Get the id of the outer window that is or will be in this docshell.
+ */
+ [infallible] readonly attribute unsigned long long outerWindowID;
+
+ /**
+ * This attribute allows chrome to tie in to handle DOM events that may
+ * be of interest to chrome.
+ */
+ attribute EventTarget chromeEventHandler;
+
+ /**
+ * This allows chrome to set a custom User agent on a specific docshell
+ */
+ attribute AString customUserAgent;
+
+ /**
+ * Whether CSS error reporting is enabled.
+ */
+ attribute boolean cssErrorReportingEnabled;
+
+ /**
+ * Whether to allow plugin execution
+ */
+ attribute boolean allowPlugins;
+
+ /**
+ * Whether to allow Javascript execution
+ */
+ attribute boolean allowJavascript;
+
+ /**
+ * Attribute stating if refresh based redirects can be allowed
+ */
+ attribute boolean allowMetaRedirects;
+
+ /**
+ * Attribute stating if it should allow subframes (framesets/iframes) or not
+ */
+ attribute boolean allowSubframes;
+
+ /**
+ * Attribute stating whether or not images should be loaded.
+ */
+ attribute boolean allowImages;
+
+ /**
+ * Attribute stating whether or not media (audio/video) should be loaded.
+ */
+ [infallible] attribute boolean allowMedia;
+
+ /**
+ * Attribute that determines whether DNS prefetch is allowed for this subtree
+ * of the docshell tree. Defaults to true. Setting this will make it take
+ * effect starting with the next document loaded in the docshell.
+ */
+ attribute boolean allowDNSPrefetch;
+
+ /**
+ * Attribute that determines whether window control (move/resize) is allowed.
+ */
+ attribute boolean allowWindowControl;
+
+ /**
+ * True if the docshell allows its content to be handled by a content listener
+ * other than the docshell itself, including the external helper app service,
+ * and false otherwise. Defaults to true.
+ */
+ [infallible] attribute boolean allowContentRetargeting;
+
+ /**
+ * True if new child docshells should allow content retargeting.
+ * Setting allowContentRetargeting also overwrites this value.
+ */
+ [infallible] attribute boolean allowContentRetargetingOnChildren;
+
+ /**
+ * Get an array of this docShell and its children.
+ *
+ * @param aItemType - Only include docShells of this type, or if typeAll,
+ * include all child shells.
+ * Uses types from nsIDocShellTreeItem.
+ * @param aDirection - Whether to enumerate forwards or backwards.
+ */
+
+ cenum DocShellEnumeratorDirection : 8 {
+ ENUMERATE_FORWARDS = 0,
+ ENUMERATE_BACKWARDS = 1
+ };
+
+ Array<nsIDocShell> getAllDocShellsInSubtree(in long aItemType,
+ in nsIDocShell_DocShellEnumeratorDirection aDirection);
+
+ /**
+ * The type of application that created this window.
+ *
+ * DO NOT DELETE, see bug 176166. For firefox, this value will always be
+ * UNKNOWN. However, it is used heavily in Thunderbird/comm-central and we
+ * don't really have a great replacement at the moment, so we'll just leave it
+ * here.
+ */
+ cenum AppType : 8 {
+ APP_TYPE_UNKNOWN = 0,
+ APP_TYPE_MAIL = 1,
+ APP_TYPE_EDITOR = 2
+ };
+
+ [infallible] attribute nsIDocShell_AppType appType;
+
+ /**
+ * certain docshells (like the message pane)
+ * should not throw up auth dialogs
+ * because it can act as a password trojan
+ */
+ attribute boolean allowAuth;
+
+ /**
+ * Set/Get the document scale factor. When setting this attribute, a
+ * NS_ERROR_NOT_IMPLEMENTED error may be returned by implementations
+ * not supporting zoom. Implementations not supporting zoom should return
+ * 1.0 all the time for the Get operation. 1.0 by the way is the default
+ * of zoom. This means 100% of normal scaling or in other words normal size
+ * no zoom.
+ */
+ attribute float zoom;
+
+ /*
+ * Tells the docshell to offer focus to its tree owner.
+ * This is currently only necessary for embedding chrome.
+ * If forDocumentNavigation is true, then document navigation should be
+ * performed, where only the root of documents are selected. Otherwise, the
+ * next element in the parent should be returned. Returns true if focus was
+ * successfully taken by the tree owner.
+ */
+ bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation);
+
+ /**
+ * Current busy state for DocShell
+ */
+ cenum BusyFlags : 8 {
+ BUSY_FLAGS_NONE = 0,
+ BUSY_FLAGS_BUSY = 1,
+ BUSY_FLAGS_BEFORE_PAGE_LOAD = 2,
+ BUSY_FLAGS_PAGE_LOADING = 4,
+ };
+
+ [infallible] readonly attribute nsIDocShell_BusyFlags busyFlags;
+
+ /**
+ * Load commands for the document
+ */
+ cenum LoadCommand : 8 {
+ LOAD_CMD_NORMAL = 0x1, // Normal load
+ LOAD_CMD_RELOAD = 0x2, // Reload
+ LOAD_CMD_HISTORY = 0x4, // Load from history
+ LOAD_CMD_PUSHSTATE = 0x8, // History.pushState()
+ };
+
+ /*
+ * Attribute to access the loadtype for the document. LoadType Enum is
+ * defined in nsDocShellLoadTypes.h
+ */
+ [infallible] attribute unsigned long loadType;
+
+ /*
+ * Default load flags (as defined in nsIRequest) that will be set on all
+ * requests made by this docShell and propagated to all child docShells and
+ * to nsILoadGroup::defaultLoadFlags for the docShell's loadGroup.
+ * Default is no flags. Once set, only future requests initiated by the
+ * docShell are affected, so in general, these flags should be set before
+ * the docShell loads any content.
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /*
+ * returns true if the docshell is being destroyed, false otherwise
+ */
+ boolean isBeingDestroyed();
+
+ /*
+ * Returns true if the docshell is currently executing the onLoad Handler
+ */
+ readonly attribute boolean isExecutingOnLoadHandler;
+
+ attribute nsILayoutHistoryState layoutHistoryState;
+
+ /**
+ * Object used to delegate URI loading to an upper context.
+ * Currently only set for GeckoView to allow handling of load requests
+ * at the application level.
+ */
+ readonly attribute nsILoadURIDelegate loadURIDelegate;
+
+ /**
+ * Cancel the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. The meta-refresh timers can be
+ * restarted using resumeRefreshURIs(). If the timers are already suspended,
+ * this has no effect.
+ */
+ void suspendRefreshURIs();
+
+ /**
+ * Restart the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. If the timers are already
+ * running, this has no effect.
+ */
+ void resumeRefreshURIs();
+
+ /**
+ * Begin firing WebProgressListener notifications for restoring a page
+ * presentation. |viewer| is the content viewer whose document we are
+ * starting to load. If null, it defaults to the docshell's current content
+ * viewer, creating one if necessary. |top| should be true for the toplevel
+ * docshell that is being restored; it will be set to false when this method
+ * is called for child docshells. This method will post an event to
+ * complete the simulated load after returning to the event loop.
+ */
+ void beginRestore(in nsIContentViewer viewer, in boolean top);
+
+ /**
+ * Finish firing WebProgressListener notifications and DOM events for
+ * restoring a page presentation. This should only be called via
+ * beginRestore().
+ */
+ void finishRestore();
+
+ void clearCachedUserAgent();
+
+ void clearCachedPlatform();
+
+ /* Track whether we're currently restoring a document presentation. */
+ readonly attribute boolean restoringDocument;
+
+ /* attribute to access whether error pages are enabled */
+ attribute boolean useErrorPages;
+
+ /**
+ * Display a load error in a frame while keeping that frame's currentURI
+ * pointing correctly to the page where the error ocurred, rather than to
+ * the error document page. You must provide either the aURI or aURL parameter.
+ *
+ * @param aError The error code to be displayed
+ * @param aURI nsIURI of the page where the error happened
+ * @param aURL wstring of the page where the error happened
+ * @param aFailedChannel The channel related to this error
+ *
+ * Returns whether or not we displayed an error page (note: will always
+ * return false if in-content error pages are disabled!)
+ */
+ boolean displayLoadError(in nsresult aError,
+ in nsIURI aURI,
+ in wstring aURL,
+ [optional] in nsIChannel aFailedChannel);
+
+ /**
+ * The channel that failed to load and resulted in an error page.
+ * May be null. Relevant only to error pages.
+ */
+ readonly attribute nsIChannel failedChannel;
+
+ /**
+ * Keeps track of the previous nsISHEntry index and the current
+ * nsISHEntry index at the time that the doc shell begins to load.
+ * Used for ContentViewer eviction.
+ */
+ readonly attribute long previousEntryIndex;
+ readonly attribute long loadedEntryIndex;
+
+ /**
+ * Notification that entries have been removed from the beginning of a
+ * nsSHistory which has this as its rootDocShell.
+ *
+ * @param numEntries - The number of entries removed
+ */
+ void historyPurged(in long numEntries);
+
+ /**
+ * Gets the channel for the currently loaded document, if any.
+ * For a new document load, this will be the channel of the previous document
+ * until after OnLocationChange fires.
+ */
+ readonly attribute nsIChannel currentDocumentChannel;
+
+ /**
+ * The original offset of this child in its container. This property is -1 for
+ * dynamically added docShells.
+ */
+ [notxpcom,nostdcall] attribute long childOffset;
+
+ /**
+ * Find out whether the docshell is currently in the middle of a page
+ * transition. This is set just before the pagehide/unload events fire.
+ */
+ [infallible] readonly attribute boolean isInUnload;
+
+ /**
+ * Disconnects this docshell's editor from its window, and stores the
+ * editor data in the open document's session history entry. This
+ * should be called only during page transitions.
+ */
+ [noscript, notxpcom] void DetachEditorFromWindow();
+
+ /**
+ * If true, this browser is not visible in the traditional sense, but
+ * is actively being rendered to the screen (ex. painted on a canvas)
+ * and should be treated accordingly.
+ **/
+ attribute boolean isOffScreenBrowser;
+
+ /**
+ * Propagated to the print preview document viewer. Must only be called on
+ * a document viewer that has been initialized for print preview.
+ */
+ void exitPrintPreview();
+
+ /**
+ * Whether this docshell can execute scripts based on its hierarchy.
+ * The rule of thumb here is that we disable js if this docshell or any
+ * of its parents disallow scripting.
+ */
+ [infallible] readonly attribute boolean canExecuteScripts;
+
+ /**
+ * The ID of the docshell in the session history.
+ */
+ readonly attribute nsIDRef historyID;
+
+ /**
+ * Helper method for accessing this value from C++
+ */
+ [noscript, notxpcom] nsIDRef HistoryID();
+
+ /**
+ * Sets whether a docshell is an app tab. An app tab docshell may behave
+ * differently than a non-app tab docshell in some cases, such as when
+ * handling link clicks. Docshells are not app tabs unless told otherwise.
+ */
+ attribute boolean isAppTab;
+
+ /**
+ * Create a new about:blank document and content viewer.
+ * @param aPrincipal the principal to use for the new document.
+ * @param aPartitionedPrincipal the partitioned principal to use for the new
+ * document.
+ * @param aCsp the CSP to use for the new document.
+ */
+ void createAboutBlankContentViewer(in nsIPrincipal aPrincipal,
+ in nsIPrincipal aPartitionedPrincipal,
+ [optional] in nsIContentSecurityPolicy aCSP);
+
+ /**
+ * Upon getting, returns the canonical encoding label of the document
+ * currently loaded into this docshell.
+ *
+ * Upon setting, sets the forced encoding for compatibility with legacy callers.
+ */
+ attribute ACString charset;
+
+ /**
+ * Called when the user chose an encoding override from the character
+ * encoding menu. Separate from the setter for the charset property to avoid
+ * extensions adding noise to the data.
+ */
+ void gatherCharsetMenuTelemetry();
+
+ /**
+ * In a child docshell, this is the charset of the parent docshell
+ */
+ [noscript, notxpcom, nostdcall] void setParentCharset(
+ in Encoding parentCharset,
+ in int32_t parentCharsetSource,
+ in nsIPrincipal parentCharsetPrincipal);
+ [noscript, notxpcom, nostdcall] void getParentCharset(
+ out Encoding parentCharset,
+ out int32_t parentCharsetSource,
+ out nsIPrincipal parentCharsetPrincipal);
+
+ /**
+ * Whether the docShell records profile timeline markers at the moment
+ */
+ [infallible] attribute boolean recordProfileTimelineMarkers;
+
+ /**
+ * Return a DOMHighResTimeStamp representing the number of
+ * milliseconds from an arbitrary point in time. The reference
+ * point is shared by all DocShells and is also used by timestamps
+ * on markers.
+ */
+ DOMHighResTimeStamp now();
+
+ /**
+ * Returns and flushes the profile timeline markers gathered by the docShell
+ */
+ [implicit_jscontext]
+ jsval popProfileTimelineMarkers();
+
+ /**
+ * Add an observer to the list of parties to be notified when this docshell's
+ * private browsing status is changed. |obs| must support weak references.
+ */
+ void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when reflows are
+ * occurring. |obs| must support weak references.
+ */
+ void addWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Remove an observer from the list of parties to be notified about reflows.
+ */
+ void removeWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Notify all attached observers that a reflow has just occurred.
+ *
+ * @param interruptible if true, the reflow was interruptible.
+ * @param start timestamp when reflow started, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ [noscript] void notifyReflowObservers(in bool interruptible,
+ in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void addWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void removeWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Notify all attached observers that the scroll position of some element
+ * has changed.
+ */
+ [noscript] void notifyScrollObservers();
+
+ /**
+ * Returns true if this docshell is the top level content docshell.
+ */
+ [infallible] readonly attribute boolean isTopLevelContentDocShell;
+
+ /**
+ * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
+ * mozbrowser> boundaries. Which no longer exist.
+ *
+ * @deprecated: Use `BrowsingContext::GetParent()` in the future.
+ */
+ nsIDocShell getSameTypeInProcessParentIgnoreBrowserBoundaries();
+
+ /**
+ * True iff asynchronous panning and zooming is enabled for this
+ * docshell.
+ */
+ readonly attribute bool asyncPanZoomEnabled;
+
+ /**
+ * This member variable determines whether a document has Mixed Active Content that
+ * was initially blocked from loading, but the user has choosen to override the
+ * block and allow the content to load. mMixedContentChannel is set to the document's
+ * channel when the user allows mixed content. The nsMixedContentBlocker content policy
+ * checks if the document's root channel matches the mMixedContentChannel. If it matches,
+ * then Mixed Content is loaded. If it does match, mixed content is blocked.
+ *
+ * A match implies that there is definitely mixed active content on a page that was
+ * initially blocked by nsMixedContentBlocker and then allowed and loaded by the user.
+ * A miss imples that IF there is mixed active content on the page AND it was
+ * blocked by nsMixedContentBlocker.cpp, the user has not choosen to override
+ * the block. Note that if the about:config setting
+ * security.mixed_content.block_active_content is set to false, this boolean
+ * will be false, mMixedContentChannel will remain null since blocking active content has
+ * been disabled and hence mMixedContentChannel will never be set.
+ */
+ attribute nsIChannel mixedContentChannel;
+
+ /**
+ * Are plugins allowed in the current document loaded in this docshell ?
+ * (if there is one). This depends on whether plugins are allowed by this
+ * docshell itself or if the document is sandboxed and hence plugins should
+ * not be allowed.
+ */
+ [noscript, notxpcom] bool pluginsAllowedInCurrentDoc();
+
+ [noscript, infallible] attribute boolean affectPrivateSessionLifetime;
+
+ /**
+ * Indicates whether the UI may enable the character encoding menu. The UI
+ * must disable the menu when this property is false.
+ */
+ [infallible] readonly attribute boolean mayEnableCharacterEncodingMenu;
+
+ /**
+ * Indicates that the character encoding was autodetected.
+ */
+ [infallible] readonly attribute boolean charsetAutodetected;
+
+ attribute nsIEditor editor;
+ readonly attribute boolean editable; /* this docShell is editable */
+ readonly attribute boolean hasEditingSession; /* this docShell has an editing session */
+
+ /**
+ * Make this docShell editable, setting a flag that causes
+ * an editor to get created, either immediately, or after
+ * a url has been loaded.
+ * @param inWaitForUriLoad true to wait for a URI before
+ * creating the editor.
+ */
+ void makeEditable(in boolean inWaitForUriLoad);
+
+ /**
+ * Returns false for mLSHE, true for mOSHE
+ */
+ boolean getCurrentSHEntry(out nsISHEntry aEntry);
+
+ /**
+ * Cherry picked parts of nsIController.
+ * They are here, because we want to call these functions
+ * from JS.
+ */
+ boolean isCommandEnabled(in string command);
+ [can_run_script]
+ void doCommand(in string command);
+ [can_run_script]
+ void doCommandWithParams(in string command, in nsICommandParams aParams);
+
+ /**
+ * Invisible DocShell are dummy construct to simulate DOM windows
+ * without any actual visual representation. They have to be marked
+ * at construction time, to avoid any painting activity.
+ */
+ [noscript, notxpcom] bool IsInvisible();
+ [noscript, notxpcom] void SetInvisible(in bool aIsInvisibleDocshell);
+
+/**
+ * Get the script global for the document in this docshell.
+*/
+ [noscript,notxpcom,nostdcall] nsIScriptGlobalObject GetScriptGlobalObject();
+
+ [noscript,notxpcom,nostdcall] Document getExtantDocument();
+
+ /**
+ * If deviceSizeIsPageSize is set to true, device-width/height media queries
+ * will be calculated from the page size, not the device size.
+ *
+ * Used by the Responsive Design Mode and B2G Simulator.
+ *
+ * Default is False.
+ * Default value can be overriden with
+ * docshell.device_size_is_page_size pref.
+ */
+ [infallible] attribute boolean deviceSizeIsPageSize;
+
+ /**
+ * Notify DocShell when the browser is about to start executing JS, and after
+ * that execution has stopped. This only occurs when the Timeline devtool
+ * is collecting information.
+ */
+ [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
+ in AString functionName,
+ in AString fileName,
+ in unsigned long lineNumber,
+ in jsval asyncStack,
+ in string asyncCause);
+ [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
+
+ /**
+ * This attribute determines whether a document which is not about:blank has
+ * already be loaded by this docShell.
+ */
+ [infallible] readonly attribute boolean hasLoadedNonBlankURI;
+
+ /**
+ * Allow usage of -moz-window-dragging:drag for content docshells.
+ * True for top level chrome docshells. Throws if set to false with
+ * top level chrome docshell.
+ */
+ attribute boolean windowDraggingAllowed;
+
+ /**
+ * Sets/gets the current scroll restoration mode.
+ * @see https://html.spec.whatwg.org/#dom-history-scroll-restoration
+ */
+ attribute boolean currentScrollRestorationIsManual;
+
+ /**
+ * Setter and getter for the origin attributes living on this docshell.
+ */
+ [implicit_jscontext]
+ jsval getOriginAttributes();
+
+ [implicit_jscontext]
+ void setOriginAttributes(in jsval aAttrs);
+
+ /**
+ * The editing session for this docshell.
+ */
+ readonly attribute nsIEditingSession editingSession;
+
+ /**
+ * The browser child for this docshell.
+ */
+ [binaryname(ScriptableBrowserChild)] readonly attribute nsIBrowserChild browserChild;
+ [noscript,notxpcom,nostdcall] BrowserChildRef GetBrowserChild();
+
+ [noscript,nostdcall,notxpcom] nsCommandManager GetCommandManager();
+
+ cenum MetaViewportOverride: 8 {
+ /**
+ * Override platform/pref default behaviour and force-disable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_DISABLED = 0,
+ /**
+ * Override platform/pref default behaviour and force-enable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_ENABLED = 1,
+ /**
+ * Don't override the platform/pref default behaviour for support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_NONE = 2,
+ };
+
+ /**
+ * This allows chrome to override the default choice of whether the
+ * <meta name="viewport"> tag is respected in a specific docshell.
+ * Possible values are listed above.
+ */
+ [infallible] attribute nsIDocShell_MetaViewportOverride metaViewportOverride;
+
+ /**
+ * Attribute that determines whether tracking protection is enabled.
+ */
+ attribute boolean useTrackingProtection;
+
+ /**
+ * Fire a dummy location change event asynchronously.
+ */
+ [noscript] void dispatchLocationChangeEvent();
+
+
+ /**
+ * Start delayed autoplay media which are in the current document.
+ */
+ [noscript] void startDelayedAutoplayMediaComponents();
+
+ /**
+ * Take ownership of the ClientSource representing an initial about:blank
+ * document that was never needed. As an optimization we avoid creating
+ * this document if no code calls GetDocument(), but we still need a
+ * ClientSource object to represent the about:blank window. This may return
+ * nullptr; for example if the docshell has created a real window and document
+ * already.
+ */
+ [noscript, nostdcall, notxpcom]
+ UniqueClientSource TakeInitialClientSource();
+
+ void setColorMatrix(in Array<float> aMatrix);
+
+ /**
+ * Returns true if the current load is a forced reload,
+ * e.g. started by holding shift whilst triggering reload.
+ */
+ readonly attribute bool isForceReloading;
+
+ Array<float> getColorMatrix();
+
+%{C++
+ /**
+ * These methods call nsDocShell::GetHTMLEditorInternal() and
+ * nsDocShell::SetHTMLEditorInternal() with static_cast.
+ */
+ mozilla::HTMLEditor* GetHTMLEditor();
+ nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+%}
+
+ /**
+ * The message manager for this docshell. This does not throw, but
+ * can return null if the docshell has no message manager.
+ */
+ [infallible] readonly attribute ContentFrameMessageManager messageManager;
+
+ /**
+ * This returns a Promise which resolves to a boolean. True when the
+ * document has Tracking Content that has been blocked from loading, false
+ * otherwise.
+ */
+ Promise getHasTrackingContentBlocked();
+
+ /**
+ * Return whether this docshell is "attempting to navigate" in the
+ * sense that's relevant to document.open.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isAttemptingToNavigate;
+
+ /*
+ * Whether or not this docshell is executing a nsIWebNavigation navigation
+ * method.
+ *
+ * This will be true when the following methods are executing:
+ * nsIWebNavigation.binaryLoadURI
+ * nsIWebNavigation.goBack
+ * nsIWebNavigation.goForward
+ * nsIWebNavigation.gotoIndex
+ * nsIWebNavigation.loadURI
+ */
+ [infallible] readonly attribute boolean isNavigating;
+
+ /**
+ * @see nsISHEntry synchronizeLayoutHistoryState().
+ */
+ void synchronizeLayoutHistoryState();
+
+ /**
+ * This attempts to save any applicable layout history state (like
+ * scroll position) in the nsISHEntry. This is normally done
+ * automatically when transitioning from page to page in the
+ * same process. We expose this function to support transitioning
+ * from page to page across processes as a workaround for bug 1630234
+ * until session history state is moved into the parent process.
+ */
+ void persistLayoutHistoryState();
+};
diff --git a/docshell/base/nsIDocShellTreeItem.idl b/docshell/base/nsIDocShellTreeItem.idl
new file mode 100644
index 0000000000..ea34cef74b
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeItem.idl
@@ -0,0 +1,171 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDocShellTreeOwner;
+interface nsPIDOMWindowOuter;
+
+webidl Document;
+webidl BrowsingContext;
+
+/**
+ * The nsIDocShellTreeItem supplies the methods that are required of any item
+ * that wishes to be able to live within the docshell tree either as a middle
+ * node or a leaf.
+ */
+
+[scriptable, builtinclass, uuid(9b7c586f-9214-480c-a2c4-49b526fff1a6)]
+interface nsIDocShellTreeItem : nsISupports
+{
+ /*
+ name of the DocShellTreeItem
+ */
+ attribute AString name;
+
+ /**
+ * Compares the provided name against the item's name and
+ * returns the appropriate result.
+ *
+ * @return <CODE>PR_TRUE</CODE> if names match;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean nameEquals(in AString name);
+
+ /*
+ Definitions for the item types.
+ */
+ const long typeChrome=0; // typeChrome must equal 0
+ const long typeContent=1; // typeContent must equal 1
+ const long typeContentWrapper=2; // typeContentWrapper must equal 2
+ const long typeChromeWrapper=3; // typeChromeWrapper must equal 3
+
+ const long typeAll=0x7FFFFFFF;
+
+ /*
+ The type this item is.
+ */
+ readonly attribute long itemType;
+ [noscript,notxpcom,nostdcall] long ItemType();
+
+ /*
+ Parent DocShell.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ (NOTE: `BrowsingContext::GetParent()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessParent)]
+ readonly attribute nsIDocShellTreeItem parent;
+
+ /*
+ This getter returns the same thing parent does however if the parent
+ is of a different itemType, or if the parent is an <iframe mozbrowser>.
+ It will instead return nullptr. This call is a convience function for
+ Ithose wishing to not cross the boundaries at which item types change.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ */
+ [binaryname(InProcessSameTypeParent)]
+ readonly attribute nsIDocShellTreeItem sameTypeParent;
+
+ /*
+ Returns the root DocShellTreeItem. This is a convience equivalent to
+ getting the parent and its parent until there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ (NOTE: `BrowsingContext::Top()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem rootTreeItem;
+
+ /*
+ Returns the root DocShellTreeItem of the same type. This is a convience
+ equivalent to getting the parent of the same type and its parent until
+ there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ */
+ [binaryname(InProcessSameTypeRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem sameTypeRootTreeItem;
+
+ /*
+ The owner of the DocShell Tree. This interface will be called upon when
+ the docshell has things it needs to tell to the owner of the docshell.
+ Note that docShell tree ownership does not cross tree types. Meaning
+ setting ownership on a chrome tree does not set ownership on the content
+ sub-trees. A given tree's boundaries are identified by the type changes.
+ Trees of different types may be connected, but should not be traversed
+ for things such as ownership.
+
+ Note implementers of this interface should NOT effect the lifetime of the
+ parent DocShell by holding this reference as it creates a cycle. Owners
+ when releasing this interface should set the treeOwner to nullptr.
+ Implementers of this interface are guaranteed that when treeOwner is
+ set that the poitner is valid without having to addref.
+
+ Further note however when others try to get the interface it should be
+ addref'd before handing it to them.
+ */
+ readonly attribute nsIDocShellTreeOwner treeOwner;
+ [noscript] void setTreeOwner(in nsIDocShellTreeOwner treeOwner);
+
+ /*
+ The current number of DocShells which are immediate children of the
+ this object.
+
+
+ @deprecated: Prefer using `BrowsingContext::Children()`, as this count will
+ not include out-of-process iframes.
+ */
+ [binaryname(InProcessChildCount), infallible]
+ readonly attribute long childCount;
+
+ /*
+ Add a new child DocShellTreeItem. Adds to the end of the list.
+ Note that this does NOT take a reference to the child. The child stays
+ alive only as long as it's referenced from outside the docshell tree.
+
+ @throws NS_ERROR_ILLEGAL_VALUE if child corresponds to the same
+ object as this treenode or an ancestor of this treenode
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void addChild(in nsIDocShellTreeItem child);
+
+ /*
+ Removes a child DocShellTreeItem.
+
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void removeChild(in nsIDocShellTreeItem child);
+
+ /**
+ * Return the child at the index requested. This is 0-based.
+ *
+ * @deprecated: Prefer using `BrowsingContext::Children()`, as this will not
+ * include out-of-process iframes.
+ *
+ * @throws NS_ERROR_UNEXPECTED if the index is out of range
+ */
+ [binaryname(GetInProcessChildAt)]
+ nsIDocShellTreeItem getChildAt(in long index);
+
+ /**
+ * BrowsingContext associated with the DocShell.
+ */
+ [binaryname(BrowsingContextXPCOM)]
+ readonly attribute BrowsingContext browsingContext;
+
+ [noscript,notxpcom,nostdcall] BrowsingContext getBrowsingContext();
+
+ /**
+ * Returns the DOM outer window for the content viewer.
+ */
+ readonly attribute mozIDOMWindowProxy domWindow;
+
+ [noscript,nostdcall,notxpcom] Document getDocument();
+ [noscript,nostdcall,notxpcom] nsPIDOMWindowOuter getWindow();
+};
diff --git a/docshell/base/nsIDocShellTreeOwner.idl b/docshell/base/nsIDocShellTreeOwner.idl
new file mode 100644
index 0000000000..7e8aed5910
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeOwner.idl
@@ -0,0 +1,106 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIDocShellTreeOwner
+ */
+
+interface nsIDocShellTreeItem;
+interface nsIRemoteTab;
+
+[scriptable, uuid(0e3dc4b1-4cea-4a37-af71-79f0afd07574)]
+interface nsIDocShellTreeOwner : nsISupports
+{
+ /**
+ * Called when a content shell is added to the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell).
+ *
+ * @param aContentShell the shell being added.
+ * @param aPrimary whether the shell is primary.
+ */
+ void contentShellAdded(in nsIDocShellTreeItem aContentShell,
+ in boolean aPrimary);
+
+ /**
+ * Called when a content shell is removed from the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell). Note that if aContentShell was never added,
+ * contentShellRemoved should just do nothing.
+ *
+ * @param aContentShell the shell being removed.
+ */
+ void contentShellRemoved(in nsIDocShellTreeItem aContentShell);
+
+ /*
+ Returns the Primary Content Shell
+ */
+ readonly attribute nsIDocShellTreeItem primaryContentShell;
+
+ void remoteTabAdded(in nsIRemoteTab aTab, in boolean aPrimary);
+ void remoteTabRemoved(in nsIRemoteTab aTab);
+
+ /*
+ In multiprocess case we may not have primaryContentShell but
+ primaryRemoteTab.
+ */
+ readonly attribute nsIRemoteTab primaryRemoteTab;
+
+ /*
+ Tells the tree owner to size its window or parent window in such a way
+ that the shell passed along will be the size specified.
+ */
+ [can_run_script]
+ void sizeShellTo(in nsIDocShellTreeItem shell, in long cx, in long cy);
+
+ /*
+ Gets the size of the primary content area in CSS pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void getPrimaryContentSize(out long width, out long height);
+ /*
+ Sets the size of the primary content area in CSS pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void setPrimaryContentSize(in long width, in long height);
+
+ /*
+ Gets the size of the root docshell in CSS pixels.
+ */
+ void getRootShellSize(out long width, out long height);
+ /*
+ Sets the size of the root docshell in CSS pixels.
+ */
+ void setRootShellSize(in long width, in long height);
+
+ /*
+ Sets the persistence of different attributes of the window.
+ */
+ void setPersistence(in boolean aPersistPosition,
+ in boolean aPersistSize,
+ in boolean aPersistSizeMode);
+
+ /*
+ Gets the current persistence states of the window.
+ */
+ void getPersistence(out boolean aPersistPosition,
+ out boolean aPersistSize,
+ out boolean aPersistSizeMode);
+
+ /*
+ Gets the number of tabs currently open in our window, assuming
+ this tree owner has such a concept.
+ */
+ readonly attribute unsigned long tabCount;
+
+ /*
+ Returns true if there is a primary content shell or a primary
+ remote tab.
+ */
+ readonly attribute bool hasPrimaryContent;
+};
diff --git a/docshell/base/nsIDocumentLoaderFactory.idl b/docshell/base/nsIDocumentLoaderFactory.idl
new file mode 100644
index 0000000000..cd08bc0f3f
--- /dev/null
+++ b/docshell/base/nsIDocumentLoaderFactory.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIContentViewer;
+interface nsIStreamListener;
+interface nsIDocShell;
+interface nsILoadGroup;
+interface nsIPrincipal;
+
+webidl Document;
+
+/**
+ * To get a component that implements nsIDocumentLoaderFactory
+ * for a given mimetype, use nsICategoryManager to find an entry
+ * with the mimetype as its name in the category "Gecko-Content-Viewers".
+ * The value of the entry is the contractid of the component.
+ * The component is a service, so use GetService, not CreateInstance to get it.
+ */
+
+[scriptable, uuid(e795239e-9d3c-47c4-b063-9e600fb3b287)]
+interface nsIDocumentLoaderFactory : nsISupports {
+ nsIContentViewer createInstance(in string aCommand,
+ in nsIChannel aChannel,
+ in nsILoadGroup aLoadGroup,
+ in ACString aContentType,
+ in nsIDocShell aContainer,
+ in nsISupports aExtraInfo,
+ out nsIStreamListener aDocListenerResult);
+
+ nsIContentViewer createInstanceForDocument(in nsISupports aContainer,
+ in Document aDocument,
+ in string aCommand);
+};
diff --git a/docshell/base/nsILoadContext.idl b/docshell/base/nsILoadContext.idl
new file mode 100644
index 0000000000..af71b96b34
--- /dev/null
+++ b/docshell/base/nsILoadContext.idl
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+webidl Element;
+
+[ref] native OriginAttributes(mozilla::OriginAttributes);
+
+%{C++
+#ifdef MOZILLA_INTERNAL_API
+namespace mozilla {
+class OriginAttributes;
+}
+#endif
+%}
+
+/**
+ * An nsILoadContext represents the context of a load. This interface
+ * can be queried for various information about where the load is
+ * happening.
+ */
+[builtinclass, scriptable, uuid(2813a7a3-d084-4d00-acd0-f76620315c02)]
+interface nsILoadContext : nsISupports
+{
+ /**
+ * associatedWindow is the window with which the load is associated, if any.
+ * Note that the load may be triggered by a document which is different from
+ * the document in associatedWindow, and in fact the source of the load need
+ * not be same-origin with the document in associatedWindow. This attribute
+ * may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy associatedWindow;
+
+ /**
+ * topWindow is the top window which is of same type as associatedWindow.
+ * This is equivalent to associatedWindow.top, but is provided here as a
+ * convenience. All the same caveats as associatedWindow of apply, of
+ * course. This attribute may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy topWindow;
+
+ /**
+ * topFrameElement is the <iframe>, <frame>, or <browser> element which
+ * contains the topWindow with which the load is associated.
+ *
+ * Note that we may have a topFrameElement even when we don't have an
+ * associatedWindow, if the topFrameElement's content lives out of process.
+ * topFrameElement is available in single-process and multiprocess contexts.
+ * Note that topFrameElement may be in chrome even when the nsILoadContext is
+ * associated with content.
+ */
+ readonly attribute Element topFrameElement;
+
+ /**
+ * True if the load context is content (as opposed to chrome). This is
+ * determined based on the type of window the load is performed in, NOT based
+ * on any URIs that might be around.
+ */
+ readonly attribute boolean isContent;
+
+ /*
+ * Attribute that determines if private browsing should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean usePrivateBrowsing;
+
+ /**
+ * Attribute that determines if remote (out-of-process) tabs should be used.
+ */
+ readonly attribute boolean useRemoteTabs;
+
+ /**
+ * Determines if out-of-process iframes should be used.
+ */
+ readonly attribute boolean useRemoteSubframes;
+
+ /*
+ * Attribute that determines if tracking protection should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean useTrackingProtection;
+
+%{C++
+ /**
+ * De-XPCOMed getter to make call-sites cleaner.
+ */
+ bool UsePrivateBrowsing()
+ {
+ bool usingPB = false;
+ GetUsePrivateBrowsing(&usingPB);
+ return usingPB;
+ }
+
+ bool UseRemoteTabs()
+ {
+ bool usingRT = false;
+ GetUseRemoteTabs(&usingRT);
+ return usingRT;
+ }
+
+ bool UseRemoteSubframes()
+ {
+ bool usingRSF = false;
+ GetUseRemoteSubframes(&usingRSF);
+ return usingRSF;
+ }
+
+ bool UseTrackingProtection()
+ {
+ bool usingTP = false;
+ GetUseTrackingProtection(&usingTP);
+ return usingTP;
+ }
+%}
+
+ /**
+ * Set the private browsing state of the load context, meant to be used internally.
+ */
+ [noscript] void SetPrivateBrowsing(in boolean aInPrivateBrowsing);
+
+ /**
+ * Set the remote tabs state of the load context, meant to be used internally.
+ */
+ [noscript] void SetRemoteTabs(in boolean aUseRemoteTabs);
+
+ /**
+ * Set the remote subframes bit of this load context. Exclusively meant to be used internally.
+ */
+ [noscript] void SetRemoteSubframes(in boolean aUseRemoteSubframes);
+
+ /**
+ * A dictionary of the non-default origin attributes associated with this
+ * nsILoadContext.
+ */
+ [binaryname(ScriptableOriginAttributes), implicit_jscontext]
+ readonly attribute jsval originAttributes;
+
+ /**
+ * The C++ getter for origin attributes.
+ */
+ [noscript, notxpcom] void GetOriginAttributes(out OriginAttributes aAttrs);
+};
diff --git a/docshell/base/nsILoadURIDelegate.idl b/docshell/base/nsILoadURIDelegate.idl
new file mode 100644
index 0000000000..512535cee5
--- /dev/null
+++ b/docshell/base/nsILoadURIDelegate.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrincipal;
+
+/**
+ * The nsILoadURIDelegate interface.
+ * Used for delegating URI loads to GeckoView's application, e.g., Custom Tabs
+ * or Progressive Web Apps.
+ */
+[scriptable, uuid(78e42d37-a34c-4d96-b901-25385669aba4)]
+interface nsILoadURIDelegate : nsISupports
+{
+ /**
+ * Delegates the URI load. This should only be called for top-level loads.
+ *
+ * @param aURI The URI to load.
+ * @param aWhere See possible values described in nsIBrowserDOMWindow.
+ * @param aFlags Flags which control the behavior of the load.
+ * @param aTriggeringPrincipal The principal that triggered the load of aURI.
+ *
+ * Returns whether the load has been successfully handled.
+ */
+ boolean
+ loadURI(in nsIURI aURI, in short aWhere, in long aFlags,
+ in nsIPrincipal aTriggeringPrincipal);
+
+ /**
+ * Delegates page load error handling. This may be called for either top-level
+ * loads or subframes.
+ *
+ * @param aURI The URI that failed to load.
+ * @param aError The error code.
+ * @param aErrorModule The error module code.
+
+ * Returns an error page URL to load, or null to show the default error page.
+ * No error page is shown at all if an error is thrown.
+ */
+ nsIURI
+ handleLoadError(in nsIURI aURI, in nsresult aError, in short aErrorModule);
+};
diff --git a/docshell/base/nsIPrivacyTransitionObserver.idl b/docshell/base/nsIPrivacyTransitionObserver.idl
new file mode 100644
index 0000000000..c85d468d33
--- /dev/null
+++ b/docshell/base/nsIPrivacyTransitionObserver.idl
@@ -0,0 +1,11 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, function, uuid(b4b1449d-0ef0-47f5-b62e-adc57fd49702)]
+interface nsIPrivacyTransitionObserver : nsISupports
+{
+ void privateModeChanged(in bool enabled);
+};
diff --git a/docshell/base/nsIReflowObserver.idl b/docshell/base/nsIReflowObserver.idl
new file mode 100644
index 0000000000..fb602e2603
--- /dev/null
+++ b/docshell/base/nsIReflowObserver.idl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+[scriptable, uuid(832e692c-c4a6-11e2-8fd1-dce678957a39)]
+interface nsIReflowObserver : nsISupports
+{
+ /**
+ * Called when an uninterruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflow(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Called when an interruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflowInterruptible(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+};
diff --git a/docshell/base/nsIRefreshURI.idl b/docshell/base/nsIRefreshURI.idl
new file mode 100644
index 0000000000..c9a76b1fc2
--- /dev/null
+++ b/docshell/base/nsIRefreshURI.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(a5e61a3c-51bd-45be-ac0c-e87b71860656)]
+interface nsIRefreshURI : nsISupports {
+ /**
+ * Load a uri after waiting for aMillis milliseconds. If the docshell
+ * is busy loading a page currently, the refresh request will be
+ * queued and executed when the current load finishes.
+ *
+ * @param aUri The uri to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds to wait.
+ * @param aRepeat Flag to indicate if the uri is to be
+ * repeatedly refreshed every aMillis milliseconds.
+ * @param aMetaRefresh Flag to indicate if this is a Meta refresh.
+ */
+ void refreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in long aMillis, in boolean aRepeat,
+ in boolean aMetaRefresh);
+
+ /**
+ * Loads a URI immediately as if it were a refresh.
+ *
+ * @param aURI The URI to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds by which this refresh would
+ * be delayed if it were not being forced.
+ * @param aMetaRefresh Flag to indicate if this is a meta refresh.
+ */
+ void forceRefreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in long aMillis, in boolean aMetaRefresh);
+
+ /**
+ * Checks the passed in channel to see if there is a refresh header,
+ * if there is, will setup a timer to refresh the uri found
+ * in the header. If docshell is busy loading a page currently, the
+ * request will be queued and executed when the current page
+ * finishes loading.
+ *
+ * Returns the NS_REFRESHURI_HEADER_FOUND success code if a refresh
+ * header was found and successfully setup.
+ *
+ * @param aChannel The channel to be parsed.
+ */
+ void setupRefreshURI(in nsIChannel aChannel);
+
+ /**
+ * Parses the passed in header string and sets up a refreshURI if
+ * a "refresh" header is found. If docshell is busy loading a page
+ * currently, the request will be queued and executed when
+ * the current page finishes loading.
+ *
+ * @param aBaseURI base URI to resolve refresh uri with.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aInnerWindowID The window id to use for error reporting.
+ * @param aHeader The meta refresh header string.
+ */
+ void setupRefreshURIFromHeader(in nsIURI aBaseURI,
+ in nsIPrincipal principal,
+ in unsigned long long aInnerWindowID,
+ in ACString aHeader);
+
+ /**
+ * Cancels all timer loads.
+ */
+ void cancelRefreshURITimers();
+
+ /**
+ * True when there are pending refreshes, false otherwise.
+ */
+ readonly attribute boolean refreshPending;
+};
diff --git a/docshell/base/nsIScrollObserver.h b/docshell/base/nsIScrollObserver.h
new file mode 100644
index 0000000000..9ff89002f0
--- /dev/null
+++ b/docshell/base/nsIScrollObserver.h
@@ -0,0 +1,45 @@
+/* -*- 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 nsIScrollObserver_h___
+#define nsIScrollObserver_h___
+
+#include "nsISupports.h"
+#include "Units.h"
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCROLLOBSERVER_IID \
+ { \
+ 0xaa5026eb, 0x2f88, 0x4026, { \
+ 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 \
+ } \
+ }
+
+class nsIScrollObserver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
+
+ /**
+ * Called when the scroll position of some element has changed.
+ */
+ virtual void ScrollPositionChanged() = 0;
+
+ /**
+ * Called when an async panning/zooming transform has started being applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStarted(){};
+
+ /**
+ * Called when an async panning/zooming transform is no longer applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStopped(){};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
+
+#endif /* nsIScrollObserver_h___ */
diff --git a/docshell/base/nsITooltipListener.idl b/docshell/base/nsITooltipListener.idl
new file mode 100644
index 0000000000..802478481f
--- /dev/null
+++ b/docshell/base/nsITooltipListener.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An optional interface for embedding clients wishing to receive
+ * notifications for when a tooltip should be displayed or removed.
+ * The embedder implements this interface on the web browser chrome
+ * object associated with the window that notifications are required
+ * for.
+ *
+ * @see nsITooltipTextProvider
+ */
+[scriptable, uuid(44b78386-1dd2-11b2-9ad2-e4eee2ca1916)]
+interface nsITooltipListener : nsISupports
+{
+ /**
+ * Called when a tooltip should be displayed.
+ *
+ * @param aXCoords The tooltip left edge X coordinate.
+ * @param aYCoords The tooltip top edge Y coordinate.
+ * @param aTipText The text to display in the tooltip, typically obtained
+ * from the TITLE attribute of the node (or containing parent)
+ * over which the pointer has been positioned.
+ * @param aTipDir The direction (ltr or rtl) in which to display the text
+ *
+ * @note
+ * Coordinates are specified in pixels, relative to the top-left
+ * corner of the browser area.
+ *
+ * @return <code>NS_OK</code> if the tooltip was displayed.
+ */
+ void onShowTooltip(in long aXCoords, in long aYCoords, in AString aTipText,
+ in AString aTipDir);
+
+ /**
+ * Called when the tooltip should be hidden, either because the pointer
+ * has moved or the tooltip has timed out.
+ */
+ void onHideTooltip();
+};
diff --git a/docshell/base/nsITooltipTextProvider.idl b/docshell/base/nsITooltipTextProvider.idl
new file mode 100644
index 0000000000..3afaddbe2a
--- /dev/null
+++ b/docshell/base/nsITooltipTextProvider.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl Node;
+
+/**
+ * An interface implemented by a tooltip text provider service. This
+ * service is called to discover what tooltip text is associated
+ * with the node that the pointer is positioned over.
+ *
+ * Embedders may implement and register their own tooltip text provider
+ * service if they wish to provide different tooltip text.
+ *
+ * The default service returns the text stored in the TITLE
+ * attribute of the node or a containing parent.
+ *
+ * @note
+ * The tooltip text provider service is registered with the contract
+ * defined in NS_TOOLTIPTEXTPROVIDER_CONTRACTID.
+ *
+ * @see nsITooltipListener
+ * @see nsIComponentManager
+ * @see Node
+ */
+[scriptable, uuid(b128a1e6-44f3-4331-8fbe-5af360ff21ee)]
+interface nsITooltipTextProvider : nsISupports
+{
+ /**
+ * Called to obtain the tooltip text for a node.
+ *
+ * @arg aNode The node to obtain the text from.
+ * @arg aText The tooltip text.
+ * @arg aDirection The text direction (ltr or rtl) to use
+ *
+ * @return <CODE>PR_TRUE</CODE> if tooltip text is associated
+ * with the node and was returned in the aText argument;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean getNodeText(in Node aNode, out wstring aText, out wstring aDirection);
+};
diff --git a/docshell/base/nsIURIFixup.idl b/docshell/base/nsIURIFixup.idl
new file mode 100644
index 0000000000..b6d46315e9
--- /dev/null
+++ b/docshell/base/nsIURIFixup.idl
@@ -0,0 +1,156 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+webidl BrowsingContext;
+
+/**
+ * Interface indicating what we found/corrected when fixing up a URI
+ */
+[scriptable, uuid(4819f183-b532-4932-ac09-b309cd853be7)]
+interface nsIURIFixupInfo : nsISupports
+{
+ /**
+ * Consumer that asked for fixed up URI.
+ */
+ attribute BrowsingContext consumer;
+
+ /**
+ * Our best guess as to what URI the consumer will want. Might
+ * be null if we couldn't salvage anything (for instance, because
+ * the input was invalid as a URI and FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ * was not passed)
+ */
+ attribute nsIURI preferredURI;
+
+ /**
+ * The fixed-up original input, *never* using a keyword search.
+ * (might be null if the original input was not recoverable as
+ * a URL, e.g. "foo bar"!)
+ */
+ attribute nsIURI fixedURI;
+
+ /**
+ * The name of the keyword search provider used to provide a keyword search;
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordProviderName;
+
+ /**
+ * The keyword as used for the search (post trimming etc.)
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordAsSent;
+
+ /**
+ * Whether we changed the protocol instead of using one from the input as-is.
+ */
+ attribute boolean fixupChangedProtocol;
+
+ /**
+ * Whether we created an alternative URI. We might have added a prefix and/or
+ * suffix, the contents of which are controlled by the
+ * browser.fixup.alternate.prefix and .suffix prefs, with the defaults being
+ * "www." and ".com", respectively.
+ */
+ attribute boolean fixupCreatedAlternateURI;
+
+ /**
+ * The original input
+ */
+ attribute AUTF8String originalInput;
+
+ /**
+ * The POST data to submit with the returned URI (see nsISearchSubmission).
+ */
+ attribute nsIInputStream postData;
+};
+
+
+/**
+ * Interface implemented by objects capable of fixing up strings into URIs
+ */
+[scriptable, uuid(1da7e9d4-620b-4949-849a-1cd6077b1b2d)]
+interface nsIURIFixup : nsISupports
+{
+ /** No fixup flags. */
+ const unsigned long FIXUP_FLAG_NONE = 0;
+
+ /**
+ * Allow the fixup to use a keyword lookup service to complete the URI.
+ * The fixup object implementer should honour this flag and only perform
+ * any lengthy keyword (or search) operation if it is set.
+ */
+ const unsigned long FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP = 1;
+
+ /**
+ * Tell the fixup to make an alternate URI from the input URI, for example
+ * to turn foo into www.foo.com.
+ */
+ const unsigned long FIXUP_FLAGS_MAKE_ALTERNATE_URI = 2;
+
+ /*
+ * Set when the fixup happens in a private context, the used search engine
+ * may differ in this case. Not all consumers care about this, because they
+ * may not want the url to be transformed in a search.
+ */
+ const unsigned long FIXUP_FLAG_PRIVATE_CONTEXT = 4;
+
+ /*
+ * Fix common scheme typos.
+ */
+ const unsigned long FIXUP_FLAG_FIX_SCHEME_TYPOS = 8;
+
+ /**
+ * Tries to converts the specified string into a URI, first attempting
+ * to correct any errors in the syntax or other vagaries.
+ * It returns information about what it corrected
+ * (e.g. whether we could rescue the URI or "just" generated a keyword
+ * search URI instead).
+ *
+ * @param aURIText Candidate URI.
+ * @param aFixupFlags Flags that govern ways the URI may be fixed up.
+ * Defaults to FIXUP_FLAG_NONE.
+ */
+ nsIURIFixupInfo getFixupURIInfo(in AUTF8String aURIText,
+ [optional] in unsigned long aFixupFlags);
+
+ /**
+ * Convert load flags from nsIWebNavigation to URI fixup flags for use in
+ * getFixupURIInfo.
+ *
+ * @param aURIText Candidate URI; used for determining whether to
+ * allow keyword lookups.
+ * @param aDocShellFlags Load flags from nsIDocShell to convert.
+ */
+ unsigned long webNavigationFlagsToFixupFlags(
+ in AUTF8String aURIText, in unsigned long aDocShellFlags);
+
+ /**
+ * Converts the specified keyword string into a URI. Note that it's the
+ * caller's responsibility to check whether keywords are enabled and
+ * whether aKeyword is a sensible keyword.
+ *
+ * @param aKeyword The keyword string to convert into a URI
+ * @param aIsPrivateContext Whether this is invoked from a private context.
+ */
+ nsIURIFixupInfo keywordToURI(in AUTF8String aKeyword,
+ [optional] in boolean aIsPrivateContext);
+
+ /**
+ * Returns true if the specified domain is known and false otherwise.
+ * A known domain is relevant when we have a single word and can't be
+ * sure whether to treat the word as a host name or should instead be
+ * treated as a search term.
+ *
+ * @param aDomain A domain name to query.
+ */
+ bool isDomainKnown(in AUTF8String aDomain);
+};
+
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
new file mode 100644
index 0000000000..30b6dd276c
--- /dev/null
+++ b/docshell/base/nsIWebNavigation.idl
@@ -0,0 +1,375 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsISHistory;
+interface nsIURI;
+interface nsIPrincipal;
+interface nsIChildSHistory;
+webidl Document;
+
+%{ C++
+#include "mozilla/dom/ChildSHistory.h"
+namespace mozilla {
+namespace dom {
+struct LoadURIOptions;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native LoadURIOptionsRef(const mozilla::dom::LoadURIOptions);
+
+/**
+ * The nsIWebNavigation interface defines an interface for navigating the web.
+ * It provides methods and attributes to direct an object to navigate to a new
+ * location, stop or restart an in process load, or determine where the object
+ * has previously gone.
+ *
+ * Even though this is builtinclass, most of the interface is also implemented
+ * in RemoteWebNavigation, so if this interface changes, the implementation
+ * there may also need to change.
+ */
+[scriptable, builtinclass, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)]
+interface nsIWebNavigation : nsISupports
+{
+ /**
+ * Indicates if the object can go back. If true this indicates that
+ * there is back session history available for navigation.
+ */
+ readonly attribute boolean canGoBack;
+
+ /**
+ * Indicates if the object can go forward. If true this indicates that
+ * there is forward session history available for navigation
+ */
+ readonly attribute boolean canGoForward;
+
+ /**
+ * Tells the object to navigate to the previous session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goBack to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the first session history entry.
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoBack is false.
+ */
+ void goBack([optional] in boolean aRequireUserInteraction);
+
+ /**
+ * Tells the object to navigate to the next session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goForward to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the latest session history entry.
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoForward is false.
+ */
+ void goForward([optional] in boolean aRequireUserInteraction);
+
+ /**
+ * Tells the object to navigate to the session history item at a given index.
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that session history entry at the given index does not exist.
+ */
+ void gotoIndex(in long index);
+
+ /****************************************************************************
+ * The following flags may be bitwise combined to form the load flags
+ * parameter passed to either the loadURI or reload method. Some of these
+ * flags are only applicable to loadURI.
+ */
+
+ /**
+ * This flags defines the range of bits that may be specified. Flags
+ * outside this range may be used, but may not be passed to Reload().
+ */
+ const unsigned long LOAD_FLAGS_MASK = 0xffff;
+
+ /**
+ * This is the default value for the load flags parameter.
+ */
+ const unsigned long LOAD_FLAGS_NONE = 0x0000;
+
+ /**
+ * Flags 0x1, 0x2, 0x4, 0x8 are reserved for internal use by
+ * nsIWebNavigation implementations for now.
+ */
+
+ /**
+ * This flag specifies that the load should have the semantics of an HTML
+ * Meta-refresh tag (i.e., that the cache should be bypassed). This flag
+ * is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ * XXX no one uses this, so we should probably deprecate and remove it.
+ */
+ const unsigned long LOAD_FLAGS_IS_REFRESH = 0x0010;
+
+ /**
+ * This flag specifies that the load should have the semantics of a link
+ * click. This flag is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ */
+ const unsigned long LOAD_FLAGS_IS_LINK = 0x0020;
+
+ /**
+ * This flag specifies that history should not be updated. This flag is only
+ * applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_HISTORY = 0x0040;
+
+ /**
+ * This flag specifies that any existing history entry should be replaced.
+ * This flag is only applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_REPLACE_HISTORY = 0x0080;
+
+ /**
+ * This flag specifies that the local web cache should be bypassed, but an
+ * intermediate proxy cache could still be used to satisfy the load.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CACHE = 0x0100;
+
+ /**
+ * This flag specifies that any intermediate proxy caches should be bypassed
+ * (i.e., that the content should be loaded from the origin server).
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_PROXY = 0x0200;
+
+ /**
+ * This flag specifies that a reload was triggered as a result of detecting
+ * an incorrect character encoding while parsing a previously loaded
+ * document.
+ */
+ const unsigned long LOAD_FLAGS_CHARSET_CHANGE = 0x0400;
+
+ /**
+ * If this flag is set, Stop() will be called before the load starts
+ * and will stop both content and network activity (the default is to
+ * only stop network activity). Effectively, this passes the
+ * STOP_CONTENT flag to Stop(), in addition to the STOP_NETWORK flag.
+ */
+ const unsigned long LOAD_FLAGS_STOP_CONTENT = 0x0800;
+
+ /**
+ * A hint this load was prompted by an external program: take care!
+ */
+ const unsigned long LOAD_FLAGS_FROM_EXTERNAL = 0x1000;
+
+ /**
+ This flag is set when a user explicitly disables the Mixed Content
+ Blocker, and allows Mixed Content to load on an https page.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_MIXED_CONTENT = 0x2000;
+
+ /**
+ * This flag specifies that this is the first load in this object.
+ * Set with care, since setting incorrectly can cause us to assume that
+ * nothing was actually loaded in this object if the load ends up being
+ * handled by an external application. This flag must not be passed to
+ * Reload.
+ */
+ const unsigned long LOAD_FLAGS_FIRST_LOAD = 0x4000;
+
+ /**
+ * This flag specifies that the load should not be subject to popup
+ * blocking checks. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_POPUPS = 0x8000;
+
+ /**
+ * This flag specifies that the URI classifier should not be checked for
+ * this load. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10000;
+
+ /**
+ * Force relevant cookies to be sent with this load even if normally they
+ * wouldn't be.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20000;
+
+ /**
+ * Prevent the owner principal from being inherited for this load.
+ */
+ const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL = 0x40000;
+
+ /**
+ * Overwrite the returned error code with a specific result code
+ * when an error page is displayed.
+ */
+ const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000;
+
+ /**
+ * This flag specifies that the URI may be submitted to a third-party
+ * server for correction. This should only be applied to non-sensitive
+ * URIs entered by users. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x100000;
+
+ /**
+ * This flag specifies that common scheme typos should be corrected.
+ */
+ const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000;
+
+ /**
+ * Allows a top-level data: navigation to occur. E.g. view-image
+ * is an explicit user action which should be allowed.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x400000;
+
+ /**
+ * This load is the result of an HTTP redirect.
+ */
+ const unsigned long LOAD_FLAGS_IS_REDIRECT = 0x800000;
+
+ /**
+ * These flags force TRR modes 1 or 3 for the load.
+ */
+ const unsigned long LOAD_FLAGS_DISABLE_TRR = 0x1000000;
+ const unsigned long LOAD_FLAGS_FORCE_TRR = 0x2000000;
+
+ /**
+ * This load should bypass the LoadURIDelegate.loadUri.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x4000000;
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URI dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aURI
+ * The URI string to load. For HTTP and FTP URLs and possibly others,
+ * characters above U+007F will be converted to UTF-8 and then URL-
+ * escaped per the rules of RFC 2396.
+ * @param aLoadURIOptions
+ * A JSObject defined in LoadURIOptions.webidl holding info like e.g.
+ * the triggeringPrincipal, the referrer info.
+ */
+ [implicit_jscontext, binaryname(LoadURIFromScript)]
+ void loadURI(in AString aURI,
+ in jsval aLoadURIOptions);
+
+ /**
+ * A C++ friendly version of loadURI
+ */
+ [nostdcall, binaryname(LoadURI)]
+ void binaryLoadURI(in AString aURI,
+ in LoadURIOptionsRef aLoadURIOptions);
+
+ /**
+ * Tells the Object to reload the current page. There may be cases where the
+ * user will be asked to confirm the reload (for example, when it is
+ * determined that the request is non-idempotent).
+ *
+ * @param aReloadFlags
+ * Flags modifying load behaviour. This parameter is a bitwise
+ * combination of the Load Flags defined above. (Undefined bits are
+ * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE
+ * for this parameter.
+ *
+ * @throw NS_BINDING_ABORTED
+ * Indicating that the user canceled the reload.
+ */
+ void reload(in unsigned long aReloadFlags);
+
+ /****************************************************************************
+ * The following flags may be passed as the stop flags parameter to the stop
+ * method defined on this interface.
+ */
+
+ /**
+ * This flag specifies that all network activity should be stopped. This
+ * includes both active network loads and pending META-refreshes.
+ */
+ const unsigned long STOP_NETWORK = 0x01;
+
+ /**
+ * This flag specifies that all content activity should be stopped. This
+ * includes animated images, plugins and pending Javascript timeouts.
+ */
+ const unsigned long STOP_CONTENT = 0x02;
+
+ /**
+ * This flag specifies that all activity should be stopped.
+ */
+ const unsigned long STOP_ALL = 0x03;
+
+ /**
+ * Stops a load of a URI.
+ *
+ * @param aStopFlags
+ * This parameter is one of the stop flags defined above.
+ */
+ void stop(in unsigned long aStopFlags);
+
+ /**
+ * Retrieves the current DOM document for the frame, or lazily creates a
+ * blank document if there is none. This attribute never returns null except
+ * for unexpected error situations.
+ */
+ readonly attribute Document document;
+
+ /**
+ * The currently loaded URI or null.
+ */
+ readonly attribute nsIURI currentURI;
+
+ /**
+ * The session history object used by this web navigation instance. This
+ * object will be a mozilla::dom::ChildSHistory object, but is returned as
+ * nsISupports so it can be called from JS code.
+ */
+ [binaryname(SessionHistoryXPCOM)]
+ readonly attribute nsISupports sessionHistory;
+
+ %{ C++
+ /**
+ * Get the session history object used by this nsIWebNavigation instance.
+ * Use this method instead of the XPCOM method when getting the
+ * SessionHistory from C++ code.
+ */
+ already_AddRefed<mozilla::dom::ChildSHistory>
+ GetSessionHistory()
+ {
+ nsCOMPtr<nsISupports> history;
+ GetSessionHistoryXPCOM(getter_AddRefs(history));
+ return history.forget()
+ .downcast<mozilla::dom::ChildSHistory>();
+ }
+ %}
+
+ /**
+ * Resume a load which has been redirected from another process.
+ *
+ * A negative |aHistoryIndex| value corresponds to a non-history load being
+ * resumed.
+ */
+ void resumeRedirectedLoad(in unsigned long long aLoadIdentifier,
+ in long aHistoryIndex);
+};
diff --git a/docshell/base/nsIWebNavigationInfo.idl b/docshell/base/nsIWebNavigationInfo.idl
new file mode 100644
index 0000000000..ff93920723
--- /dev/null
+++ b/docshell/base/nsIWebNavigationInfo.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebNavigation;
+
+/**
+ * The nsIWebNavigationInfo interface exposes a way to get information
+ * on the capabilities of Gecko webnavigation objects.
+ */
+[scriptable, uuid(62a93afb-93a1-465c-84c8-0432264229de)]
+interface nsIWebNavigationInfo : nsISupports
+{
+ /**
+ * Returned by isTypeSupported to indicate lack of support for a type.
+ * @note this is guaranteed not to change, so that boolean tests can be done
+ * on the return value if isTypeSupported to detect whether a type is
+ * supported at all.
+ */
+ const unsigned long UNSUPPORTED = 0;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported as an
+ * image.
+ */
+ const unsigned long IMAGE = 1;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported via an
+ * NPAPI ("Netscape 4 API") plug-in. This is not the value returned for
+ * "XPCOM plug-ins".
+ */
+ const unsigned long PLUGIN = 2;
+
+ /**
+ * @note Other return types may be added here in the future as they become
+ * relevant.
+ */
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported via some
+ * other means.
+ */
+ const unsigned long OTHER = 1 << 15;
+
+ /**
+ * Query whether aType is supported.
+ * @param aType the MIME type in question.
+ * @param aWebNav the nsIWebNavigation object for which the request
+ * is being made. This is allowed to be null. If it is non-null,
+ * the return value of this method may depend on the exact state of
+ * aWebNav and the values set through nsIWebBrowserSetup; otherwise
+ * the method will assume that the caller is interested in information
+ * about nsIWebNavigation objects in their default state.
+ * @return an enum value indicating whether and how aType is supported.
+ * @note This method may rescan plugins to ensure that they're properly
+ * registered for the types they support.
+ */
+ unsigned long isTypeSupported(in ACString aType, in nsIWebNavigation aWebNav);
+};
diff --git a/docshell/base/nsIWebPageDescriptor.idl b/docshell/base/nsIWebPageDescriptor.idl
new file mode 100644
index 0000000000..866cb54e6b
--- /dev/null
+++ b/docshell/base/nsIWebPageDescriptor.idl
@@ -0,0 +1,30 @@
+/* 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 "nsISupports.idl"
+interface nsIDocShell;
+
+/**
+ * The nsIWebPageDescriptor interface allows content being displayed in one
+ * window to be loaded into another window without refetching it from the
+ * network.
+ */
+
+[scriptable, uuid(6f30b676-3710-4c2c-80b1-0395fb26516e)]
+interface nsIWebPageDescriptor : nsISupports
+{
+ /**
+ * Tells the object to load the page that otherDocShell is currently loading,
+ * or has loaded already, as view source, with the url being `aURL`.
+ *
+ * @throws NS_ERROR_FAILURE - NS_ERROR_INVALID_POINTER
+ */
+ void loadPageAsViewSource(in nsIDocShell otherDocShell, in AString aURL);
+
+
+ /**
+ * Retrieves the page descriptor for the curent document.
+ * @note, currentDescriptor is currently always an nsISHEntry object or null.
+ */
+ readonly attribute nsISupports currentDescriptor;
+};
diff --git a/docshell/base/nsPingListener.cpp b/docshell/base/nsPingListener.cpp
new file mode 100644
index 0000000000..23ac220489
--- /dev/null
+++ b/docshell/base/nsPingListener.cpp
@@ -0,0 +1,345 @@
+/* -*- 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 "nsPingListener.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIUploadChannel2.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsWhitespaceTokenizer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
+
+//*****************************************************************************
+// <a ping> support
+//*****************************************************************************
+
+#define PREF_PINGS_ENABLED "browser.send_pings"
+#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link"
+#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
+
+// Check prefs to see if pings are enabled and if so what restrictions might
+// be applied.
+//
+// @param maxPerLink
+// This parameter returns the number of pings that are allowed per link click
+//
+// @param requireSameHost
+// This parameter returns true if pings are restricted to the same host as
+// the document in which the click occurs. If the same host restriction is
+// imposed, then we still allow for pings to cross over to different
+// protocols and ports for flexibility and because it is not possible to send
+// a ping via FTP.
+//
+// @returns
+// true if pings are enabled and false otherwise.
+//
+static bool PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost) {
+ bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
+
+ *aMaxPerLink = 1;
+ *aRequireSameHost = true;
+
+ if (allow) {
+ Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
+ Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
+ }
+
+ return allow;
+}
+
+// We wait this many milliseconds before killing the ping channel...
+#define PING_TIMEOUT 10000
+
+static void OnPingTimeout(nsITimer* aTimer, void* aClosure) {
+ nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
+ if (loadGroup) {
+ loadGroup->Cancel(NS_ERROR_ABORT);
+ }
+}
+
+struct MOZ_STACK_CLASS SendPingInfo {
+ int32_t numPings;
+ int32_t maxPings;
+ bool requireSameHost;
+ nsIURI* target;
+ nsIReferrerInfo* referrerInfo;
+ nsIDocShell* docShell;
+};
+
+static void SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
+ nsIIOService* aIOService) {
+ SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
+ if (info->maxPings > -1 && info->numPings >= info->maxPings) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+
+ nsCOMPtr<nsIChannel> chan;
+ NS_NewChannel(getter_AddRefs(chan), aURI, doc,
+ info->requireSameHost
+ ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_PING,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, // aLoadFlags,
+ aIOService);
+
+ if (!chan) {
+ return;
+ }
+
+ // Don't bother caching the result of this URI load, but do not exempt
+ // it from Safe Browsing.
+ chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING);
+
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
+ if (!httpChan) {
+ return;
+ }
+
+ // This is needed in order for 3rd-party cookie blocking to work.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
+ nsresult rv;
+ if (httpInternal) {
+ rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = httpChan->SetRequestMethod("POST"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Remove extraneous request headers (to reduce request size)
+ rv = httpChan->SetRequestHeader("accept"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-language"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-encoding"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Always send a Ping-To header.
+ nsAutoCString pingTo;
+ if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
+ rv = httpChan->SetRequestHeader("Ping-To"_ns, pingTo, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> sm =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+ if (sm && info->referrerInfo) {
+ nsCOMPtr<nsIURI> referrer = info->referrerInfo->GetOriginalReferrer();
+ bool referrerIsSecure = false;
+ uint32_t flags = nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY;
+ if (referrer) {
+ rv = NS_URIChainHasFlags(referrer, flags, &referrerIsSecure);
+ }
+
+ // Default to sending less data if NS_URIChainHasFlags() fails.
+ referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
+
+ bool isPrivateWin = false;
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ bool sameOrigin = NS_SUCCEEDED(
+ sm->CheckSameOriginURI(referrer, aURI, false, isPrivateWin));
+
+ // If both the address of the document containing the hyperlink being
+ // audited and "ping URL" have the same origin or the document containing
+ // the hyperlink being audited was not retrieved over an encrypted
+ // connection, send a Ping-From header.
+ if (sameOrigin || !referrerIsSecure) {
+ nsAutoCString pingFrom;
+ if (NS_SUCCEEDED(referrer->GetSpec(pingFrom))) {
+ rv = httpChan->SetRequestHeader("Ping-From"_ns, pingFrom, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // If the document containing the hyperlink being audited was not retrieved
+ // over an encrypted connection and its address does not have the same
+ // origin as "ping URL", send a referrer.
+ if (!sameOrigin && !referrerIsSecure && info->referrerInfo) {
+ rv = httpChan->SetReferrerInfo(info->referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
+ if (!uploadChan) {
+ return;
+ }
+
+ constexpr auto uploadData = "PING"_ns;
+
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uploadChan->ExplicitSetUploadStream(uploadStream, "text/ping"_ns,
+ uploadData.Length(), "POST"_ns, false);
+
+ // The channel needs to have a loadgroup associated with it, so that we can
+ // cancel the channel and any redirected channels it may create.
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ if (!loadGroup) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
+ loadGroup->SetNotificationCallbacks(callbacks);
+ chan->SetLoadGroup(loadGroup);
+
+ RefPtr<nsPingListener> pingListener = new nsPingListener();
+ chan->AsyncOpen(pingListener);
+
+ // Even if AsyncOpen failed, we still count this as a successful ping. It's
+ // possible that AsyncOpen may have failed after triggering some background
+ // process that may have written something to the network.
+ info->numPings++;
+
+ // Prevent ping requests from stalling and never being garbage collected...
+ if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
+ // If we failed to setup the timer, then we should just cancel the channel
+ // because we won't be able to ensure that it goes away in a timely manner.
+ chan->Cancel(NS_ERROR_ABORT);
+ return;
+ }
+ // if the channel openend successfully, then make the pingListener hold
+ // a strong reference to the loadgroup which is released in ::OnStopRequest
+ pingListener->SetLoadGroup(loadGroup);
+}
+
+typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
+ nsIURI* uri, nsIIOService* ios);
+
+static void ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback,
+ void* aClosure) {
+ // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
+ // since we'd still need to parse the resulting string. Instead, we
+ // just parse the raw attribute. It might be nice if the content node
+ // implemented an interface that exposed an enumeration of nsIURIs.
+
+ // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+ // or XHTML namespace.
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area)) {
+ return;
+ }
+
+ nsAutoString value;
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
+ if (value.IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (!ios) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+ nsAutoCString charset;
+ doc->GetDocumentCharacterSet()->Name(charset);
+
+ nsWhitespaceTokenizer tokenizer(value);
+
+ while (tokenizer.hasMoreTokens()) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), tokenizer.nextToken(), charset.get(),
+ aContent->GetBaseURI());
+ // if we can't generate a valid URI, then there is nothing to do
+ if (!uri) {
+ continue;
+ }
+ // Explicitly not allow loading data: URIs
+ if (!net::SchemeIsData(uri)) {
+ aCallback(aClosure, aContent, uri, ios);
+ }
+ }
+}
+
+// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
+/*static*/ void nsPingListener::DispatchPings(nsIDocShell* aDocShell,
+ nsIContent* aContent,
+ nsIURI* aTarget,
+ nsIReferrerInfo* aReferrerInfo) {
+ SendPingInfo info;
+
+ if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
+ return;
+ }
+ if (info.maxPings == 0) {
+ return;
+ }
+
+ info.numPings = 0;
+ info.target = aTarget;
+ info.referrerInfo = aReferrerInfo;
+ info.docShell = aDocShell;
+
+ ForEachPing(aContent, SendPing, &info);
+}
+
+nsPingListener::~nsPingListener() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+nsresult nsPingListener::StartTimeout(DocGroup* aDocGroup) {
+ NS_ENSURE_ARG(aDocGroup);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), OnPingTimeout, mLoadGroup, PING_TIMEOUT,
+ nsITimer::TYPE_ONE_SHOT, "nsPingListener::StartTimeout",
+ aDocGroup->EventTargetFor(TaskCategory::Network));
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t result;
+ return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mLoadGroup = nullptr;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsPingListener.h b/docshell/base/nsPingListener.h
new file mode 100644
index 0000000000..7cf6ff98b5
--- /dev/null
+++ b/docshell/base/nsPingListener.h
@@ -0,0 +1,48 @@
+/* -*- 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 nsPingListener_h__
+#define nsPingListener_h__
+
+#include "nsIStreamListener.h"
+#include "nsIReferrerInfo.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DocGroup;
+}
+} // namespace mozilla
+
+class nsIContent;
+class nsIDocShell;
+class nsILoadGroup;
+class nsITimer;
+class nsIURI;
+
+class nsPingListener final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsPingListener() {}
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ nsresult StartTimeout(mozilla::dom::DocGroup* aDocGroup);
+
+ static void DispatchPings(nsIDocShell* aDocShell, nsIContent* aContent,
+ nsIURI* aTarget, nsIReferrerInfo* aReferrerInfo);
+
+ private:
+ ~nsPingListener();
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif /* nsPingListener_h__ */
diff --git a/docshell/base/nsRefreshTimer.cpp b/docshell/base/nsRefreshTimer.cpp
new file mode 100644
index 0000000000..4f133ce0df
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "nsRefreshTimer.h"
+
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+#include "nsDocShell.h"
+
+NS_IMPL_ADDREF(nsRefreshTimer)
+NS_IMPL_RELEASE(nsRefreshTimer)
+
+NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END
+
+nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI,
+ nsIPrincipal* aPrincipal, int32_t aDelay,
+ bool aRepeat, bool aMetaRefresh)
+ : mDocShell(aDocShell),
+ mURI(aURI),
+ mPrincipal(aPrincipal),
+ mDelay(aDelay),
+ mRepeat(aRepeat),
+ mMetaRefresh(aMetaRefresh) {}
+
+nsRefreshTimer::~nsRefreshTimer() {}
+
+NS_IMETHODIMP
+nsRefreshTimer::Notify(nsITimer* aTimer) {
+ NS_ASSERTION(mDocShell, "DocShell is somehow null");
+
+ if (mDocShell && aTimer) {
+ // Get the delay count to determine load type
+ uint32_t delay = 0;
+ aTimer->GetDelay(&delay);
+ mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, mMetaRefresh,
+ aTimer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsRefreshTimer");
+ return NS_OK;
+}
diff --git a/docshell/base/nsRefreshTimer.h b/docshell/base/nsRefreshTimer.h
new file mode 100644
index 0000000000..78dc1d5220
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.h
@@ -0,0 +1,41 @@
+/* -*- 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 nsRefreshTimer_h__
+#define nsRefreshTimer_h__
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+
+class nsDocShell;
+class nsIURI;
+class nsIPrincipal;
+
+class nsRefreshTimer : public nsITimerCallback, public nsINamed {
+ public:
+ nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI, nsIPrincipal* aPrincipal,
+ int32_t aDelay, bool aRepeat, bool aMetaRefresh);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ int32_t GetDelay() { return mDelay; }
+
+ RefPtr<nsDocShell> mDocShell;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ int32_t mDelay;
+ bool mRepeat;
+ bool mMetaRefresh;
+
+ private:
+ virtual ~nsRefreshTimer();
+};
+
+#endif /* nsRefreshTimer_h__ */
diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp
new file mode 100644
index 0000000000..23c4ffaf16
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "nsWebNavigationInfo.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "nsIWebNavigation.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIPluginHost.h"
+#include "nsIDocShell.h"
+#include "nsContentUtils.h"
+#include "imgLoader.h"
+#include "nsPluginHost.h"
+
+NS_IMPL_ISUPPORTS(nsWebNavigationInfo, nsIWebNavigationInfo)
+
+NS_IMETHODIMP
+nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
+ nsIWebNavigation* aWebNav,
+ uint32_t* aIsTypeSupported) {
+ MOZ_ASSERT(aIsTypeSupported, "null out param?");
+
+ *aIsTypeSupported = IsTypeSupported(aType, aWebNav);
+ return NS_OK;
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
+ nsIWebNavigation* aWebNav) {
+ // Note to self: aWebNav could be an nsWebBrowser or an nsDocShell here (or
+ // an nsSHistory, but not much we can do with that). So if we start using
+ // it here, we need to be careful to get to the docshell correctly.
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aWebNav));
+ auto* browsingContext = docShell ? docShell->GetBrowsingContext() : nullptr;
+ bool pluginsAllowed =
+ browsingContext ? browsingContext->GetAllowPlugins() : true;
+
+ return IsTypeSupported(aType, pluginsAllowed);
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
+ bool aPluginsAllowed) {
+ // We want to claim that the type for PDF documents is unsupported,
+ // so that the internal PDF viewer's stream converted will get used.
+ if (aType.LowerCaseEqualsLiteral("application/pdf") &&
+ nsContentUtils::IsPDFJSEnabled()) {
+ return nsIWebNavigationInfo::UNSUPPORTED;
+ ;
+ }
+
+ const nsCString& flatType = PromiseFlatCString(aType);
+ uint32_t result = IsTypeSupportedInternal(flatType);
+ if (result != nsIWebNavigationInfo::UNSUPPORTED) {
+ return result;
+ }
+
+ // As of FF 52, we only support flash and test plugins, so if the mime types
+ // don't match for that, exit before we start loading plugins.
+ if (!nsPluginHost::CanUsePluginForMIMEType(aType)) {
+ return nsIWebNavigationInfo::UNSUPPORTED;
+ }
+
+ // If this request is for a docShell that isn't going to allow plugins,
+ // there's no need to try and find a plugin to handle it.
+ if (!aPluginsAllowed) {
+ return nsIWebNavigationInfo::UNSUPPORTED;
+ }
+
+ // Try reloading plugins in case they've changed.
+ nsCOMPtr<nsIPluginHost> pluginHost =
+ do_GetService(MOZ_PLUGIN_HOST_CONTRACTID);
+ if (pluginHost) {
+ // false will ensure that currently running plugins will not
+ // be shut down
+ nsresult rv = pluginHost->ReloadPlugins();
+ if (NS_SUCCEEDED(rv)) {
+ // OK, we reloaded plugins and there were new ones
+ // (otherwise NS_ERROR_PLUGINS_PLUGINSNOTCHANGED would have
+ // been returned). Try checking whether we can handle the
+ // content now.
+ return IsTypeSupportedInternal(flatType);
+ }
+ }
+
+ return nsIWebNavigationInfo::UNSUPPORTED;
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupportedInternal(const nsCString& aType) {
+ nsContentUtils::ContentViewerType vtype = nsContentUtils::TYPE_UNSUPPORTED;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ nsContentUtils::FindInternalContentViewer(aType, &vtype);
+
+ switch (vtype) {
+ case nsContentUtils::TYPE_UNSUPPORTED:
+ return nsIWebNavigationInfo::UNSUPPORTED;
+
+ case nsContentUtils::TYPE_PLUGIN:
+ return nsIWebNavigationInfo::PLUGIN;
+
+ case nsContentUtils::TYPE_UNKNOWN:
+ return nsIWebNavigationInfo::OTHER;
+
+ case nsContentUtils::TYPE_CONTENT:
+ // XXXbz we only need this because images register for the same
+ // contractid as documents, so we can't tell them apart based on
+ // contractid.
+ if (imgLoader::SupportImageWithMimeType(aType.get())) {
+ return nsIWebNavigationInfo::IMAGE;
+ } else {
+ return nsIWebNavigationInfo::OTHER;
+ }
+ }
+
+ return nsIWebNavigationInfo::UNSUPPORTED;
+}
diff --git a/docshell/base/nsWebNavigationInfo.h b/docshell/base/nsWebNavigationInfo.h
new file mode 100644
index 0000000000..5abf2e9bab
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.h
@@ -0,0 +1,37 @@
+/* -*- 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 nsWebNavigationInfo_h__
+#define nsWebNavigationInfo_h__
+
+#include "nsIWebNavigationInfo.h"
+#include "nsCOMPtr.h"
+#include "nsICategoryManager.h"
+#include "nsStringFwd.h"
+#include "mozilla/Attributes.h"
+
+class nsWebNavigationInfo final : public nsIWebNavigationInfo {
+ public:
+ nsWebNavigationInfo() {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWEBNAVIGATIONINFO
+
+ static uint32_t IsTypeSupported(const nsACString& aType,
+ nsIWebNavigation* aWebNav);
+ static uint32_t IsTypeSupported(const nsACString& aType,
+ bool aPluginsAllowed);
+
+ private:
+ ~nsWebNavigationInfo() {}
+
+ // Check whether aType is supported, and returns an nsIWebNavigationInfo
+ // constant.
+ static uint32_t IsTypeSupportedInternal(const nsCString& aType);
+};
+
+#endif // nsWebNavigationInfo_h__
diff --git a/docshell/base/timeline/AbstractTimelineMarker.cpp b/docshell/base/timeline/AbstractTimelineMarker.cpp
new file mode 100644
index 0000000000..471742089c
--- /dev/null
+++ b/docshell/base/timeline/AbstractTimelineMarker.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "AbstractTimelineMarker.h"
+
+#include "mozilla/TimeStamp.h"
+#include "MainThreadUtils.h"
+#include "nsAppRunner.h"
+
+namespace mozilla {
+
+AbstractTimelineMarker::AbstractTimelineMarker(const char* aName,
+ MarkerTracingType aTracingType)
+ : mName(aName),
+ mTracingType(aTracingType),
+ mProcessType(XRE_GetProcessType()),
+ mIsOffMainThread(!NS_IsMainThread()) {
+ MOZ_COUNT_CTOR(AbstractTimelineMarker);
+ SetCurrentTime();
+}
+
+AbstractTimelineMarker::AbstractTimelineMarker(const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType)
+ : mName(aName),
+ mTracingType(aTracingType),
+ mProcessType(XRE_GetProcessType()),
+ mIsOffMainThread(!NS_IsMainThread()) {
+ MOZ_COUNT_CTOR(AbstractTimelineMarker);
+ SetCustomTime(aTime);
+}
+
+UniquePtr<AbstractTimelineMarker> AbstractTimelineMarker::Clone() {
+ MOZ_ASSERT(false, "Clone method not yet implemented on this marker type.");
+ return nullptr;
+}
+
+bool AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther) {
+ // Check whether two markers should be considered the same, for the purpose
+ // of pairing start and end markers. Normally this definition suffices.
+ return strcmp(mName, aOther.mName) == 0;
+}
+
+AbstractTimelineMarker::~AbstractTimelineMarker() {
+ MOZ_COUNT_DTOR(AbstractTimelineMarker);
+}
+
+void AbstractTimelineMarker::SetCurrentTime() {
+ TimeStamp now = TimeStamp::Now();
+ SetCustomTime(now);
+}
+
+void AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime) {
+ mTime = (aTime - TimeStamp::ProcessCreation()).ToMilliseconds();
+}
+
+void AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime) {
+ mTime = aTime;
+}
+
+void AbstractTimelineMarker::SetProcessType(GeckoProcessType aProcessType) {
+ mProcessType = aProcessType;
+}
+
+void AbstractTimelineMarker::SetOffMainThread(bool aIsOffMainThread) {
+ mIsOffMainThread = aIsOffMainThread;
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h
new file mode 100644
index 0000000000..8581ba53f8
--- /dev/null
+++ b/docshell/base/timeline/AbstractTimelineMarker.h
@@ -0,0 +1,71 @@
+/* -*- 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_AbstractTimelineMarker_h_
+#define mozilla_AbstractTimelineMarker_h_
+
+#include "TimelineMarkerEnums.h" // for MarkerTracingType
+#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
+#include "nsXULAppAPI.h" // for GeckoProcessType
+#include "mozilla/UniquePtr.h"
+
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+class TimeStamp;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class AbstractTimelineMarker {
+ private:
+ AbstractTimelineMarker() = delete;
+ AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete;
+ void operator=(const AbstractTimelineMarker& aOther) = delete;
+
+ public:
+ AbstractTimelineMarker(const char* aName, MarkerTracingType aTracingType);
+
+ AbstractTimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType);
+
+ virtual ~AbstractTimelineMarker();
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone();
+ virtual bool Equals(const AbstractTimelineMarker& aOther);
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) = 0;
+ virtual JSObject* GetStack() = 0;
+
+ const char* GetName() const { return mName; }
+ DOMHighResTimeStamp GetTime() const { return mTime; }
+ MarkerTracingType GetTracingType() const { return mTracingType; }
+
+ uint8_t GetProcessType() const { return mProcessType; };
+ bool IsOffMainThread() const { return mIsOffMainThread; };
+
+ private:
+ const char* mName;
+ DOMHighResTimeStamp mTime;
+ MarkerTracingType mTracingType;
+
+ uint8_t mProcessType; // @see `enum GeckoProcessType`.
+ bool mIsOffMainThread;
+
+ protected:
+ void SetCurrentTime();
+ void SetCustomTime(const TimeStamp& aTime);
+ void SetCustomTime(DOMHighResTimeStamp aTime);
+ void SetProcessType(GeckoProcessType aProcessType);
+ void SetOffMainThread(bool aIsOffMainThread);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AbstractTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
new file mode 100644
index 0000000000..d8814a960d
--- /dev/null
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
@@ -0,0 +1,41 @@
+/* -*- 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 "AutoGlobalTimelineMarker.h"
+
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+AutoGlobalTimelineMarker::AutoGlobalTimelineMarker(
+ const char* aName, MarkerStackRequest aStackRequest /* = STACK */
+ )
+ : mName(aName), mStackRequest(aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || timelines->IsEmpty()) {
+ return;
+ }
+
+ timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START,
+ mStackRequest);
+}
+
+AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || timelines->IsEmpty()) {
+ return;
+ }
+
+ timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END,
+ mStackRequest);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h
new file mode 100644
index 0000000000..a9bbc92fce
--- /dev/null
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h
@@ -0,0 +1,48 @@
+/* -*- 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_AutoGlobalTimelineMarker_h_
+#define mozilla_AutoGlobalTimelineMarker_h_
+
+#include "mozilla/Attributes.h"
+#include "TimelineMarkerEnums.h"
+
+namespace mozilla {
+
+// # AutoGlobalTimelineMarker
+//
+// Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells,
+// not a single particular one. This is useful for operations that aren't
+// associated with any one particular doc shell, or when it isn't clear which
+// docshell triggered the operation.
+//
+// Example usage:
+//
+// {
+// AutoGlobalTimelineMarker marker("Cycle Collection");
+// nsCycleCollector* cc = GetCycleCollector();
+// cc->Collect();
+// ...
+// }
+class MOZ_RAII AutoGlobalTimelineMarker {
+ // The name of the marker we are adding.
+ const char* mName;
+ // Whether to capture the JS stack or not.
+ MarkerStackRequest mStackRequest;
+
+ public:
+ explicit AutoGlobalTimelineMarker(
+ const char* aName,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ ~AutoGlobalTimelineMarker();
+
+ AutoGlobalTimelineMarker(const AutoGlobalTimelineMarker& aOther) = delete;
+ void operator=(const AutoGlobalTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoGlobalTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.cpp b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp
new file mode 100644
index 0000000000..c012dfbc17
--- /dev/null
+++ b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "AutoRestyleTimelineMarker.h"
+
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+#include "nsIDocShell.h"
+#include "RestyleTimelineMarker.h"
+
+namespace mozilla {
+
+AutoRestyleTimelineMarker::AutoRestyleTimelineMarker(nsIDocShell* aDocShell,
+ bool aIsAnimationOnly)
+ : mDocShell(nullptr), mIsAnimationOnly(aIsAnimationOnly) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || !timelines->HasConsumer(aDocShell)) {
+ return;
+ }
+
+ mDocShell = aDocShell;
+ timelines->AddMarkerForDocShell(
+ mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly,
+ MarkerTracingType::START));
+}
+
+AutoRestyleTimelineMarker::~AutoRestyleTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || !timelines->HasConsumer(mDocShell)) {
+ return;
+ }
+
+ timelines->AddMarkerForDocShell(
+ mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly,
+ MarkerTracingType::END));
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.h b/docshell/base/timeline/AutoRestyleTimelineMarker.h
new file mode 100644
index 0000000000..1246e1a9bd
--- /dev/null
+++ b/docshell/base/timeline/AutoRestyleTimelineMarker.h
@@ -0,0 +1,30 @@
+/* -*- 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_AutoRestyleTimelineMarker_h_
+#define mozilla_AutoRestyleTimelineMarker_h_
+
+#include "mozilla/RefPtr.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+
+class MOZ_RAII AutoRestyleTimelineMarker {
+ RefPtr<nsIDocShell> mDocShell;
+ bool mIsAnimationOnly;
+
+ public:
+ AutoRestyleTimelineMarker(nsIDocShell* aDocShell, bool aIsAnimationOnly);
+ ~AutoRestyleTimelineMarker();
+
+ AutoRestyleTimelineMarker(const AutoRestyleTimelineMarker& aOther) = delete;
+ void operator=(const AutoRestyleTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoRestyleTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoTimelineMarker.cpp b/docshell/base/timeline/AutoTimelineMarker.cpp
new file mode 100644
index 0000000000..714b818f0a
--- /dev/null
+++ b/docshell/base/timeline/AutoTimelineMarker.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "AutoTimelineMarker.h"
+
+#include "nsIDocShell.h"
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell,
+ const char* aName)
+ : mName(aName), mDocShell(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || !timelines->HasConsumer(aDocShell)) {
+ return;
+ }
+
+ mDocShell = aDocShell;
+ timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START);
+}
+
+AutoTimelineMarker::~AutoTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || !timelines->HasConsumer(mDocShell)) {
+ return;
+ }
+
+ timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h
new file mode 100644
index 0000000000..a86c741bb0
--- /dev/null
+++ b/docshell/base/timeline/AutoTimelineMarker.h
@@ -0,0 +1,46 @@
+/* -*- 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_AutoTimelineMarker_h_
+#define mozilla_AutoTimelineMarker_h_
+
+#include "mozilla/RefPtr.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+
+// # AutoTimelineMarker
+//
+// An RAII class to trace some task in the platform by adding a start and end
+// timeline marker pair. These markers are then rendered in the devtools'
+// performance tool's waterfall graph.
+//
+// Example usage:
+//
+// {
+// AutoTimelineMarker marker(mDocShell, "Parse CSS");
+// nsresult rv = ParseTheCSSFile(mFile);
+// ...
+// }
+class MOZ_RAII AutoTimelineMarker {
+ // The name of the marker we are adding.
+ const char* mName;
+
+ // The docshell that is associated with this marker.
+ RefPtr<nsIDocShell> mDocShell;
+
+ public:
+ AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName);
+ ~AutoTimelineMarker();
+
+ AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete;
+ void operator=(const AutoTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoTimelineMarker_h_ */
diff --git a/docshell/base/timeline/CompositeTimelineMarker.h b/docshell/base/timeline/CompositeTimelineMarker.h
new file mode 100644
index 0000000000..cd2896fefa
--- /dev/null
+++ b/docshell/base/timeline/CompositeTimelineMarker.h
@@ -0,0 +1,31 @@
+/* -*- 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_CompositeTimelineMarker_h_
+#define mozilla_CompositeTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class CompositeTimelineMarker : public TimelineMarker {
+ public:
+ CompositeTimelineMarker(const TimeStamp& aTime,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("Composite", aTime, aTracingType) {
+ // Even though these markers end up being created on the main thread in the
+ // content or chrome processes, they actually trace down code in the
+ // compositor parent process. All the information for creating these markers
+ // is sent along via IPC to an nsView when a composite finishes.
+ // Mark this as 'off the main thread' to style it differently in frontends.
+ SetOffMainThread(true);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CompositeTimelineMarker_h_
diff --git a/docshell/base/timeline/ConsoleTimelineMarker.h b/docshell/base/timeline/ConsoleTimelineMarker.h
new file mode 100644
index 0000000000..232aa1a60e
--- /dev/null
+++ b/docshell/base/timeline/ConsoleTimelineMarker.h
@@ -0,0 +1,53 @@
+/* -*- 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_ConsoleTimelineMarker_h_
+#define mozilla_ConsoleTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class ConsoleTimelineMarker : public TimelineMarker {
+ public:
+ ConsoleTimelineMarker(const nsAString& aCause, MarkerTracingType aTracingType)
+ : TimelineMarker("ConsoleTime", aTracingType), mCause(aCause) {
+ // Stack is captured by default on the "start" marker. Explicitly also
+ // capture stack on the "end" marker.
+ if (aTracingType == MarkerTracingType::END) {
+ CaptureStack();
+ }
+ }
+
+ virtual bool Equals(const AbstractTimelineMarker& aOther) override {
+ if (!TimelineMarker::Equals(aOther)) {
+ return false;
+ }
+ // Console markers must have matching causes as well. It is safe to perform
+ // a static_cast here as the previous equality check ensures that this is
+ // a console marker instance.
+ return mCause == static_cast<const ConsoleTimelineMarker*>(&aOther)->mCause;
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mCauseName.Construct(mCause);
+ } else {
+ aMarker.mEndStack = GetStack();
+ }
+ }
+
+ private:
+ nsString mCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ConsoleTimelineMarker_h_
diff --git a/docshell/base/timeline/DocLoadingTimelineMarker.h b/docshell/base/timeline/DocLoadingTimelineMarker.h
new file mode 100644
index 0000000000..8f6a7db780
--- /dev/null
+++ b/docshell/base/timeline/DocLoadingTimelineMarker.h
@@ -0,0 +1,38 @@
+/* -*- 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_DocLoadingTimelineMarker_h_
+#define mozilla_DocLoadingTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class DocLoadingTimelineMarker : public TimelineMarker {
+ public:
+ explicit DocLoadingTimelineMarker(const char* aName)
+ : TimelineMarker(aName, MarkerTracingType::TIMESTAMP),
+ mUnixTime(PR_Now()) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+ aMarker.mUnixTime.Construct(mUnixTime);
+ }
+
+ private:
+ // Certain consumers might use Date.now() or similar for tracing time.
+ // However, TimelineMarkers use process creation as an epoch, which provides
+ // more precision. To allow syncing, attach an additional unix timestamp.
+ // Using this instead of `AbstractTimelineMarker::GetTime()'s` timestamp
+ // is strongly discouraged.
+ PRTime mUnixTime;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DocLoadingTimelineMarker_h_
diff --git a/docshell/base/timeline/EventTimelineMarker.h b/docshell/base/timeline/EventTimelineMarker.h
new file mode 100644
index 0000000000..5e5bc5c4a6
--- /dev/null
+++ b/docshell/base/timeline/EventTimelineMarker.h
@@ -0,0 +1,40 @@
+/* -*- 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_EventTimelineMarker_h_
+#define mozilla_EventTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class EventTimelineMarker : public TimelineMarker {
+ public:
+ EventTimelineMarker(const nsAString& aType, uint16_t aPhase,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("DOMEvent", aTracingType),
+ mType(aType),
+ mPhase(aPhase) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mType.Construct(mType);
+ aMarker.mEventPhase.Construct(mPhase);
+ }
+ }
+
+ private:
+ nsString mType;
+ uint16_t mPhase;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_EventTimelineMarker_h_
diff --git a/docshell/base/timeline/JavascriptTimelineMarker.h b/docshell/base/timeline/JavascriptTimelineMarker.h
new file mode 100644
index 0000000000..0e07f3b605
--- /dev/null
+++ b/docshell/base/timeline/JavascriptTimelineMarker.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 mozilla_JavascriptTimelineMarker_h_
+#define mozilla_JavascriptTimelineMarker_h_
+
+#include "TimelineMarker.h"
+
+#include "mozilla/Maybe.h"
+
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+
+namespace mozilla {
+
+class JavascriptTimelineMarker : public TimelineMarker {
+ public:
+ // The caller owns |aAsyncCause| here, so we must copy it into a separate
+ // string for use later on.
+ JavascriptTimelineMarker(const char* aReason, const nsAString& aFunctionName,
+ const nsAString& aFileName, uint32_t aLineNumber,
+ MarkerTracingType aTracingType,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause)
+ : TimelineMarker("Javascript", aTracingType,
+ MarkerStackRequest::NO_STACK),
+ mCause(NS_ConvertUTF8toUTF16(aReason)),
+ mFunctionName(aFunctionName),
+ mFileName(aFileName),
+ mLineNumber(aLineNumber),
+ mAsyncCause(aAsyncCause) {
+ JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+ if (ctx) {
+ mAsyncStack.init(ctx, aAsyncStack);
+ }
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ aMarker.mCauseName.Construct(mCause);
+
+ if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) {
+ dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx);
+ stackFrame.mLine.Construct(mLineNumber);
+ stackFrame.mSource.Construct(mFileName);
+ stackFrame.mFunctionDisplayName.Construct(mFunctionName);
+
+ if (mAsyncStack.isObject() && !mAsyncCause.IsEmpty()) {
+ JS::Rooted<JSObject*> asyncStack(aCx, &mAsyncStack.toObject());
+ JS::Rooted<JSObject*> parentFrame(aCx);
+ JS::Rooted<JSString*> asyncCause(
+ aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
+ mAsyncCause.Length()));
+ if (!asyncCause) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ if (JS::IsMaybeWrappedSavedFrame(asyncStack) &&
+ !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame,
+ mozilla::Nothing())) {
+ JS_ClearPendingException(aCx);
+ } else {
+ stackFrame.mAsyncParent = parentFrame;
+ }
+ }
+
+ JS::Rooted<JS::Value> newStack(aCx);
+ if (ToJSValue(aCx, stackFrame, &newStack)) {
+ if (newStack.isObject()) {
+ aMarker.mStack = &newStack.toObject();
+ }
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+ }
+
+ private:
+ nsString mCause;
+ nsString mFunctionName;
+ nsString mFileName;
+ uint32_t mLineNumber;
+ JS::PersistentRooted<JS::Value> mAsyncStack;
+ NS_ConvertUTF8toUTF16 mAsyncCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_JavascriptTimelineMarker_h_
diff --git a/docshell/base/timeline/LayerTimelineMarker.h b/docshell/base/timeline/LayerTimelineMarker.h
new file mode 100644
index 0000000000..7c7336ba52
--- /dev/null
+++ b/docshell/base/timeline/LayerTimelineMarker.h
@@ -0,0 +1,47 @@
+/* -*- 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_LayerTimelineMarker_h_
+#define mozilla_LayerTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/mozalloc_oom.h"
+#include "nsRegion.h"
+
+namespace mozilla {
+
+class LayerTimelineMarker : public TimelineMarker {
+ public:
+ explicit LayerTimelineMarker(const nsIntRegion& aRegion)
+ : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT),
+ mRegion(aRegion) {}
+
+ void AddLayerRectangles(
+ dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles) {
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const nsIntRect& iterRect = iter.Get();
+ dom::ProfileTimelineLayerRect rect;
+ rect.mX = iterRect.X();
+ rect.mY = iterRect.Y();
+ rect.mWidth = iterRect.Width();
+ rect.mHeight = iterRect.Height();
+ if (!aRectangles.AppendElement(rect, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+
+ private:
+ nsIntRegion mRegion;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LayerTimelineMarker_h_
diff --git a/docshell/base/timeline/MarkersStorage.cpp b/docshell/base/timeline/MarkersStorage.cpp
new file mode 100644
index 0000000000..4a84135f29
--- /dev/null
+++ b/docshell/base/timeline/MarkersStorage.cpp
@@ -0,0 +1,20 @@
+/* -*- 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 "MarkersStorage.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+MarkersStorage::MarkersStorage(const char* aMutexName) : mLock(aMutexName) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+MarkersStorage::~MarkersStorage() { MOZ_ASSERT(NS_IsMainThread()); }
+
+Mutex& MarkersStorage::GetLock() { return mLock; }
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/MarkersStorage.h b/docshell/base/timeline/MarkersStorage.h
new file mode 100644
index 0000000000..746a02e4ec
--- /dev/null
+++ b/docshell/base/timeline/MarkersStorage.h
@@ -0,0 +1,48 @@
+/* -*- 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_MarkersStorage_h_
+#define mozilla_MarkersStorage_h_
+
+#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/LinkedList.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class MarkersStorage : public LinkedListElement<MarkersStorage> {
+ private:
+ MarkersStorage() = delete;
+ MarkersStorage(const MarkersStorage& aOther) = delete;
+ void operator=(const MarkersStorage& aOther) = delete;
+
+ public:
+ explicit MarkersStorage(const char* aMutexName);
+ virtual ~MarkersStorage();
+
+ virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+ virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+ virtual void ClearMarkers() = 0;
+ virtual void PopMarkers(JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) = 0;
+
+ protected:
+ Mutex& GetLock();
+
+ private:
+ Mutex mLock;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_MarkersStorage_h_ */
diff --git a/docshell/base/timeline/MessagePortTimelineMarker.h b/docshell/base/timeline/MessagePortTimelineMarker.h
new file mode 100644
index 0000000000..c6d91fa7eb
--- /dev/null
+++ b/docshell/base/timeline/MessagePortTimelineMarker.h
@@ -0,0 +1,46 @@
+/* -*- 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_MessagePortTimelineMarker_h_
+#define mozilla_MessagePortTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class MessagePortTimelineMarker : public TimelineMarker {
+ public:
+ MessagePortTimelineMarker(
+ dom::ProfileTimelineMessagePortOperationType aOperationType,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("MessagePort", aTracingType,
+ MarkerStackRequest::NO_STACK),
+ mOperationType(aOperationType) {}
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone() override {
+ MessagePortTimelineMarker* clone =
+ new MessagePortTimelineMarker(mOperationType, GetTracingType());
+ clone->SetCustomTime(GetTime());
+ return UniquePtr<AbstractTimelineMarker>(clone);
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mMessagePortOperation.Construct(mOperationType);
+ }
+ }
+
+ private:
+ dom::ProfileTimelineMessagePortOperationType mOperationType;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_MessagePortTimelineMarker_h_ */
diff --git a/docshell/base/timeline/ObservedDocShell.cpp b/docshell/base/timeline/ObservedDocShell.cpp
new file mode 100644
index 0000000000..4cba68ad29
--- /dev/null
+++ b/docshell/base/timeline/ObservedDocShell.cpp
@@ -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/. */
+
+#include "ObservedDocShell.h"
+
+#include <utility>
+
+#include "AbstractTimelineMarker.h"
+#include "LayerTimelineMarker.h"
+#include "MainThreadUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "nsIDocShell.h"
+
+namespace mozilla {
+
+ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell)
+ : MarkersStorage("ObservedDocShellMutex"),
+ mDocShell(aDocShell),
+ mPopping(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ // Only allow main thread markers to go into this list. No need to lock
+ // here since `mTimelineMarkers` will only be accessed or modified on the
+ // main thread only.
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't accept any markers generated by the process of popping
+ // markers.
+ if (!mPopping) {
+ mTimelineMarkers.AppendElement(std::move(aMarker));
+ }
+}
+
+void ObservedDocShell::AddOTMTMarker(
+ UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ // Only allow off the main thread markers to go into this list. Since most
+ // of our markers come from the main thread, be a little more efficient and
+ // avoid dealing with multithreading scenarios until all the markers are
+ // actually cleared or popped in `ClearMarkers` or `PopMarkers`.
+ MOZ_ASSERT(!NS_IsMainThread());
+ MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
+ mOffTheMainThreadTimelineMarkers.AppendElement(std::move(aMarker));
+}
+
+void ObservedDocShell::ClearMarkers() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
+ mTimelineMarkers.Clear();
+ mOffTheMainThreadTimelineMarkers.Clear();
+}
+
+void ObservedDocShell::PopMarkers(
+ JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
+
+ MOZ_RELEASE_ASSERT(!mPopping);
+ AutoRestore<bool> resetPopping(mPopping);
+ mPopping = true;
+
+ // First, move all of our markers into a single array. We'll chose
+ // the `mTimelineMarkers` store because that's where we expect most of
+ // our markers to be.
+ mTimelineMarkers.AppendElements(std::move(mOffTheMainThreadTimelineMarkers));
+
+ // If we see an unpaired START, we keep it around for the next call
+ // to ObservedDocShell::PopMarkers. We store the kept START objects here.
+ nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers;
+
+ for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) {
+ UniquePtr<AbstractTimelineMarker>& startPayload =
+ mTimelineMarkers.ElementAt(i);
+
+ // If this is a TIMESTAMP marker, there's no corresponding END,
+ // as it's a single unit of time, not a duration.
+ if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) {
+ dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+ marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+ marker->mStart = startPayload->GetTime();
+ marker->mEnd = startPayload->GetTime();
+ marker->mStack = startPayload->GetStack();
+ startPayload->AddDetails(aCx, *marker);
+ continue;
+ }
+
+ // Whenever a START marker is found, look for the corresponding END
+ // and build a {name,start,end} JS object.
+ if (startPayload->GetTracingType() == MarkerTracingType::START) {
+ bool hasSeenEnd = false;
+
+ // "Paint" markers are different because painting is handled at root
+ // docshell level. The information that a paint was done is stored at
+ // sub-docshell level, but we can only be sure that a paint did actually
+ // happen in if a "Layer" marker was recorded too.
+ bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0;
+ bool hasSeenLayerType = false;
+
+ // If we are processing a "Paint" marker, we append information from
+ // all the embedded "Layer" markers to this array.
+ dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
+
+ // DOM events can be nested, so we must take care when searching
+ // for the matching end. It doesn't hurt to apply this logic to
+ // all event types.
+ uint32_t markerDepth = 0;
+
+ // The assumption is that the devtools timeline flushes markers frequently
+ // enough for the amount of markers to always be small enough that the
+ // nested for loop isn't going to be a performance problem.
+ for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) {
+ UniquePtr<AbstractTimelineMarker>& endPayload =
+ mTimelineMarkers.ElementAt(j);
+ bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0;
+
+ // Look for "Layer" markers to stream out "Paint" markers.
+ if (startIsPaintType && endIsLayerType) {
+ AbstractTimelineMarker* raw = endPayload.get();
+ LayerTimelineMarker* layerPayload =
+ static_cast<LayerTimelineMarker*>(raw);
+ layerPayload->AddLayerRectangles(layerRectangles);
+ hasSeenLayerType = true;
+ }
+ if (!startPayload->Equals(*endPayload)) {
+ continue;
+ }
+ if (endPayload->GetTracingType() == MarkerTracingType::START) {
+ ++markerDepth;
+ continue;
+ }
+ if (endPayload->GetTracingType() == MarkerTracingType::END) {
+ if (markerDepth > 0) {
+ --markerDepth;
+ continue;
+ }
+ if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) {
+ dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+ marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+ marker->mStart = startPayload->GetTime();
+ marker->mEnd = endPayload->GetTime();
+ marker->mStack = startPayload->GetStack();
+ if (hasSeenLayerType) {
+ marker->mRectangles.Construct(layerRectangles);
+ }
+ startPayload->AddDetails(aCx, *marker);
+ endPayload->AddDetails(aCx, *marker);
+ }
+ hasSeenEnd = true;
+ break;
+ }
+ }
+
+ // If we did not see the corresponding END, keep the START.
+ if (!hasSeenEnd) {
+ keptStartMarkers.AppendElement(
+ std::move(mTimelineMarkers.ElementAt(i)));
+ mTimelineMarkers.RemoveElementAt(i);
+ --i;
+ }
+ }
+ }
+
+ mTimelineMarkers = std::move(keptStartMarkers);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/ObservedDocShell.h b/docshell/base/timeline/ObservedDocShell.h
new file mode 100644
index 0000000000..88b6747efb
--- /dev/null
+++ b/docshell/base/timeline/ObservedDocShell.h
@@ -0,0 +1,52 @@
+/* -*- 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_ObservedDocShell_h_
+#define mozilla_ObservedDocShell_h_
+
+#include "MarkersStorage.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+// # ObservedDocShell
+//
+// A wrapper around a docshell for which docshell-specific markers are
+// allowed to exist. See TimelineConsumers for register/unregister logic.
+class ObservedDocShell : public MarkersStorage {
+ private:
+ RefPtr<nsIDocShell> mDocShell;
+
+ // Main thread only.
+ nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers;
+ bool mPopping;
+
+ // Off the main thread only.
+ nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers;
+
+ public:
+ explicit ObservedDocShell(nsIDocShell* aDocShell);
+ nsIDocShell* operator*() const { return mDocShell.get(); }
+
+ void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+ void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+ void ClearMarkers() override;
+ void PopMarkers(JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) override;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_ObservedDocShell_h_ */
diff --git a/docshell/base/timeline/RestyleTimelineMarker.h b/docshell/base/timeline/RestyleTimelineMarker.h
new file mode 100644
index 0000000000..4cce10d94b
--- /dev/null
+++ b/docshell/base/timeline/RestyleTimelineMarker.h
@@ -0,0 +1,37 @@
+/* -*- 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_RestyleTimelineMarker_h_
+#define mozilla_RestyleTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class RestyleTimelineMarker : public TimelineMarker {
+ public:
+ RestyleTimelineMarker(bool aIsAnimationOnly, MarkerTracingType aTracingType)
+ : TimelineMarker("Styles", aTracingType) {
+ mIsAnimationOnly = aIsAnimationOnly;
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mIsAnimationOnly.Construct(mIsAnimationOnly);
+ }
+ }
+
+ private:
+ bool mIsAnimationOnly;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RestyleTimelineMarker_h_
diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp
new file mode 100644
index 0000000000..805a54979c
--- /dev/null
+++ b/docshell/base/timeline/TimelineConsumers.cpp
@@ -0,0 +1,287 @@
+/* -*- 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 "TimelineConsumers.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ObservedDocShell.h"
+#include "mozilla/Services.h"
+#include "mozilla/TimelineMarker.h"
+#include "jsapi.h"
+#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess
+#include "nsCRT.h"
+#include "nsDocShell.h"
+#include "nsIObserverService.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver);
+
+StaticMutex TimelineConsumers::sMutex;
+
+// Manually manage this singleton's lifetime and destroy it before shutdown.
+// This avoids the leakchecker detecting false-positive memory leaks when
+// using automatic memory management (i.e. statically instantiating this
+// singleton inside the `Get` method), which would automatically destroy it on
+// application shutdown, but too late for the leakchecker. Sigh...
+StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance;
+
+// This flag makes sure the singleton never gets instantiated while a shutdown
+// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work
+// in these cases.
+bool TimelineConsumers::sInShutdown = false;
+
+already_AddRefed<TimelineConsumers> TimelineConsumers::Get() {
+ // Using this class is not supported yet for other processes other than
+ // parent or content. To avoid accidental checks to methods like `IsEmpty`,
+ // which would probably always be true in those cases, assert here.
+ // Remember, there will be different singletons available to each process.
+
+ // TODO: we have to avoid calling this function in socket process.
+ MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess());
+
+ // If we are shutting down, don't bother doing anything. Note: we can only
+ // know whether or not we're in shutdown if we're instantiated.
+ if (sInShutdown) {
+ return nullptr;
+ }
+
+ // Note: We don't simply check `sInstance` for null-ness here, since otherwise
+ // this can resurrect the TimelineConsumers pretty late during shutdown.
+ // We won't know if we're in shutdown or not though, because the singleton
+ // could have been destroyed or just never instantiated, so in the previous
+ // conditional `sInShutdown` would be false.
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+
+ StaticMutexAutoLock lock(sMutex);
+ sInstance = new TimelineConsumers();
+
+ // Make sure the initialization actually suceeds, otherwise don't allow
+ // access by destroying the instance immediately.
+ if (sInstance->Init()) {
+ ClearOnShutdown(&sInstance);
+ } else {
+ sInstance->RemoveObservers();
+ sInstance = nullptr;
+ }
+ }
+
+ RefPtr<TimelineConsumers> copy = sInstance.get();
+ return copy.forget();
+}
+
+bool TimelineConsumers::Init() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return false;
+ }
+ if (NS_WARN_IF(NS_FAILED(
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
+ return false;
+ }
+ return true;
+}
+
+bool TimelineConsumers::RemoveObservers() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return false;
+ }
+ if (NS_WARN_IF(NS_FAILED(
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) {
+ return false;
+ }
+ return true;
+}
+
+nsresult TimelineConsumers::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ sInShutdown = true;
+ RemoveObservers();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+TimelineConsumers::TimelineConsumers() : mActiveConsumers(0) {}
+
+void TimelineConsumers::AddConsumer(nsDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(
+ sMutex); // for `mActiveConsumers` and `mMarkersStores`.
+
+ UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
+ MOZ_ASSERT(!observed);
+
+ if (mActiveConsumers == 0) {
+ JS::SetProfileTimelineRecordingEnabled(true);
+ }
+ mActiveConsumers++;
+
+ ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell);
+ MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell);
+
+ observed.reset(obsDocShell);
+ mMarkersStores.insertFront(storage);
+}
+
+void TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(
+ sMutex); // for `mActiveConsumers` and `mMarkersStores`.
+
+ UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
+ MOZ_ASSERT(observed);
+
+ mActiveConsumers--;
+ if (mActiveConsumers == 0) {
+ JS::SetProfileTimelineRecordingEnabled(false);
+ }
+
+ // Clear all markers from the `mTimelineMarkers` store.
+ observed.get()->ClearMarkers();
+ // Remove self from the `mMarkersStores` store.
+ observed.get()->remove();
+ // Prepare for becoming a consumer later.
+ observed.reset(nullptr);
+}
+
+bool TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return aDocShell ? aDocShell->GetRecordProfileTimelineMarkers() : false;
+}
+
+bool TimelineConsumers::IsEmpty() {
+ StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`.
+ return mActiveConsumers == 0;
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
+ const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(
+ MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
+ const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(
+ MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(std::move(aMarker));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
+ const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType,
+ aStackRequest);
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
+ const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime,
+ aTracingType, aStackRequest);
+}
+
+void TimelineConsumers::AddMarkerForDocShell(
+ nsIDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), std::move(aMarker));
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest /* = STACK */) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
+
+ for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> marker =
+ MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest);
+ if (isMainThread) {
+ storage->AddMarker(std::move(marker));
+ } else {
+ storage->AddOTMTMarker(std::move(marker));
+ }
+ }
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest /* = STACK */) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
+
+ for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> marker =
+ MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest);
+ if (isMainThread) {
+ storage->AddMarker(std::move(marker));
+ } else {
+ storage->AddOTMTMarker(std::move(marker));
+ }
+ }
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ UniquePtr<AbstractTimelineMarker>& aMarker) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
+
+ for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone();
+ if (isMainThread) {
+ storage->AddMarker(std::move(clone));
+ } else {
+ storage->AddOTMTMarker(std::move(clone));
+ }
+ }
+}
+
+void TimelineConsumers::PopMarkers(
+ nsDocShell* aDocShell, JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell || !aDocShell->mObserved) {
+ return;
+ }
+
+ aDocShell->mObserved->PopMarkers(aCx, aStore);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/TimelineConsumers.h b/docshell/base/timeline/TimelineConsumers.h
new file mode 100644
index 0000000000..6a152cfd55
--- /dev/null
+++ b/docshell/base/timeline/TimelineConsumers.h
@@ -0,0 +1,131 @@
+/* -*- 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_TimelineConsumers_h_
+#define mozilla_TimelineConsumers_h_
+
+#include "nsIObserver.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticMutex.h"
+#include "nsTArray.h"
+#include "TimelineMarkerEnums.h" // for MarkerTracingType
+
+class nsDocShell;
+class nsIDocShell;
+struct JSContext;
+
+namespace mozilla {
+class TimeStamp;
+class MarkersStorage;
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class TimelineConsumers : public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ TimelineConsumers();
+ TimelineConsumers(const TimelineConsumers& aOther) = delete;
+ void operator=(const TimelineConsumers& aOther) = delete;
+ virtual ~TimelineConsumers() = default;
+
+ bool Init();
+ bool RemoveObservers();
+
+ public:
+ static already_AddRefed<TimelineConsumers> Get();
+
+ // Methods for registering interested consumers (i.e. "devtools toolboxes").
+ // Each consumer should be directly focused on a particular docshell, but
+ // timeline markers don't necessarily have to be tied to that docshell.
+ // See the public `AddMarker*` methods below.
+ // Main thread only.
+ void AddConsumer(nsDocShell* aDocShell);
+ void RemoveConsumer(nsDocShell* aDocShell);
+
+ bool HasConsumer(nsIDocShell* aDocShell);
+
+ // Checks if there's any existing interested consumer.
+ // May be called from any thread.
+ bool IsEmpty();
+
+ // Methods for adding markers relevant for particular docshells, or generic
+ // (meaning that they either can't be tied to a particular docshell, or one
+ // wasn't accessible in the part of the codebase where they're instantiated).
+ // These will only add markers if at least one docshell is currently being
+ // observed by a timeline. Markers tied to a particular docshell won't be
+ // created unless that docshell is specifically being currently observed.
+ // See nsIDocShell::recordProfileTimelineMarkers
+
+ // These methods create a basic TimelineMarker from a name and some metadata,
+ // relevant for a specific docshell.
+ // Main thread only.
+ void AddMarkerForDocShell(
+ nsDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ void AddMarkerForDocShell(
+ nsIDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ void AddMarkerForDocShell(
+ nsDocShell* aDocShell, const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ void AddMarkerForDocShell(
+ nsIDocShell* aDocShell, const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ // These methods register and receive ownership of an already created marker,
+ // relevant for a specific docshell.
+ // Main thread only.
+ void AddMarkerForDocShell(nsDocShell* aDocShell,
+ UniquePtr<AbstractTimelineMarker>&& aMarker);
+ void AddMarkerForDocShell(nsIDocShell* aDocShell,
+ UniquePtr<AbstractTimelineMarker>&& aMarker);
+
+ // These methods create a basic marker from a name and some metadata,
+ // which doesn't have to be relevant to a specific docshell.
+ // May be called from any thread.
+ void AddMarkerForAllObservedDocShells(
+ const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ void AddMarkerForAllObservedDocShells(
+ const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ // This method clones and registers an already instantiated marker,
+ // which doesn't have to be relevant to a specific docshell.
+ // May be called from any thread.
+ void AddMarkerForAllObservedDocShells(
+ UniquePtr<AbstractTimelineMarker>& aMarker);
+
+ void PopMarkers(nsDocShell* aDocShell, JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore);
+
+ private:
+ static StaticRefPtr<TimelineConsumers> sInstance;
+ static bool sInShutdown;
+
+ // Counter for how many timelines are currently interested in markers,
+ // and a list of the MarkersStorage interfaces representing them.
+ unsigned long mActiveConsumers;
+ LinkedList<MarkersStorage> mMarkersStores;
+
+ // Protects this class's data structures.
+ static StaticMutex sMutex;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimelineConsumers_h_ */
diff --git a/docshell/base/timeline/TimelineMarker.cpp b/docshell/base/timeline/TimelineMarker.cpp
new file mode 100644
index 0000000000..f43c892a04
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarker.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "TimelineMarker.h"
+
+#include "jsapi.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+TimelineMarker::TimelineMarker(const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest)
+ : AbstractTimelineMarker(aName, aTracingType) {
+ CaptureStackIfNecessary(aTracingType, aStackRequest);
+}
+
+TimelineMarker::TimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest)
+ : AbstractTimelineMarker(aName, aTime, aTracingType) {
+ CaptureStackIfNecessary(aTracingType, aStackRequest);
+}
+
+void TimelineMarker::AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) {
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mProcessType.Construct(GetProcessType());
+ aMarker.mIsOffMainThread.Construct(IsOffMainThread());
+ }
+}
+
+JSObject* TimelineMarker::GetStack() {
+ if (mStackTrace.initialized()) {
+ return mStackTrace;
+ }
+ return nullptr;
+}
+
+void TimelineMarker::CaptureStack() {
+ JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+ if (ctx) {
+ JS::RootedObject stack(ctx);
+ if (JS::CaptureCurrentStack(ctx, &stack)) {
+ mStackTrace.init(ctx, stack.get());
+ } else {
+ JS_ClearPendingException(ctx);
+ }
+ }
+}
+
+void TimelineMarker::CaptureStackIfNecessary(MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ if ((aTracingType == MarkerTracingType::START ||
+ aTracingType == MarkerTracingType::TIMESTAMP) &&
+ aStackRequest != MarkerStackRequest::NO_STACK) {
+ CaptureStack();
+ }
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/TimelineMarker.h b/docshell/base/timeline/TimelineMarker.h
new file mode 100644
index 0000000000..b7c5a83ce9
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarker.h
@@ -0,0 +1,47 @@
+/* -*- 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_TimelineMarker_h_
+#define mozilla_TimelineMarker_h_
+
+#include "AbstractTimelineMarker.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla {
+
+// Objects of this type can be added to the timeline if there is an interested
+// consumer. The class can also be subclassed to let a given marker creator
+// provide custom details.
+class TimelineMarker : public AbstractTimelineMarker {
+ public:
+ TimelineMarker(const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ TimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override;
+ virtual JSObject* GetStack() override;
+
+ protected:
+ void CaptureStack();
+
+ private:
+ // While normally it is not a good idea to make a persistent root,
+ // in this case changing nsDocShell to participate in cycle
+ // collection was deemed too invasive, and the markers are only held
+ // here temporarily to boot.
+ JS::PersistentRooted<JSObject*> mStackTrace;
+
+ void CaptureStackIfNecessary(MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimelineMarker_h_ */
diff --git a/docshell/base/timeline/TimelineMarkerEnums.h b/docshell/base/timeline/TimelineMarkerEnums.h
new file mode 100644
index 0000000000..6c6a39245c
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarkerEnums.h
@@ -0,0 +1,18 @@
+/* -*- 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_TimelineMarkerEnums_h_
+#define mozilla_TimelineMarkerEnums_h_
+
+namespace mozilla {
+
+enum class MarkerTracingType { START, END, TIMESTAMP, HELPER_EVENT };
+
+enum class MarkerStackRequest { STACK, NO_STACK };
+
+} // namespace mozilla
+
+#endif // mozilla_TimelineMarkerEnums_h_
diff --git a/docshell/base/timeline/TimestampTimelineMarker.h b/docshell/base/timeline/TimestampTimelineMarker.h
new file mode 100644
index 0000000000..6950f85899
--- /dev/null
+++ b/docshell/base/timeline/TimestampTimelineMarker.h
@@ -0,0 +1,36 @@
+/* -*- 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_TimestampTimelineMarker_h_
+#define mozilla_TimestampTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class TimestampTimelineMarker : public TimelineMarker {
+ public:
+ explicit TimestampTimelineMarker(const nsAString& aCause)
+ : TimelineMarker("TimeStamp", MarkerTracingType::TIMESTAMP),
+ mCause(aCause) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (!mCause.IsEmpty()) {
+ aMarker.mCauseName.Construct(mCause);
+ }
+ }
+
+ private:
+ nsString mCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TimestampTimelineMarker_h_
diff --git a/docshell/base/timeline/WorkerTimelineMarker.h b/docshell/base/timeline/WorkerTimelineMarker.h
new file mode 100644
index 0000000000..c0c0517b09
--- /dev/null
+++ b/docshell/base/timeline/WorkerTimelineMarker.h
@@ -0,0 +1,44 @@
+/* -*- 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_WorkerTimelineMarker_h_
+#define mozilla_WorkerTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class WorkerTimelineMarker : public TimelineMarker {
+ public:
+ WorkerTimelineMarker(dom::ProfileTimelineWorkerOperationType aOperationType,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK),
+ mOperationType(aOperationType) {}
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone() override {
+ WorkerTimelineMarker* clone =
+ new WorkerTimelineMarker(mOperationType, GetTracingType());
+ clone->SetCustomTime(GetTime());
+ return UniquePtr<AbstractTimelineMarker>(clone);
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mWorkerOperation.Construct(mOperationType);
+ }
+ }
+
+ private:
+ dom::ProfileTimelineWorkerOperationType mOperationType;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_WorkerTimelineMarker_h_ */
diff --git a/docshell/base/timeline/moz.build b/docshell/base/timeline/moz.build
new file mode 100644
index 0000000000..9147e8263c
--- /dev/null
+++ b/docshell/base/timeline/moz.build
@@ -0,0 +1,45 @@
+# -*- 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 = ("DevTools", "Performance Tools (Profiler/Timeline)")
+
+EXPORTS.mozilla += [
+ "AbstractTimelineMarker.h",
+ "AutoGlobalTimelineMarker.h",
+ "AutoRestyleTimelineMarker.h",
+ "AutoTimelineMarker.h",
+ "CompositeTimelineMarker.h",
+ "ConsoleTimelineMarker.h",
+ "DocLoadingTimelineMarker.h",
+ "EventTimelineMarker.h",
+ "JavascriptTimelineMarker.h",
+ "LayerTimelineMarker.h",
+ "MarkersStorage.h",
+ "MessagePortTimelineMarker.h",
+ "ObservedDocShell.h",
+ "RestyleTimelineMarker.h",
+ "TimelineConsumers.h",
+ "TimelineMarker.h",
+ "TimelineMarkerEnums.h",
+ "TimestampTimelineMarker.h",
+ "WorkerTimelineMarker.h",
+]
+
+UNIFIED_SOURCES += [
+ "AbstractTimelineMarker.cpp",
+ "AutoGlobalTimelineMarker.cpp",
+ "AutoRestyleTimelineMarker.cpp",
+ "AutoTimelineMarker.cpp",
+ "MarkersStorage.cpp",
+ "ObservedDocShell.cpp",
+ "TimelineConsumers.cpp",
+ "TimelineMarker.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += ["/docshell/base"]
diff --git a/docshell/base/timeline/readme.md b/docshell/base/timeline/readme.md
new file mode 100644
index 0000000000..d2c3d5c215
--- /dev/null
+++ b/docshell/base/timeline/readme.md
@@ -0,0 +1,97 @@
+
+#Timeline
+
+The files in this directory are concerned with providing the backend platform features required for the developer tools interested in tracking down operations done in Gecko. The mechanism we use to define these operations are `markers`.
+
+Examples of traced operations include:
+
+* Style Recalculation
+* Layout
+* Painting
+* JavaScript run-to-completion
+* HTML parsing
+* etc.
+
+The traced operations are displayed in the DevTools Performance tool's timeline.
+
+This is an overview of how everything works and can be extended.
+
+##MarkersStorage
+A `MarkersStorage` is an abstract class defining a place where timeline markers may be held. It defines an interface with pure virtual functions to highlight how this storage can be interacted with:
+
+- `AddMarker`: adding a marker, from the main thread only
+- `AddOTMTMarker`: adding a marker off the main thread only
+- `ClearMarkers`: clearing all accumulated markers (both from the main thread and off it)
+- `PopMarkers`: popping all accumulated markers (both from the main thread and off it).
+
+Note on why we handle on/off the main thread markers separately: since most of our markers will come from the main thread, we can be a little more efficient and avoid dealing with multithreading scenarios until all the markers are actually cleared or popped in `ClearMarkers` or `PopMarkers`. Main thread markers may only be added via `AddMarker`, while off the main thread markers may only be added via `AddOTMTMarker`. Clearing and popping markers will yield until all operations involving off the main thread markers finish. When popping, the markers accumulated off the main thread will be moved over. We expect popping to be fairly infrequent (every few hundred milliseconds, currently we schedule this to happen every 200ms).
+
+##ObservedDocShell
+The only implementation of a MarkersStorage we have right now is an `ObservedDocShell`.
+
+Instances of `ObservedDocShell` accumulate markers that are *mostly* about a particular docshell. At a high level, for example, an `ObservedDocshell` would be created when a timeline tool is opened on a page. It is reasonable to assume that most operations which are interesting for that particular page happen on the main thread. However certain operations may happen outside of it, yet are interesting for its developers, for which markers can be created as well (e.g. web audio stuff, service workers etc.). It is also reasonable to assume that a docshell may sometimes not be easily accessible from certain parts of the platform code, but for which markers still need to be created.
+
+Therefore, the following scenarios arise:
+
+- a). creating a marker on the main thread about a particular docshell
+
+- b). creating a marker on the main thread without pinpointing to an affected docshell (unlikely, but allowed; in this case such a marker would have to be stored in all currently existing `ObservedDocShell` instances)
+
+- c). creating a marker off the main thread about a particular docshell (impossible; docshells can't be referenced outside the main thread, in which case some other type of identification mechanism needs to be put in place).
+
+- d). creating a marker off the main thread without pinpointing to a particular docshell (same path as c. here, such a marker would have to be stored in all currently existing `ObservedDocShell` instances).
+
+An observed docshell (in other words, "a docshell for which a timeline tool was opened") can thus receive both main thread and off the main thread markers.
+
+Cross-process markers are unnecessary at the moment, but tracked in bug 1200120.
+
+##TimelineConsumers
+A `TimelineConsumer` is a singleton that facilitates access to `ObservedDocShell` instances. This is where a docshell can register/unregister itself as being observed via the `AddConsumer` and `RemoveConsumer` methods.
+
+All markers may only be stored via this singleton. Certain helper methods are available:
+
+* Main thread only
+`AddMarkerForDocShell(nsDocShell*, const char*, MarkerTracingType)`
+`AddMarkerForDocShell(nsDocShell*, const char*, const TimeStamp&, MarkerTracingType)`
+`AddMarkerForDocShell(nsDocShell*, UniquePtr<AbstractTimelineMarker>&&)`
+
+* Any thread
+`AddMarkerForAllObservedDocShells(const char*, MarkerTracingType)`
+`AddMarkerForAllObservedDocShells(const char*, const TimeStamp&, MarkerTracingType)`
+`AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>&)`
+
+The "main thread only" methods deal with point a). described above. The "any thread" methods deal with points b). and d).
+
+##AbstractTimelineMarker
+
+All markers inherit from this abstract class, providing a simple thread-safe extendable blueprint.
+
+Markers are readonly after instantiation, and will always be identified by a name, a timestamp and their tracing type (`START`, `END`, `TIMESTAMP`). It *should not* make sense to modify their data after their creation.
+
+There are only two accessible constructors:
+`AbstractTimelineMarker(const char*, MarkerTracingType)`
+`AbstractTimelineMarker(const char*, const TimeStamp&, MarkerTracingType)`
+which create a marker with a name and a tracing type. If unspecified, the corresponding timestamp will be the current instantiation time. Instantiating a marker *much later* after a particular operation is possible, but be careful providing the correct timestamp.
+
+The `AddDetails` virtual method should be implemented by subclasses when creating WebIDL versions of these markers, which will be sent over to a JavaScript frontend.
+
+##TimelineMarker
+A `TimelineMarker` is the main `AbstractTimelineMarker` implementation. They allow attaching a JavaScript stack on `START` and `TIMESTAMP` markers.
+
+These markers will be created when using the `TimelineConsumers` helper methods which take in a string, a tracing type and (optionally) a timestamp. For more complex markers, subclasses are encouraged. See `EventTimelineMarker` or `ConsoleTimelineMarker` for some examples.
+
+##RAII
+
+### mozilla::AutoTimelineMarker
+
+The easiest way to trace Gecko events/tasks with start and end timeline markers is to use the `mozilla::AutoTimelineMarker` RAII class. It automatically adds the start marker on construction, and adds the end marker on destruction. Don't worry too much about potential performance impact! It only actually adds the markers when the given docshell is being observed by a timeline consumer, so essentially nothing will happen if a tool to inspect those markers isn't specifically open.
+
+This class may only be used on the main thread, and pointer to a docshell is necessary. If the docshell is a nullptr, nothing happens and this operation fails silently.
+
+Example: `AutoTimelineMarker marker(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");`
+
+### mozilla::AutoGlobalTimelineMarker`
+
+Similar to the previous RAII class, but doesn't expect a specific docshell, and the marker will be visible in all timeline consumers. This is useful for generic operations that don't involve a particular docshell, or where a docshell isn't accessible. May also only be used on the main thread.
+
+Example: `AutoGlobalTimelineMarker marker("Some global operation");`
diff --git a/docshell/build/components.conf b/docshell/build/components.conf
new file mode 100644
index 0000000000..c92e5ec317
--- /dev/null
+++ b/docshell/build/components.conf
@@ -0,0 +1,190 @@
+# -*- 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/.
+
+about_pages = [
+ 'about',
+ 'addons',
+ 'buildconfig',
+ 'certificate',
+ 'checkerboard',
+ 'crashcontent',
+ 'crashparent',
+ 'credits',
+ 'httpsonlyerror',
+ 'license',
+ 'logo',
+ 'memory',
+ 'mozilla',
+ 'neterror',
+ 'networking',
+ 'performance',
+ 'plugins',
+ 'printpreview',
+ 'processes',
+ 'serviceworkers',
+ 'srcdoc',
+ 'support',
+ 'telemetry',
+ 'url-classifier',
+ 'webrtc',
+]
+
+if buildconfig.substs['MOZ_BUILD_APP'] != 'browser':
+ about_pages.append('config')
+if defined('MOZ_CRASHREPORTER'):
+ about_pages.append('crashes')
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'android':
+ about_pages.append('profiles')
+if defined('MOZ_GLEAN'):
+ about_pages.append('glean')
+
+Headers = ['/docshell/build/nsDocShellModule.h']
+
+InitFunc = 'mozilla::InitDocShellModule'
+UnloadFunc = 'mozilla::UnloadDocShellModule'
+
+Classes = [
+ {
+ 'name': 'DocLoader',
+ 'cid': '{057b04d0-0ccf-11d2-beba-00805f8a66dc}',
+ 'contract_ids': ['@mozilla.org/docloaderservice;1'],
+ 'type': 'nsDocLoader',
+ 'headers': ['nsDocLoader.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'name': 'URIFixup',
+ 'js_name': 'uriFixup',
+ 'cid': '{c6cf88b7-452e-47eb-bdc9-86e3561648ef}',
+ 'contract_ids': ['@mozilla.org/docshell/uri-fixup;1'],
+ 'jsm': 'resource://gre/modules/URIFixup.jsm',
+ 'constructor': 'URIFixup',
+ },
+ {
+ 'cid': '{33d75835-722f-42c0-89cc-44f328e56a86}',
+ 'contract_ids': ['@mozilla.org/docshell/uri-fixup-info;1'],
+ 'jsm': 'resource://gre/modules/URIFixup.jsm',
+ 'constructor': 'URIFixupInfo',
+ },
+ {
+ 'cid': '{56ebedd4-6ccf-48e8-bdae-adc77f044567}',
+ 'contract_ids': [
+ '@mozilla.org/network/protocol/about;1?what=%s' % path
+ for path in about_pages
+ ],
+ 'legacy_constructor': 'nsAboutRedirector::Create',
+ 'headers': ['/docshell/base/nsAboutRedirector.h'],
+ },
+ {
+ 'cid': '{bd6390c8-fbea-11d4-98f6-001083010e9b}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=default'],
+ 'type': 'nsExternalProtocolHandler',
+ 'headers': ['/uriloader/exthandler/nsExternalProtocolHandler.h'],
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'name': 'OfflineCacheUpdate',
+ 'cid': '{ec06f3fc-70db-4ecd-94e0-a6e91ca44d8a}',
+ 'contract_ids': ['@mozilla.org/offlinecacheupdate-service;1'],
+ 'singleton': True,
+ 'type': 'nsOfflineCacheUpdateService',
+ 'headers': ['/uriloader/prefetch/nsOfflineCacheUpdate.h'],
+ 'constructor': 'nsOfflineCacheUpdateService::GetInstance',
+ },
+ {
+ 'cid': '{95790842-75a0-430d-98bf-f5ce3788ea6d}',
+ 'contract_ids': ['@mozilla.org/ospermissionrequest;1'],
+ 'type': 'nsOSPermissionRequest',
+ 'headers': ['nsOSPermissionRequest.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'name': 'Prefetch',
+ 'cid': '{6b8bdffc-3394-417d-be83-a81b7c0f63bf}',
+ 'contract_ids': ['@mozilla.org/prefetch-service;1'],
+ 'type': 'nsPrefetchService',
+ 'headers': ['/uriloader/prefetch/nsPrefetchService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{c4b6fb7c-bfb1-49dc-a65f-035796524b53}',
+ 'contract_ids': ['@mozilla.org/uriloader/handler-service;1'],
+ 'type': 'mozilla::dom::ContentHandlerService',
+ 'headers': ['ContentHandlerService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.CONTENT_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{bc0017e3-2438-47be-a567-41db58f17627}',
+ 'contract_ids': ['@mozilla.org/uriloader/local-handler-app;1'],
+ 'type': 'PlatformLocalHandlerApp_t',
+ 'headers': ['/uriloader/exthandler/nsLocalHandlerApp.h'],
+ },
+ {
+ 'name': 'URILoader',
+ 'cid': '{9f6d5d40-90e7-11d3-af80-00a024ffc08c}',
+ 'contract_ids': ['@mozilla.org/uriloader;1'],
+ 'type': 'nsURILoader',
+ 'headers': ['nsURILoader.h'],
+ },
+ {
+ 'cid': '{f30bc0a2-958b-4287-bf62-ce38ba0c811e}',
+ 'contract_ids': ['@mozilla.org/webnavigation-info;1'],
+ 'type': 'nsWebNavigationInfo',
+ 'headers': ['/docshell/base/nsWebNavigationInfo.h'],
+ },
+]
+
+if defined('MOZ_ENABLE_DBUS'):
+ Classes += [
+ {
+ 'name': 'DBusHandlerApp',
+ 'cid': '{6c3c274b-4cbf-4bb5-a635-05ad2cbb6535}',
+ 'contract_ids': ['@mozilla.org/uriloader/dbus-handler-app;1'],
+ 'type': 'nsDBusHandlerApp',
+ 'headers': ['/uriloader/exthandler/nsDBusHandlerApp.h'],
+ },
+ ]
+
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'android':
+ Classes += [
+ {
+ 'cid': '{4bf1f8ef-d947-4ba3-9cd3-8c9a54a63a1c}',
+ 'contract_ids': ['@mozilla.org/uriloader/external-url-handler-service;1'],
+ 'type': 'nsExternalURLHandlerService',
+ 'headers': ['nsExternalURLHandlerService.h'],
+ },
+ # Android has its own externel-helper-app-service, so we omit
+ # that here for nsExternalHelperAppService.
+ {
+ 'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+ 'contract_ids': [
+ '@mozilla.org/mime;1',
+ '@mozilla.org/uriloader/external-protocol-service;1',
+ ],
+ 'type': 'nsExternalHelperAppService',
+ 'constructor': 'nsExternalHelperAppService::GetSingleton',
+ 'headers': ['nsExternalHelperAppService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ ]
+else:
+ Classes += [
+ {
+ 'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+ 'contract_ids': [
+ '@mozilla.org/mime;1',
+ '@mozilla.org/uriloader/external-helper-app-service;1',
+ '@mozilla.org/uriloader/external-protocol-service;1',
+ ],
+ 'type': 'nsExternalHelperAppService',
+ 'constructor': 'nsExternalHelperAppService::GetSingleton',
+ 'headers': ['nsExternalHelperAppService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ ]
diff --git a/docshell/build/moz.build b/docshell/build/moz.build
new file mode 100644
index 0000000000..d9fd81848e
--- /dev/null
+++ b/docshell/build/moz.build
@@ -0,0 +1,25 @@
+# -*- 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/.
+
+EXPORTS += [
+ "nsDocShellCID.h",
+]
+
+SOURCES += [
+ "nsDocShellModule.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/shistory",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/docshell/build/nsDocShellCID.h b/docshell/build/nsDocShellCID.h
new file mode 100644
index 0000000000..ad7869eb4a
--- /dev/null
+++ b/docshell/build/nsDocShellCID.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 nsDocShellCID_h__
+#define nsDocShellCID_h__
+
+/**
+ * A contract that can be used to get a service that provides
+ * meta-information about nsIWebNavigation objects' capabilities.
+ * @implements nsIWebNavigationInfo
+ */
+#define NS_WEBNAVIGATION_INFO_CONTRACTID "@mozilla.org/webnavigation-info;1"
+
+/**
+ * Contract ID to obtain the IHistory interface. This is a non-scriptable
+ * interface used to interact with history in an asynchronous manner.
+ */
+#define NS_IHISTORY_CONTRACTID "@mozilla.org/browser/history;1"
+
+/**
+ * A contract for a service that is used for finding
+ * platform-specific applications for handling particular URLs.
+ *
+ * @implements nsIExternalURLHandlerService
+ */
+#define NS_EXTERNALURLHANDLERSERVICE_CONTRACTID \
+ "@mozilla.org/uriloader/external-url-handler-service;1"
+
+/**
+ * An observer service topic that can be listened to to catch creation
+ * of content browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * created. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_WEBNAVIGATION_CREATE "webnavigation-create"
+
+/**
+ * An observer service topic that can be listened to to catch creation
+ * of chrome browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * created. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_CHROME_WEBNAVIGATION_CREATE "chrome-webnavigation-create"
+
+/**
+ * An observer service topic that can be listened to to catch destruction
+ * of content browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * destroyed. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_WEBNAVIGATION_DESTROY "webnavigation-destroy"
+
+/**
+ * An observer service topic that can be listened to to catch destruction
+ * of chrome browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * destroyed. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_CHROME_WEBNAVIGATION_DESTROY "chrome-webnavigation-destroy"
+
+#endif // nsDocShellCID_h__
diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp
new file mode 100644
index 0000000000..8602497a57
--- /dev/null
+++ b/docshell/build/nsDocShellModule.cpp
@@ -0,0 +1,25 @@
+/* -*- 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 "mozilla/dom/BrowsingContextGroup.h"
+
+// session history
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+
+namespace mozilla {
+
+// The one time initialization for this module
+nsresult InitDocShellModule() {
+ mozilla::dom::BrowsingContext::Init();
+
+ return NS_OK;
+}
+
+void UnloadDocShellModule() { nsSHistory::Shutdown(); }
+
+} // namespace mozilla
diff --git a/docshell/build/nsDocShellModule.h b/docshell/build/nsDocShellModule.h
new file mode 100644
index 0000000000..c64f3ad8a9
--- /dev/null
+++ b/docshell/build/nsDocShellModule.h
@@ -0,0 +1,20 @@
+/* -*- 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 nsDocShellModule_h
+#define nsDocShellModule_h
+
+#include "nscore.h"
+
+namespace mozilla {
+
+nsresult InitDocShellModule();
+
+void UnloadDocShellModule();
+
+} // namespace mozilla
+
+#endif
diff --git a/docshell/moz.build b/docshell/moz.build
new file mode 100644
index 0000000000..57ae14f517
--- /dev/null
+++ b/docshell/moz.build
@@ -0,0 +1,56 @@
+# -*- 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")
+
+if CONFIG["MOZ_BUILD_APP"] == "browser":
+ DEFINES["MOZ_BUILD_APP_IS_BROWSER"] = True
+
+DIRS += [
+ "base",
+ "shistory",
+ "build",
+ "resources/content",
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "test/unit/xpcshell.ini",
+ "test/unit_ipc/xpcshell.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "test/iframesandbox/mochitest.ini",
+ "test/mochitest/mochitest.ini",
+ "test/navigation/mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "test/chrome/chrome.ini",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.ini",
+ "test/navigation/browser.ini",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.docshell.test.chrome += [
+ "test/chrome/112564_nocache.html",
+ "test/chrome/112564_nocache.html^headers^",
+ "test/chrome/215405_nocache.html",
+ "test/chrome/215405_nocache.html^headers^",
+ "test/chrome/215405_nostore.html",
+ "test/chrome/215405_nostore.html^headers^",
+ "test/chrome/582176_dummy.html",
+ "test/chrome/582176_xml.xml",
+ "test/chrome/582176_xslt.xsl",
+ "test/chrome/92598_nostore.html",
+ "test/chrome/92598_nostore.html^headers^",
+ "test/chrome/allowContentRetargeting.sjs",
+ "test/chrome/blue.png",
+ "test/chrome/bug89419.sjs",
+ "test/chrome/red.png",
+]
diff --git a/docshell/resources/content/jar.mn b/docshell/resources/content/jar.mn
new file mode 100644
index 0000000000..5b9ae94fca
--- /dev/null
+++ b/docshell/resources/content/jar.mn
@@ -0,0 +1,7 @@
+# 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/.
+
+toolkit.jar:
+ content/global/netError.xhtml
+ content/global/netError.js
diff --git a/docshell/resources/content/moz.build b/docshell/resources/content/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/docshell/resources/content/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/docshell/resources/content/netError.js b/docshell/resources/content/netError.js
new file mode 100644
index 0000000000..a8569c5ce0
--- /dev/null
+++ b/docshell/resources/content/netError.js
@@ -0,0 +1,286 @@
+/* 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/. */
+
+// Error url MUST be formatted like this:
+// moz-neterror:page?e=error&u=url&d=desc
+//
+// or optionally, to specify an alternate CSS class to allow for
+// custom styling and favicon:
+//
+// moz-neterror:page?e=error&u=url&s=classname&d=desc
+
+// Note that this file uses document.documentURI to get
+// the URL (with the format from above). This is because
+// document.location.href gets the current URI off the docshell,
+// which is the URL displayed in the location bar, i.e.
+// the URI that the user attempted to load.
+
+function getErrorCode() {
+ var url = document.documentURI;
+ var error = url.search(/e\=/);
+ var duffUrl = url.search(/\&u\=/);
+ return decodeURIComponent(url.slice(error + 2, duffUrl));
+}
+
+function getCSSClass() {
+ var url = document.documentURI;
+ var matches = url.match(/s\=([^&]+)\&/);
+ // s is optional, if no match just return nothing
+ if (!matches || matches.length < 2) {
+ return "";
+ }
+
+ // parenthetical match is the second entry
+ return decodeURIComponent(matches[1]);
+}
+
+function getDescription() {
+ var url = document.documentURI;
+ var desc = url.search(/d\=/);
+
+ // desc == -1 if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (desc == -1) {
+ return "";
+ }
+
+ return decodeURIComponent(url.slice(desc + 2));
+}
+
+function retryThis(buttonEl) {
+ // Note: The application may wish to handle switching off "offline mode"
+ // before this event handler runs, but using a capturing event handler.
+
+ // Session history has the URL of the page that failed
+ // to load, not the one of the error page. So, just call
+ // reload(), which will also repost POST data correctly.
+ try {
+ location.reload();
+ } catch (e) {
+ // We probably tried to reload a URI that caused an exception to
+ // occur; e.g. a nonexistent file.
+ }
+
+ buttonEl.disabled = true;
+}
+
+function initPage() {
+ var err = getErrorCode();
+
+ // if it's an unknown error or there's no title or description
+ // defined, get the generic message
+ var errTitle = document.getElementById("et_" + err);
+ var errDesc = document.getElementById("ed_" + err);
+ if (!errTitle || !errDesc) {
+ errTitle = document.getElementById("et_generic");
+ errDesc = document.getElementById("ed_generic");
+ }
+
+ var title = document.getElementById("errorTitleText");
+ if (title) {
+ title.parentNode.replaceChild(errTitle, title);
+ // change id to the replaced child's id so styling works
+ errTitle.id = "errorTitleText";
+ }
+
+ var sd = document.getElementById("errorShortDescText");
+ if (sd) {
+ sd.textContent = getDescription();
+ }
+
+ var ld = document.getElementById("errorLongDesc");
+ if (ld) {
+ ld.parentNode.replaceChild(errDesc, ld);
+ // change id to the replaced child's id so styling works
+ errDesc.id = "errorLongDesc";
+ }
+
+ // remove undisplayed errors to avoid bug 39098
+ var errContainer = document.getElementById("errorContainer");
+ errContainer.remove();
+
+ var className = getCSSClass();
+ if (className && className != "expertBadCert") {
+ // Associate a CSS class with the root of the page, if one was passed in,
+ // to allow custom styling.
+ // Not "expertBadCert" though, don't want to deal with the favicon
+ document.documentElement.className = className;
+
+ // Also, if they specified a CSS class, they must supply their own
+ // favicon. In order to trigger the browser to repaint though, we
+ // need to remove/add the link element.
+ var favicon = document.getElementById("favicon");
+ var faviconParent = favicon.parentNode;
+ faviconParent.removeChild(favicon);
+ favicon.setAttribute(
+ "href",
+ "chrome://global/skin/icons/" + className + "_favicon.png"
+ );
+ faviconParent.appendChild(favicon);
+ }
+ if (className == "expertBadCert") {
+ showSecuritySection();
+ }
+
+ if (err == "remoteXUL") {
+ // Remove the "Try again" button for remote XUL errors given that
+ // it is useless.
+ document.getElementById("errorTryAgain").style.display = "none";
+ }
+
+ if (err == "cspBlocked" || err == "xfoBlocked") {
+ // Remove the "Try again" button for XFO and CSP violations, since it's
+ // almost certainly useless. (Bug 553180)
+ document.getElementById("errorTryAgain").style.display = "none";
+ }
+
+ if (err == "nssBadCert") {
+ // Remove the "Try again" button for security exceptions, since it's
+ // almost certainly useless.
+ document.getElementById("errorTryAgain").style.display = "none";
+ document
+ .getElementById("errorPageContainer")
+ .setAttribute("class", "certerror");
+ addDomainErrorLink();
+ } else {
+ // Remove the override block for non-certificate errors. CSS-hiding
+ // isn't good enough here, because of bug 39098
+ var secOverride = document.getElementById("securityOverrideDiv");
+ secOverride.remove();
+ }
+
+ if (err == "inadequateSecurityError" || err == "blockedByPolicy") {
+ // Remove the "Try again" button from pages that don't need it.
+ // For HTTP/2 inadequate security or pages blocked by policy, trying
+ // again won't help.
+ document.getElementById("errorTryAgain").style.display = "none";
+
+ var container = document.getElementById("errorLongDesc");
+ for (var span of container.querySelectorAll("span.hostname")) {
+ span.textContent = document.location.hostname;
+ }
+ }
+
+ if (document.getElementById("errorTryAgain").style.display != "none") {
+ addAutofocus("errorTryAgain");
+ }
+}
+
+function showSecuritySection() {
+ // Swap link out, content in
+ document.getElementById("securityOverrideContent").style.display = "";
+ document.getElementById("securityOverrideLink").style.display = "none";
+}
+
+/* In the case of SSL error pages about domain mismatch, see if
+ we can hyperlink the user to the correct site. We don't want
+ to do this generically since it allows MitM attacks to redirect
+ users to a site under attacker control, but in certain cases
+ it is safe (and helpful!) to do so. Bug 402210
+ */
+function addDomainErrorLink() {
+ // Rather than textContent, we need to treat description as HTML
+ var sd = document.getElementById("errorShortDescText");
+ if (sd) {
+ var desc = getDescription();
+
+ // sanitize description text - see bug 441169
+
+ // First, find the index of the <a> tag we care about, being careful not to
+ // use an over-greedy regex
+ var re = /<a id="cert_domain_link" title="([^"]+)">/;
+ var result = re.exec(desc);
+ if (!result) {
+ return;
+ }
+
+ // Remove sd's existing children
+ sd.textContent = "";
+
+ // Everything up to the link should be text content
+ sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
+
+ // Now create the link itself
+ var anchorEl = document.createElement("a");
+ anchorEl.setAttribute("id", "cert_domain_link");
+ anchorEl.setAttribute("title", result[1]);
+ anchorEl.appendChild(document.createTextNode(result[1]));
+ sd.appendChild(anchorEl);
+
+ // Finally, append text for anything after the closing </a>
+ sd.appendChild(
+ document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length))
+ );
+ }
+
+ var link = document.getElementById("cert_domain_link");
+ if (!link) {
+ return;
+ }
+
+ var okHost = link.getAttribute("title");
+ var thisHost = document.location.hostname;
+ var proto = document.location.protocol;
+
+ // If okHost is a wildcard domain ("*.example.com") let's
+ // use "www" instead. "*.example.com" isn't going to
+ // get anyone anywhere useful. bug 432491
+ okHost = okHost.replace(/^\*\./, "www.");
+
+ /* case #1:
+ * example.com uses an invalid security certificate.
+ *
+ * The certificate is only valid for www.example.com
+ *
+ * Make sure to include the "." ahead of thisHost so that
+ * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
+ *
+ * We'd normally just use a RegExp here except that we lack a
+ * library function to escape them properly (bug 248062), and
+ * domain names are famous for having '.' characters in them,
+ * which would allow spurious and possibly hostile matches.
+ */
+ if (endsWith(okHost, "." + thisHost)) {
+ link.href = proto + okHost;
+ }
+
+ /* case #2:
+ * browser.garage.maemo.org uses an invalid security certificate.
+ *
+ * The certificate is only valid for garage.maemo.org
+ */
+ if (endsWith(thisHost, "." + okHost)) {
+ link.href = proto + okHost;
+ }
+}
+
+function endsWith(haystack, needle) {
+ return haystack.slice(-needle.length) == needle;
+}
+
+/* Only do autofocus if we're the toplevel frame; otherwise we
+ don't want to call attention to ourselves! The key part is
+ that autofocus happens on insertion into the tree, so we
+ can remove the button, add @autofocus, and reinsert the
+ button.
+ */
+function addAutofocus(buttonId, position = "afterbegin") {
+ if (window.top == window) {
+ var button = document.getElementById(buttonId);
+ var parent = button.parentNode;
+ button.remove();
+ button.setAttribute("autofocus", "true");
+ parent.insertAdjacentElement(position, button);
+ }
+}
+
+let errorTryAgain = document.getElementById("errorTryAgain");
+errorTryAgain.addEventListener("click", function() {
+ retryThis(this);
+});
+
+// Note: It is important to run the script this way, instead of using
+// an onload handler. This is because error pages are loaded as
+// LOAD_BACKGROUND, which means that onload handlers will not be executed.
+initPage();
diff --git a/docshell/resources/content/netError.xhtml b/docshell/resources/content/netError.xhtml
new file mode 100644
index 0000000000..48b263d8c8
--- /dev/null
+++ b/docshell/resources/content/netError.xhtml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorAppDTD
+ SYSTEM "chrome://global/locale/netErrorApp.dtd">
+ %netErrorAppDTD;
+ <!ENTITY % netErrorDTD
+ SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
+ <title>&loadError.label;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
+ toolkit/components/places/src/nsFaviconService.h should be updated. -->
+ <link rel="icon" id="favicon" href="chrome://global/skin/icons/warning.svg"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
+ <div id="errorContainer">
+ <div id="errorTitlesContainer">
+ <h1 id="et_generic">&generic.title;</h1>
+ <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
+ <h1 id="et_fileNotFound">&fileNotFound.title;</h1>
+ <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
+ <h1 id="et_malformedURI">&malformedURI.title;</h1>
+ <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
+ <h1 id="et_connectionFailure">&connectionFailure.title;</h1>
+ <h1 id="et_netTimeout">&netTimeout.title;</h1>
+ <h1 id="et_redirectLoop">&redirectLoop.title;</h1>
+ <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
+ <h1 id="et_netReset">&netReset.title;</h1>
+ <h1 id="et_notCached">&notCached.title;</h1>
+ <h1 id="et_netOffline">&netOffline.title;</h1>
+ <h1 id="et_netInterrupt">&netInterrupt.title;</h1>
+ <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
+ <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
+ <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
+ <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
+ <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
+ <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
+ <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
+ <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
+ <h1 id="et_xfoBlocked">&xfoBlocked.title;</h1>
+ <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
+ <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
+ <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
+ <h1 id="et_blockedByPolicy">&blockedByPolicy.title;</h1>
+ <h1 id="et_networkProtocolError">&networkProtocolError.title;</h1>
+ </div>
+ <div id="errorDescriptionsContainer">
+ <div id="ed_generic">&generic.longDesc;</div>
+ <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
+ <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
+ <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
+ <div id="ed_malformedURI">&malformedURI.longDesc;</div>
+ <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
+ <div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
+ <div id="ed_netTimeout">&netTimeout.longDesc;</div>
+ <div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
+ <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
+ <div id="ed_netReset">&netReset.longDesc;</div>
+ <div id="ed_notCached">&notCached.longDesc;</div>
+ <div id="ed_netOffline">&netOffline.longDesc2;</div>
+ <div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
+ <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
+ <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
+ <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
+ <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
+ <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
+ <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
+ <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
+ <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
+ <div id="ed_xfoBlocked">&xfoBlocked.longDesc;</div>
+ <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
+ <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
+ <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
+ <div id="ed_blockedByPolicy"></div>
+ <div id="ed_networkProtocolError">&networkProtocolError.longDesc;</div>
+ </div>
+ </div>
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText" />
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText" />
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc" />
+
+ <!-- Override section - For ssl errors only. Removed on init for other
+ error types. -->
+ <div id="securityOverrideDiv">
+ <a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
+ <div id="securityOverrideContent">&securityOverride.warningContent;</div>
+ </div>
+ </div>
+
+ <!-- Retry Button -->
+ <button id="errorTryAgain" autocomplete="off">&retry.label;</button>
+ </div>
+
+ <script src="chrome://global/content/netError.js"/>
+ </body>
+</html>
diff --git a/docshell/shistory/ChildSHistory.cpp b/docshell/shistory/ChildSHistory.cpp
new file mode 100644
index 0000000000..de4f4c6187
--- /dev/null
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -0,0 +1,298 @@
+/* -*- 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/ChildSHistory.h"
+#include "mozilla/dom/ChildSHistoryBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "nsIXULRuntime.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSHEntry.h"
+#include "nsSHistory.h"
+#include "nsDocShell.h"
+#include "nsXULAppAPI.h"
+
+extern mozilla::LazyLogModule gSHLog;
+
+namespace mozilla {
+namespace dom {
+
+ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext) {}
+
+void ChildSHistory::SetBrowsingContext(BrowsingContext* aBrowsingContext) {
+ mBrowsingContext = aBrowsingContext;
+}
+
+void ChildSHistory::SetIsInProcess(bool aIsInProcess) {
+ if (!aIsInProcess) {
+ mHistory = nullptr;
+
+ return;
+ }
+
+ if (mHistory || mozilla::SessionHistoryInParent()) {
+ return;
+ }
+
+ mHistory = new nsSHistory(mBrowsingContext);
+}
+
+int32_t ChildSHistory::Count() {
+ if (mozilla::SessionHistoryInParent() || mAsyncHistoryLength) {
+ uint32_t length = mLength;
+ for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) {
+ length += mPendingSHistoryChanges[i].mLengthDelta;
+ }
+
+ if (mAsyncHistoryLength) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ // XXX The assertion may be too strong here, but it fires only
+ // when the pref is enabled.
+ MOZ_ASSERT(mHistory->GetCount() == int32_t(length));
+ }
+ return length;
+ }
+ return mHistory->GetCount();
+}
+
+int32_t ChildSHistory::Index() {
+ if (mozilla::SessionHistoryInParent() || mAsyncHistoryLength) {
+ uint32_t index = mIndex;
+ for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) {
+ index += mPendingSHistoryChanges[i].mIndexDelta;
+ }
+
+ if (mAsyncHistoryLength) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ int32_t realIndex;
+ mHistory->GetIndex(&realIndex);
+ // XXX The assertion may be too strong here, but it fires only
+ // when the pref is enabled.
+ MOZ_ASSERT(realIndex == int32_t(index));
+ }
+ return index;
+ }
+ int32_t index;
+ mHistory->GetIndex(&index);
+ return index;
+}
+
+nsID ChildSHistory::AddPendingHistoryChange() {
+ int32_t indexDelta = 1;
+ int32_t lengthDelta = (Index() + indexDelta) - (Count() - 1);
+ return AddPendingHistoryChange(indexDelta, lengthDelta);
+}
+
+nsID ChildSHistory::AddPendingHistoryChange(int32_t aIndexDelta,
+ int32_t aLengthDelta) {
+ nsID changeID = {};
+ nsContentUtils::GenerateUUIDInPlace(changeID);
+ PendingSHistoryChange change = {changeID, aIndexDelta, aLengthDelta};
+ mPendingSHistoryChanges.AppendElement(change);
+ return changeID;
+}
+
+void ChildSHistory::SetIndexAndLength(uint32_t aIndex, uint32_t aLength,
+ const nsID& aChangeID) {
+ mIndex = aIndex;
+ mLength = aLength;
+ mPendingSHistoryChanges.RemoveElementsBy(
+ [aChangeID](const PendingSHistoryChange& aChange) {
+ return aChange.mChangeID == aChangeID;
+ });
+}
+
+void ChildSHistory::Reload(uint32_t aReloadFlags, ErrorResult& aRv) {
+ if (mozilla::SessionHistoryInParent()) {
+ if (XRE_IsParentProcess()) {
+ nsISHistory* shistory =
+ mBrowsingContext->Canonical()->GetSessionHistory();
+ if (shistory) {
+ aRv = shistory->Reload(aReloadFlags);
+ }
+ } else {
+ ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext,
+ aReloadFlags);
+ }
+
+ return;
+ }
+ aRv = mHistory->Reload(aReloadFlags);
+}
+
+bool ChildSHistory::CanGo(int32_t aOffset) {
+ CheckedInt<int32_t> index = Index();
+ index += aOffset;
+ if (!index.isValid()) {
+ return false;
+ }
+ return index.value() < Count() && index.value() >= 0;
+}
+
+void ChildSHistory::Go(int32_t aOffset, bool aRequireUserInteraction,
+ ErrorResult& aRv) {
+ CheckedInt<int32_t> index = Index();
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("ChildSHistory::Go(%d), current index = %d", aOffset, index.value()));
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ while (true) {
+ index += aOffset;
+ if (!index.isValid()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Check for user interaction if desired, except for the first and last
+ // history entries. We compare with >= to account for the case where
+ // aOffset >= Count().
+ if (!aRequireUserInteraction || index.value() >= Count() - 1 ||
+ index.value() <= 0) {
+ break;
+ }
+ if (mHistory && mHistory->HasUserInteractionAtIndex(index.value())) {
+ break;
+ }
+ }
+
+ GotoIndex(index.value(), aOffset, aRequireUserInteraction, aRv);
+}
+
+void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction,
+ CallerType aCallerType, ErrorResult& aRv) {
+ CheckedInt<int32_t> index = Index();
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::AsyncGo(%d), current index = %d", aOffset,
+ index.value()));
+ nsresult rv = mBrowsingContext->CheckLocationChangeRateLimit(aCallerType);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Rejected"));
+ aRv.Throw(rv);
+ return;
+ }
+
+ RefPtr<PendingAsyncHistoryNavigation> asyncNav =
+ new PendingAsyncHistoryNavigation(this, aOffset, aRequireUserInteraction);
+ mPendingNavigations.insertBack(asyncNav);
+ NS_DispatchToCurrentThread(asyncNav.forget());
+}
+
+void ChildSHistory::GotoIndex(int32_t aIndex, int32_t aOffset,
+ bool aRequireUserInteraction, ErrorResult& aRv) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::GotoIndex(%d, %d), epoch %" PRIu64, aIndex, aOffset,
+ mHistoryEpoch));
+ if (mozilla::SessionHistoryInParent()) {
+ if (!mPendingEpoch) {
+ mPendingEpoch = true;
+ RefPtr<ChildSHistory> self(this);
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("UpdateEpochRunnable", [self] {
+ self->mHistoryEpoch++;
+ self->mPendingEpoch = false;
+ }));
+ }
+
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ mBrowsingContext->HistoryGo(
+ aOffset, mHistoryEpoch, aRequireUserInteraction,
+ [shistory](int32_t&& aRequestedIndex) {
+ // FIXME Should probably only do this for non-fission.
+ if (shistory) {
+ shistory->InternalSetRequestedIndex(aRequestedIndex);
+ }
+ });
+ } else {
+ aRv = mHistory->GotoIndex(aIndex);
+ }
+}
+
+void ChildSHistory::RemovePendingHistoryNavigations() {
+ // Per the spec, this generally shouldn't remove all navigations - it
+ // depends if they're in the same document family or not. We don't do
+ // that. Also with SessionHistoryInParent, this can only abort AsyncGo's
+ // that have not yet been sent to the parent - see discussion of point
+ // 2.2 in comments in nsDocShell::UpdateURLAndHistory()
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::RemovePendingHistoryNavigations: %zu",
+ mPendingNavigations.length()));
+ mPendingNavigations.clear();
+}
+
+void ChildSHistory::EvictLocalContentViewers() {
+ if (!mozilla::SessionHistoryInParent()) {
+ mHistory->EvictAllContentViewers();
+ }
+}
+
+nsISHistory* ChildSHistory::GetLegacySHistory(ErrorResult& aError) {
+ if (mozilla::SessionHistoryInParent()) {
+ aError.ThrowTypeError(
+ "legacySHistory is not available with session history in the parent.");
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(mHistory);
+ return mHistory;
+}
+
+nsISHistory* ChildSHistory::LegacySHistory() {
+ IgnoredErrorResult ignore;
+ nsISHistory* shistory = GetLegacySHistory(ignore);
+ MOZ_RELEASE_ASSERT(shistory);
+ return shistory;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ChildSHistory, mBrowsingContext, mHistory)
+
+JSObject* ChildSHistory::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ChildSHistory_Binding::Wrap(cx, this, aGivenProto);
+}
+
+nsISupports* ChildSHistory::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+void ChildSHistory::SetAsyncHistoryLength(bool aEnable, ErrorResult& aRv) {
+ if (mozilla::SessionHistoryInParent() || !mHistory) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mAsyncHistoryLength == aEnable) {
+ return;
+ }
+
+ mAsyncHistoryLength = aEnable;
+ if (mAsyncHistoryLength) {
+ mHistory->GetIndex(&mIndex);
+ mLength = mHistory->GetCount();
+ } else {
+ mIndex = -1;
+ mLength = 0;
+ mPendingSHistoryChanges.Clear();
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/shistory/ChildSHistory.h b/docshell/shistory/ChildSHistory.h
new file mode 100644
index 0000000000..43b2f63bd0
--- /dev/null
+++ b/docshell/shistory/ChildSHistory.h
@@ -0,0 +1,152 @@
+/* -*- 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/. */
+
+/**
+ * ChildSHistory represents a view of session history from a child process. It
+ * exposes getters for some cached history state, and mutators which are
+ * implemented by communicating with the actual history storage.
+ *
+ * NOTE: Currently session history is in transition, meaning that we're still
+ * using the legacy nsSHistory class internally. The API exposed from this class
+ * should be only the API which we expect to expose when this transition is
+ * complete, and special cases will need to call through the LegacySHistory()
+ * getters.
+ */
+
+#ifndef mozilla_dom_ChildSHistory_h
+#define mozilla_dom_ChildSHistory_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsWrapperCache.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "nsID.h"
+
+class nsISHEntry;
+class nsISHistory;
+
+namespace mozilla {
+namespace dom {
+
+class BrowsingContext;
+
+class ChildSHistory : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ChildSHistory)
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ explicit ChildSHistory(BrowsingContext* aBrowsingContext);
+
+ void SetBrowsingContext(BrowsingContext* aBrowsingContext);
+
+ // Create or destroy the session history implementation in the child process.
+ // This can be removed once session history is stored exclusively in the
+ // parent process.
+ void SetIsInProcess(bool aIsInProcess);
+ bool IsInProcess() { return !!mHistory; }
+
+ int32_t Count();
+ int32_t Index();
+
+ /**
+ * Reload the current entry in the session history.
+ */
+ void Reload(uint32_t aReloadFlags, ErrorResult& aRv);
+
+ /**
+ * The CanGo and Go methods are called with an offset from the current index.
+ * Positive numbers go forward in history, while negative numbers go
+ * backwards.
+ */
+ bool CanGo(int32_t aOffset);
+ void Go(int32_t aOffset, bool aRequireUserInteraction, ErrorResult& aRv);
+ void AsyncGo(int32_t aOffset, bool aRequireUserInteraction,
+ CallerType aCallerType, ErrorResult& aRv);
+
+ // aIndex is the new index, and aOffset is the offset between new and current.
+ void GotoIndex(int32_t aIndex, int32_t aOffset, bool aRequireUserInteraction,
+ ErrorResult& aRv);
+
+ void RemovePendingHistoryNavigations();
+
+ /**
+ * Evicts all content viewers within the current process.
+ */
+ void EvictLocalContentViewers();
+
+ // GetLegacySHistory and LegacySHistory have been deprecated. Don't
+ // use these, but instead handle the interaction with nsISHistory in
+ // the parent process.
+ nsISHistory* GetLegacySHistory(ErrorResult& aError);
+ nsISHistory* LegacySHistory();
+
+ void SetIndexAndLength(uint32_t aIndex, uint32_t aLength,
+ const nsID& aChangeId);
+ nsID AddPendingHistoryChange();
+ nsID AddPendingHistoryChange(int32_t aIndexDelta, int32_t aLengthDelta);
+
+ // AsyncHistoryLength is for testing.
+ void SetAsyncHistoryLength(bool aEnable, ErrorResult& aRv);
+ bool AsyncHistoryLength() { return mAsyncHistoryLength; }
+
+ private:
+ virtual ~ChildSHistory() = default;
+
+ class PendingAsyncHistoryNavigation
+ : public Runnable,
+ public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> {
+ public:
+ PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset,
+ bool aRequireUserInteraction)
+ : Runnable("PendingAsyncHistoryNavigation"),
+ mHistory(aHistory),
+ mRequireUserInteraction(aRequireUserInteraction),
+ mOffset(aOffset) {}
+
+ NS_IMETHOD Run() override {
+ if (isInList()) {
+ remove();
+ mHistory->Go(mOffset, mRequireUserInteraction, IgnoreErrors());
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ChildSHistory> mHistory;
+ bool mRequireUserInteraction;
+ int32_t mOffset;
+ };
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+ nsCOMPtr<nsISHistory> mHistory;
+ // Can be removed once history-in-parent is the only way
+ mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
+ int32_t mIndex = -1;
+ int32_t mLength = 0;
+
+ struct PendingSHistoryChange {
+ nsID mChangeID;
+ int32_t mIndexDelta;
+ int32_t mLengthDelta;
+ };
+ AutoTArray<PendingSHistoryChange, 2> mPendingSHistoryChanges;
+
+ bool mAsyncHistoryLength = false;
+
+ // Needs to start 1 above default epoch in parent
+ uint64_t mHistoryEpoch = 1;
+ bool mPendingEpoch = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_ChildSHistory_h */
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
new file mode 100644
index 0000000000..7989d6fbe6
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -0,0 +1,1588 @@
+/* -*- 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 "SessionHistoryEntry.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsIHttpChannel.h"
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/PresState.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/dom/CSPMessageUtils.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/ReferrerInfoUtils.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+
+extern mozilla::LazyLogModule gSHLog;
+
+namespace mozilla {
+namespace dom {
+
+SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState,
+ nsIChannel* aChannel)
+ : mURI(aLoadState->URI()),
+ mOriginalURI(aLoadState->OriginalURI()),
+ mResultPrincipalURI(aLoadState->ResultPrincipalURI()),
+ mPostData(aLoadState->PostDataStream()),
+ mLoadType(aLoadState->LoadType()),
+ mSrcdocData(aLoadState->SrcdocData()),
+ mBaseURI(aLoadState->BaseURI()),
+ mLoadReplace(aLoadState->LoadReplace()),
+ /* FIXME Should this be aLoadState->IsSrcdocLoad()? */
+ mIsSrcdocEntry(!aLoadState->SrcdocData().IsEmpty()),
+ mHasUserInteraction(false),
+ mSharedState(SharedState::Create(
+ aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(),
+ /* FIXME Is this correct? */
+ aLoadState->TypeHint())) {
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI)
+ : mURI(aURI), mSharedState(aSharedStateFrom.mSharedState) {
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType)
+ : mURI(aURI),
+ mSharedState(SharedState::Create(
+ aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType)) {
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ nsIChannel* aChannel, uint32_t aLoadType,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp) {
+ aChannel->GetURI(getter_AddRefs(mURI));
+ mLoadType = aLoadType;
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(mResultPrincipalURI));
+ loadInfo->GetTriggeringPrincipal(
+ getter_AddRefs(mSharedState.Get()->mTriggeringPrincipal));
+ loadInfo->GetPrincipalToInherit(
+ getter_AddRefs(mSharedState.Get()->mPrincipalToInherit));
+
+ mSharedState.Get()->mPartitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ mSharedState.Get()->mCsp = aCsp;
+ aChannel->GetContentType(mSharedState.Get()->mContentType);
+ aChannel->GetOriginalURI(getter_AddRefs(mOriginalURI));
+
+ uint32_t loadFlags;
+ aChannel->GetLoadFlags(&loadFlags);
+ mLoadReplace = !!(loadFlags & nsIChannel::LOAD_REPLACE);
+
+ MaybeUpdateTitleFromURI();
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+}
+
+void SessionHistoryInfo::Reset(nsIURI* aURI, const nsID& aDocShellID,
+ bool aDynamicCreation,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType) {
+ mURI = aURI;
+ mOriginalURI = nullptr;
+ mResultPrincipalURI = nullptr;
+ mReferrerInfo = nullptr;
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, mTitle);
+ }
+ mPostData = nullptr;
+ mLoadType = 0;
+ mScrollPositionX = 0;
+ mScrollPositionY = 0;
+ mStateData = nullptr;
+ mSrcdocData.Truncate();
+ mBaseURI = nullptr;
+ mLoadReplace = false;
+ mURIWasModified = false;
+ mIsSrcdocEntry = false;
+ mScrollRestorationIsManual = false;
+ mPersist = false;
+ mHasUserInteraction = false;
+
+ mSharedState.Get()->mTriggeringPrincipal = aTriggeringPrincipal;
+ mSharedState.Get()->mPrincipalToInherit = aPrincipalToInherit;
+ mSharedState.Get()->mPartitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ mSharedState.Get()->mCsp = aCsp;
+ mSharedState.Get()->mContentType = aContentType;
+ mSharedState.Get()->mLayoutHistoryState = nullptr;
+}
+
+void SessionHistoryInfo::MaybeUpdateTitleFromURI() {
+ if (mTitle.IsEmpty() && mURI) {
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ AppendUTF8toUTF16(spec, mTitle);
+ }
+ }
+}
+
+uint64_t SessionHistoryInfo::SharedId() const {
+ return mSharedState.Get()->mId;
+}
+
+nsILayoutHistoryState* SessionHistoryInfo::GetLayoutHistoryState() {
+ return mSharedState.Get()->mLayoutHistoryState;
+}
+
+void SessionHistoryInfo::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
+ mSharedState.Get()->mLayoutHistoryState = aState;
+}
+
+nsIPrincipal* SessionHistoryInfo::GetTriggeringPrincipal() const {
+ return mSharedState.Get()->mTriggeringPrincipal;
+}
+
+nsIPrincipal* SessionHistoryInfo::GetPrincipalToInherit() const {
+ return mSharedState.Get()->mPrincipalToInherit;
+}
+
+nsIPrincipal* SessionHistoryInfo::GetPartitionedPrincipalToInherit() const {
+ return mSharedState.Get()->mPartitionedPrincipalToInherit;
+}
+
+nsIContentSecurityPolicy* SessionHistoryInfo::GetCsp() const {
+ return mSharedState.Get()->mCsp;
+}
+
+uint32_t SessionHistoryInfo::GetCacheKey() const {
+ return mSharedState.Get()->mCacheKey;
+}
+
+void SessionHistoryInfo::SetCacheKey(uint32_t aCacheKey) {
+ mSharedState.Get()->mCacheKey = aCacheKey;
+}
+
+bool SessionHistoryInfo::IsSubFrame() const {
+ return mSharedState.Get()->mIsFrameNavigation;
+}
+
+void SessionHistoryInfo::FillLoadInfo(nsDocShellLoadState& aLoadState) const {
+ aLoadState.SetOriginalURI(mOriginalURI);
+ aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI));
+ aLoadState.SetLoadReplace(mLoadReplace);
+ aLoadState.SetPostDataStream(mPostData);
+ aLoadState.SetReferrerInfo(mReferrerInfo);
+
+ aLoadState.SetTypeHint(mSharedState.Get()->mContentType);
+ aLoadState.SetTriggeringPrincipal(mSharedState.Get()->mTriggeringPrincipal);
+ aLoadState.SetPrincipalToInherit(mSharedState.Get()->mPrincipalToInherit);
+ aLoadState.SetPartitionedPrincipalToInherit(
+ mSharedState.Get()->mPartitionedPrincipalToInherit);
+ aLoadState.SetCsp(mSharedState.Get()->mCsp);
+
+ // Do not inherit principal from document (security-critical!);
+ uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE;
+
+ // Passing nullptr as aSourceDocShell gives the same behaviour as before
+ // aSourceDocShell was introduced. According to spec we should be passing
+ // the source browsing context that was used when the history entry was
+ // first created. bug 947716 has been created to address this issue.
+ nsAutoString srcdoc;
+ nsCOMPtr<nsIURI> baseURI;
+ if (mIsSrcdocEntry) {
+ srcdoc = mSrcdocData;
+ baseURI = mBaseURI;
+ flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ } else {
+ srcdoc = VoidString();
+ }
+ aLoadState.SetSrcdocData(srcdoc);
+ aLoadState.SetBaseURI(baseURI);
+ aLoadState.SetLoadFlags(flags);
+
+ aLoadState.SetFirstParty(true);
+}
+/* static */
+SessionHistoryInfo::SharedState SessionHistoryInfo::SharedState::Create(
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType) {
+ if (XRE_IsParentProcess()) {
+ return SharedState(new SHEntrySharedParentState(
+ aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType));
+ }
+
+ return SharedState(MakeUnique<SHEntrySharedState>(
+ aTriggeringPrincipal, aPrincipalToInherit, aPartitionedPrincipalToInherit,
+ aCsp, aContentType));
+}
+
+SessionHistoryInfo::SharedState::SharedState() { Init(); }
+
+SessionHistoryInfo::SharedState::SharedState(
+ const SessionHistoryInfo::SharedState& aOther) {
+ Init(aOther);
+}
+
+SessionHistoryInfo::SharedState::SharedState(
+ const Maybe<const SessionHistoryInfo::SharedState&>& aOther) {
+ if (aOther.isSome()) {
+ Init(aOther.ref());
+ } else {
+ Init();
+ }
+}
+
+SessionHistoryInfo::SharedState::~SharedState() {
+ if (XRE_IsParentProcess()) {
+ mParent
+ .RefPtr<SHEntrySharedParentState>::~RefPtr<SHEntrySharedParentState>();
+ } else {
+ mChild.UniquePtr<SHEntrySharedState>::~UniquePtr<SHEntrySharedState>();
+ }
+}
+
+SessionHistoryInfo::SharedState& SessionHistoryInfo::SharedState::operator=(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (this != &aOther) {
+ if (XRE_IsParentProcess()) {
+ mParent = aOther.mParent;
+ } else {
+ mChild = MakeUnique<SHEntrySharedState>(*aOther.mChild);
+ }
+ }
+ return *this;
+}
+
+SHEntrySharedState* SessionHistoryInfo::SharedState::Get() const {
+ if (XRE_IsParentProcess()) {
+ return mParent;
+ }
+
+ return mChild.get();
+}
+
+void SessionHistoryInfo::SharedState::ChangeId(uint64_t aId) {
+ if (XRE_IsParentProcess()) {
+ mParent->ChangeId(aId);
+ } else {
+ mChild->mId = aId;
+ }
+}
+
+void SessionHistoryInfo::SharedState::Init() {
+ if (XRE_IsParentProcess()) {
+ new (&mParent)
+ RefPtr<SHEntrySharedParentState>(new SHEntrySharedParentState());
+ } else {
+ new (&mChild)
+ UniquePtr<SHEntrySharedState>(MakeUnique<SHEntrySharedState>());
+ }
+}
+
+void SessionHistoryInfo::SharedState::Init(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (XRE_IsParentProcess()) {
+ new (&mParent) RefPtr<SHEntrySharedParentState>(aOther.mParent);
+ } else {
+ new (&mChild) UniquePtr<SHEntrySharedState>(
+ MakeUnique<SHEntrySharedState>(*aOther.mChild));
+ }
+}
+
+static uint64_t gLoadingSessionHistoryInfoLoadId = 0;
+
+nsDataHashtable<nsUint64HashKey, SessionHistoryEntry*>*
+ SessionHistoryEntry::sLoadIdToEntry = nullptr;
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ SessionHistoryEntry* aEntry)
+ : mInfo(aEntry->Info()), mLoadId(++gLoadingSessionHistoryInfoLoadId) {
+ SessionHistoryEntry::SetByLoadId(mLoadId, aEntry);
+}
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ SessionHistoryEntry* aEntry, uint64_t aLoadId)
+ : mInfo(aEntry->Info()), mLoadId(aLoadId) {
+ MOZ_ASSERT(SessionHistoryEntry::sLoadIdToEntry &&
+ SessionHistoryEntry::sLoadIdToEntry->Get(aLoadId) == aEntry);
+}
+
+already_AddRefed<nsDocShellLoadState>
+LoadingSessionHistoryInfo::CreateLoadInfo() const {
+ RefPtr<nsDocShellLoadState> loadState(
+ new nsDocShellLoadState(mInfo.GetURI()));
+
+ mInfo.FillLoadInfo(*loadState);
+
+ loadState->SetLoadingSessionHistoryInfo(*this);
+
+ return loadState.forget();
+}
+
+static uint32_t gEntryID;
+
+SessionHistoryEntry* SessionHistoryEntry::GetByLoadId(uint64_t aLoadId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!sLoadIdToEntry) {
+ return nullptr;
+ }
+
+ return sLoadIdToEntry->Get(aLoadId);
+}
+
+void SessionHistoryEntry::SetByLoadId(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry) {
+ if (!sLoadIdToEntry) {
+ sLoadIdToEntry =
+ new nsDataHashtable<nsUint64HashKey, SessionHistoryEntry*>();
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Verbose,
+ ("SessionHistoryEntry::SetByLoadId(%" PRIu64 " - %p)", aLoadId, aEntry));
+ sLoadIdToEntry->Put(aLoadId, aEntry);
+}
+
+void SessionHistoryEntry::RemoveLoadId(uint64_t aLoadId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!sLoadIdToEntry) {
+ return;
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("SHEntry::RemoveLoadId(%" PRIu64 ")", aLoadId));
+ sLoadIdToEntry->Remove(aLoadId);
+}
+
+SessionHistoryEntry::SessionHistoryEntry()
+ : mInfo(new SessionHistoryInfo()), mID(++gEntryID) {}
+
+SessionHistoryEntry::SessionHistoryEntry(nsDocShellLoadState* aLoadState,
+ nsIChannel* aChannel)
+ : mInfo(new SessionHistoryInfo(aLoadState, aChannel)), mID(++gEntryID) {}
+
+SessionHistoryEntry::SessionHistoryEntry(SessionHistoryInfo* aInfo)
+ : mInfo(MakeUnique<SessionHistoryInfo>(*aInfo)), mID(++gEntryID) {}
+
+SessionHistoryEntry::SessionHistoryEntry(const SessionHistoryEntry& aEntry)
+ : mInfo(MakeUnique<SessionHistoryInfo>(*aEntry.mInfo)),
+ mParent(aEntry.mParent),
+ mID(aEntry.mID) {}
+
+SessionHistoryEntry::~SessionHistoryEntry() {
+ // Null out the mParent pointers on all our kids.
+ for (nsISHEntry* entry : mChildren) {
+ if (entry) {
+ entry->SetParent(nullptr);
+ }
+ }
+
+ if (sLoadIdToEntry) {
+ sLoadIdToEntry->RemoveIf(
+ [this](auto& aIter) { return aIter.Data() == this; });
+ if (sLoadIdToEntry->IsEmpty()) {
+ delete sLoadIdToEntry;
+ sLoadIdToEntry = nullptr;
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(SessionHistoryEntry, nsISHEntry, SessionHistoryEntry)
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri = mInfo->mURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetURI(nsIURI* aURI) {
+ mInfo->mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetOriginalURI(nsIURI** aOriginalURI) {
+ nsCOMPtr<nsIURI> originalURI = mInfo->mOriginalURI;
+ originalURI.forget(aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetOriginalURI(nsIURI* aOriginalURI) {
+ mInfo->mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) {
+ nsCOMPtr<nsIURI> resultPrincipalURI = mInfo->mResultPrincipalURI;
+ resultPrincipalURI.forget(aResultPrincipalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mInfo->mResultPrincipalURI = aResultPrincipalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadReplace(bool* aLoadReplace) {
+ *aLoadReplace = mInfo->mLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadReplace(bool aLoadReplace) {
+ mInfo->mLoadReplace = aLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetTitle(nsAString& aTitle) {
+ aTitle = mInfo->mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetTitle(const nsAString& aTitle) {
+ mInfo->SetTitle(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetName(nsAString& aName) {
+ aName = mInfo->mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetName(const nsAString& aName) {
+ mInfo->mName = aName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetIsSubFrame(bool* aIsSubFrame) {
+ *aIsSubFrame = SharedInfo()->mIsFrameNavigation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetIsSubFrame(bool aIsSubFrame) {
+ SharedInfo()->mIsFrameNavigation = aIsSubFrame;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetHasUserInteraction(bool* aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ *aFlag = mInfo->mHasUserInteraction;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->GetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetHasUserInteraction(bool aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ mInfo->mHasUserInteraction = aFlag;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->SetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mInfo->mReferrerInfo;
+ referrerInfo.forget(aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mInfo->mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetContentViewer(nsIContentViewer** aContentViewer) {
+ NS_WARNING("This lives in the child process");
+ *aContentViewer = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetContentViewer(nsIContentViewer* aContentViewer) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSticky(bool* aSticky) {
+ *aSticky = SharedInfo()->mSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSticky(bool aSticky) {
+ SharedInfo()->mSticky = aSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetWindowState(nsISupports** aWindowState) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetWindowState(nsISupports* aWindowState) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetRefreshURIList(nsIMutableArray** aRefreshURIList) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetRefreshURIList(nsIMutableArray* aRefreshURIList) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPostData(nsIInputStream** aPostData) {
+ nsCOMPtr<nsIInputStream> postData = mInfo->mPostData;
+ postData.forget(aPostData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPostData(nsIInputStream* aPostData) {
+ mInfo->mPostData = aPostData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLayoutHistoryState(
+ nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> layoutHistoryState =
+ SharedInfo()->mLayoutHistoryState;
+ layoutHistoryState.forget(aLayoutHistoryState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLayoutHistoryState(
+ nsILayoutHistoryState* aLayoutHistoryState) {
+ SharedInfo()->mLayoutHistoryState = aLayoutHistoryState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetParent(nsISHEntry** aParent) {
+ nsCOMPtr<nsISHEntry> parent = mParent;
+ parent.forget(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetParent(nsISHEntry* aParent) {
+ mParent = aParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mInfo->mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadType(uint32_t aLoadType) {
+ mInfo->mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetID(uint32_t* aID) {
+ *aID = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetID(uint32_t aID) {
+ mID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetCacheKey(uint32_t* aCacheKey) {
+ *aCacheKey = SharedInfo()->mCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetCacheKey(uint32_t aCacheKey) {
+ SharedInfo()->mCacheKey = aCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSaveLayoutStateFlag(bool* aSaveLayoutStateFlag) {
+ *aSaveLayoutStateFlag = SharedInfo()->mSaveLayoutState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) {
+ SharedInfo()->mSaveLayoutState = aSaveLayoutStateFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetContentType(nsACString& aContentType) {
+ aContentType = SharedInfo()->mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetContentType(const nsACString& aContentType) {
+ SharedInfo()->mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetURIWasModified(bool* aURIWasModified) {
+ *aURIWasModified = mInfo->mURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetURIWasModified(bool aURIWasModified) {
+ mInfo->mURIWasModified = aURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetTriggeringPrincipal(
+ nsIPrincipal** aTriggeringPrincipal) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ SharedInfo()->mTriggeringPrincipal;
+ triggeringPrincipal.forget(aTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ SharedInfo()->mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ nsCOMPtr<nsIPrincipal> principalToInherit = SharedInfo()->mPrincipalToInherit;
+ principalToInherit.forget(aPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ SharedInfo()->mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPartitionedPrincipalToInherit(
+ nsIPrincipal** aPartitionedPrincipalToInherit) {
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ SharedInfo()->mPartitionedPrincipalToInherit;
+ partitionedPrincipalToInherit.forget(aPartitionedPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ SharedInfo()->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp = SharedInfo()->mCsp;
+ csp.forget(aCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ SharedInfo()->mCsp = aCsp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetStateData(nsIStructuredCloneContainer** aStateData) {
+ RefPtr<nsStructuredCloneContainer> stateData = mInfo->mStateData;
+ stateData.forget(aStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetStateData(nsIStructuredCloneContainer* aStateData) {
+ mInfo->mStateData = static_cast<nsStructuredCloneContainer*>(aStateData);
+ return NS_OK;
+}
+
+const nsID& SessionHistoryEntry::DocshellID() const {
+ return SharedInfo()->mDocShellID;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetDocshellID(nsID& aDocshellID) {
+ aDocshellID = DocshellID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetDocshellID(const nsID& aDocshellID) {
+ SharedInfo()->mDocShellID = aDocshellID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+ *aIsSrcdocEntry = mInfo->mIsSrcdocEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mInfo->mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSrcdocData(const nsAString& aSrcdocData) {
+ mInfo->mSrcdocData = aSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetBaseURI(nsIURI** aBaseURI) {
+ nsCOMPtr<nsIURI> baseURI = mInfo->mBaseURI;
+ baseURI.forget(aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetBaseURI(nsIURI* aBaseURI) {
+ mInfo->mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetScrollRestorationIsManual(
+ bool* aScrollRestorationIsManual) {
+ *aScrollRestorationIsManual = mInfo->mScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetScrollRestorationIsManual(
+ bool aScrollRestorationIsManual) {
+ mInfo->mScrollRestorationIsManual = aScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+ // FIXME
+ //*aLoadedInThisProcess = mInfo->mLoadedInThisProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetShistory(nsISHistory** aShistory) {
+ nsCOMPtr<nsISHistory> sHistory = do_QueryReferent(SharedInfo()->mSHistory);
+ sHistory.forget(aShistory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetShistory(nsISHistory* aShistory) {
+ nsWeakPtr shistory = do_GetWeakReference(aShistory);
+ // mSHistory can not be changed once it's set
+ MOZ_ASSERT(!SharedInfo()->mSHistory || (SharedInfo()->mSHistory == shistory));
+ SharedInfo()->mSHistory = shistory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLastTouched(uint32_t* aLastTouched) {
+ *aLastTouched = SharedInfo()->mLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLastTouched(uint32_t aLastTouched) {
+ SharedInfo()->mLastTouched = aLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetChildCount(int32_t* aChildCount) {
+ *aChildCount = mChildren.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPersist(bool* aPersist) {
+ *aPersist = mInfo->mPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPersist(bool aPersist) {
+ mInfo->mPersist = aPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
+ *aX = mInfo->mScrollPositionX;
+ *aY = mInfo->mScrollPositionY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetScrollPosition(int32_t aX, int32_t aY) {
+ mInfo->mScrollPositionX = aX;
+ mInfo->mScrollPositionY = aY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::GetViewerBounds(nsIntRect& bounds) {
+ bounds = SharedInfo()->mViewerBounds;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SetViewerBounds(const nsIntRect& bounds) {
+ SharedInfo()->mViewerBounds = bounds;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::AddChildShell(nsIDocShellTreeItem* shell) {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::ChildShellAt(int32_t index,
+ nsIDocShellTreeItem** _retval) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::ClearChildShells() {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SyncPresentationState() {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::InitLayoutHistoryState(
+ nsILayoutHistoryState** aLayoutHistoryState) {
+ if (!SharedInfo()->mLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ historyState = NS_NewLayoutHistoryState();
+ SetLayoutHistoryState(historyState);
+ }
+
+ return GetLayoutHistoryState(aLayoutHistoryState);
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::Create(
+ nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream,
+ uint32_t aCacheKey, const nsACString& aContentType,
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID,
+ bool aDynamicCreation, nsIURI* aOriginalURI, nsIURI* aResultPrincipalURI,
+ bool aLoadReplace, nsIReferrerInfo* aReferrerInfo, const nsAString& aSrcdoc,
+ bool aSrcdocEntry, nsIURI* aBaseURI, bool aSaveLayoutState, bool aExpired) {
+ MOZ_CRASH("Might need to implement this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::Clone(nsISHEntry** aEntry) {
+ RefPtr<SessionHistoryEntry> entry = new SessionHistoryEntry(*this);
+
+ // These are not copied for some reason, we're not sure why.
+ entry->mInfo->mLoadType = 0;
+ entry->mInfo->mScrollPositionX = 0;
+ entry->mInfo->mScrollPositionY = 0;
+ entry->mInfo->mScrollRestorationIsManual = false;
+
+ entry->mInfo->mHasUserInteraction = false;
+
+ entry.forget(aEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(nsDocShellEditorData*)
+SessionHistoryEntry::ForgetEditorData() {
+ MOZ_CRASH("This lives in the child process");
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SetEditorData(nsDocShellEditorData* aData) {
+ NS_WARNING("This lives in the child process");
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::HasDetachedEditor() {
+ NS_WARNING("This lives in the child process");
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::IsDynamicallyAdded() {
+ return SharedInfo()->mDynamicallyCreated;
+}
+
+void SessionHistoryEntry::SetIsDynamicallyAdded(bool aDynamic) {
+ MOZ_ASSERT_IF(SharedInfo()->mDynamicallyCreated, aDynamic);
+ SharedInfo()->mDynamicallyCreated = aDynamic;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::HasDynamicallyAddedChild(bool* aHasDynamicallyAddedChild) {
+ for (const auto& child : mChildren) {
+ if (child && child->IsDynamicallyAdded()) {
+ *aHasDynamicallyAddedChild = true;
+ return NS_OK;
+ }
+ }
+ *aHasDynamicallyAddedChild = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) {
+ MOZ_CRASH("This lives in the child process");
+ return false;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry);
+ NS_ENSURE_STATE(she && she->mInfo->mSharedState.Get());
+
+ mInfo->mSharedState =
+ static_cast<SessionHistoryEntry*>(aEntry)->mInfo->mSharedState;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AbandonBFCacheEntry() {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SharesDocumentWith(nsISHEntry* aEntry,
+ bool* aSharesDocumentWith) {
+ SessionHistoryEntry* entry = static_cast<SessionHistoryEntry*>(aEntry);
+
+ MOZ_ASSERT_IF(entry->SharedInfo() != SharedInfo(),
+ entry->SharedInfo()->GetId() != SharedInfo()->GetId());
+
+ *aSharesDocumentWith = entry->SharedInfo() == SharedInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadTypeAsHistory() {
+ mInfo->mLoadType = LOAD_HISTORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AddChild(nsISHEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ nsCOMPtr<SessionHistoryEntry> child = do_QueryInterface(aChild);
+ MOZ_ASSERT_IF(aChild, child);
+ AddChild(child, aOffset, aUseRemoteSubframes);
+
+ return NS_OK;
+}
+
+void SessionHistoryEntry::AddChild(SessionHistoryEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ if (aChild) {
+ aChild->SetParent(this);
+ }
+
+ if (aOffset < 0) {
+ mChildren.AppendElement(aChild);
+ return;
+ }
+
+ //
+ // Bug 52670: Ensure children are added in order.
+ //
+ // Later frames in the child list may load faster and get appended
+ // before earlier frames, causing session history to be scrambled.
+ // By growing the list here, they are added to the right position.
+
+ int32_t length = mChildren.Length();
+
+ // Assert that aOffset will not be so high as to grow us a lot.
+ NS_ASSERTION(aOffset < length + 1023, "Large frames array!\n");
+
+ // If the new child is dynamically added, try to add it to aOffset, but if
+ // there are non-dynamically added children, the child must be after those.
+ if (aChild && aChild->IsDynamicallyAdded()) {
+ int32_t lastNonDyn = aOffset - 1;
+ for (int32_t i = aOffset; i < length; ++i) {
+ SessionHistoryEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ break;
+ }
+
+ lastNonDyn = i;
+ }
+ }
+
+ // If aOffset is larger than Length(), we must first truncate the array.
+ if (aOffset > length) {
+ mChildren.SetLength(aOffset);
+ }
+
+ mChildren.InsertElementAt(lastNonDyn + 1, aChild);
+
+ return;
+ }
+
+ // If the new child isn't dynamically added, it should be set to aOffset.
+ // If there are dynamically added children before that, those must be moved
+ // to be after aOffset.
+ if (length > 0) {
+ int32_t start = std::min(length - 1, aOffset);
+ int32_t dynEntryIndex = -1;
+ DebugOnly<SessionHistoryEntry*> dynEntry = nullptr;
+ for (int32_t i = start; i >= 0; --i) {
+ SessionHistoryEntry* entry = mChildren[i];
+ if (entry) {
+ if (!entry->IsDynamicallyAdded()) {
+ break;
+ }
+
+ dynEntryIndex = i;
+ dynEntry = entry;
+ }
+ }
+
+ if (dynEntryIndex >= 0) {
+ mChildren.InsertElementsAt(dynEntryIndex, aOffset - dynEntryIndex + 1);
+ NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
+ }
+ }
+
+ // Make sure there isn't anything at aOffset.
+ if ((uint32_t)aOffset < mChildren.Length()) {
+ SessionHistoryEntry* oldChild = mChildren[aOffset];
+ if (oldChild && oldChild != aChild) {
+ // Under Fission, this can happen when a network-created iframe starts
+ // out in-process, moves out-of-process, and then switches back. At that
+ // point, we'll create a new network-created DocShell at the same index
+ // where we already have an entry for the original network-created
+ // DocShell.
+ //
+ // This should ideally stop being an issue once the Fission-aware
+ // session history rewrite is complete.
+ NS_ASSERTION(
+ aUseRemoteSubframes,
+ "Adding a child where we already have a child? This may misbehave");
+ oldChild->SetParent(nullptr);
+ }
+ } else {
+ mChildren.SetLength(aOffset + 1);
+ }
+
+ mChildren.ReplaceElementAt(aOffset, aChild);
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::RemoveChild(nsISHEntry* aChild) {
+ NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
+
+ nsCOMPtr<SessionHistoryEntry> child = do_QueryInterface(aChild);
+ MOZ_ASSERT(child);
+ RemoveChild(child);
+
+ return NS_OK;
+}
+
+void SessionHistoryEntry::RemoveChild(SessionHistoryEntry* aChild) {
+ bool childRemoved = false;
+ if (aChild->IsDynamicallyAdded()) {
+ childRemoved = mChildren.RemoveElement(aChild);
+ } else {
+ int32_t index = mChildren.IndexOf(aChild);
+ if (index >= 0) {
+ // Other alive non-dynamic child docshells still keep mChildOffset,
+ // so we don't want to change the indices here.
+ mChildren.ReplaceElementAt(index, nullptr);
+ childRemoved = true;
+ }
+ }
+
+ if (childRemoved) {
+ aChild->SetParent(nullptr);
+
+ // reduce the child count, i.e. remove empty children at the end
+ for (int32_t i = mChildren.Length() - 1; i >= 0 && !mChildren[i]; --i) {
+ mChildren.RemoveElementAt(i);
+ }
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetChildAt(int32_t aIndex, nsISHEntry** aChild) {
+ nsCOMPtr<nsISHEntry> child = mChildren.SafeElementAt(aIndex);
+ child.forget(aChild);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ int32_t aChildOffset, nsISHEntry** aChild) {
+ *aChild = nullptr;
+
+ bool dynamicallyAddedChild = false;
+ HasDynamicallyAddedChild(&dynamicallyAddedChild);
+ if (dynamicallyAddedChild) {
+ return;
+ }
+
+ // If the user did a shift-reload on this frameset page,
+ // we don't want to load the subframes from history.
+ if (IsForceReloadType(mInfo->mLoadType) || mInfo->mLoadType == LOAD_REFRESH) {
+ return;
+ }
+
+ /* Before looking for the subframe's url, check
+ * the expiration status of the parent. If the parent
+ * has expired from cache, then subframes will not be
+ * loaded from history in certain situations.
+ * If the user pressed reload and the parent frame has expired
+ * from cache, we do not want to load the child frame from history.
+ */
+ if (SharedInfo()->mExpired && (mInfo->mLoadType == LOAD_RELOAD_NORMAL)) {
+ // The parent has expired. Return null.
+ *aChild = nullptr;
+ return;
+ }
+ // Get the child subframe from session history.
+ GetChildAt(aChildOffset, aChild);
+ if (*aChild) {
+ // Set the parent's Load Type on the child
+ (*aChild)->SetLoadType(mInfo->mLoadType);
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::ReplaceChild(nsISHEntry* aNewChild) {
+ NS_ENSURE_STATE(aNewChild);
+
+ nsCOMPtr<SessionHistoryEntry> newChild = do_QueryInterface(aNewChild);
+ MOZ_ASSERT(newChild);
+ return ReplaceChild(newChild) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool SessionHistoryEntry::ReplaceChild(SessionHistoryEntry* aNewChild) {
+ const nsID& docshellID = aNewChild->DocshellID();
+
+ for (uint32_t i = 0; i < mChildren.Length(); ++i) {
+ if (mChildren[i] && docshellID == mChildren[i]->DocshellID()) {
+ mChildren[i]->SetParent(nullptr);
+ mChildren.ReplaceElementAt(i, aNewChild);
+ aNewChild->SetParent(this);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::ClearEntry() {
+ int32_t childCount = GetChildCount();
+ // Remove all children of this entry
+ for (int32_t i = childCount; i > 0; --i) {
+ nsCOMPtr<nsISHEntry> child;
+ GetChildAt(i - 1, getter_AddRefs(child));
+ RemoveChild(child);
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
+ NS_WARNING("We shouldn't be calling this!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetBfcacheID(uint64_t* aBfcacheID) {
+ *aBfcacheID = SharedInfo()->mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SyncTreesForSubframeNavigation(
+ nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC,
+ mozilla::dom::BrowsingContext* aIgnoreBC) {
+ // XXX Keep this in sync with nsSHEntry::SyncTreesForSubframeNavigation.
+ //
+ // We need to sync up the browsing context and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the top browsing context, which will then recursively sync up all browsing
+ // contexts to their corresponding entries in the new session history tree. If
+ // we don't do this, then we can cache a content viewer on the wrong cloned
+ // entry, and subsequently restore it at the wrong time.
+ nsCOMPtr<nsISHEntry> newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
+ if (newRootEntry) {
+ // newRootEntry is now the new root entry.
+ // Find the old root entry as well.
+
+ // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
+ // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
+ nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(this);
+
+ if (oldRootEntry) {
+ nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr};
+ nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data);
+ }
+ }
+}
+
+void SessionHistoryEntry::ReplaceWith(const SessionHistoryEntry& aSource) {
+ mInfo = MakeUnique<SessionHistoryInfo>(*aSource.mInfo);
+ mChildren.Clear();
+}
+
+SHEntrySharedParentState* SessionHistoryEntry::SharedInfo() const {
+ return static_cast<SHEntrySharedParentState*>(mInfo->mSharedState.Get());
+}
+
+void SessionHistoryEntry::SetInfo(SessionHistoryInfo* aInfo) {
+ // FIXME Assert that we're not changing shared state!
+ mInfo = MakeUnique<SessionHistoryInfo>(*aInfo);
+}
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::SessionHistoryInfo>::Write(
+ IPC::Message* aMsg, IProtocol* aActor,
+ const dom::SessionHistoryInfo& aParam) {
+ Maybe<Tuple<uint32_t, dom::ClonedMessageData>> stateData;
+ if (aParam.mStateData) {
+ stateData.emplace();
+ uint32_t version;
+ NS_ENSURE_SUCCESS_VOID(aParam.mStateData->GetFormatVersion(&version));
+ Get<0>(*stateData) = version;
+
+ JSStructuredCloneData& data = aParam.mStateData->Data();
+ auto iter = data.Start();
+ bool success;
+ Get<1>(*stateData).data().data = data.Borrow(iter, data.Size(), &success);
+ if (NS_WARN_IF(!success)) {
+ return;
+ }
+ MOZ_ASSERT(aParam.mStateData->PortIdentifiers().IsEmpty() &&
+ aParam.mStateData->BlobImpls().IsEmpty() &&
+ aParam.mStateData->InputStreams().IsEmpty());
+ }
+
+ WriteIPDLParam(aMsg, aActor, aParam.mURI);
+ WriteIPDLParam(aMsg, aActor, aParam.mOriginalURI);
+ WriteIPDLParam(aMsg, aActor, aParam.mResultPrincipalURI);
+ WriteIPDLParam(aMsg, aActor, aParam.mReferrerInfo);
+ WriteIPDLParam(aMsg, aActor, aParam.mTitle);
+ WriteIPDLParam(aMsg, aActor, aParam.mName);
+ WriteIPDLParam(aMsg, aActor, aParam.mPostData);
+ WriteIPDLParam(aMsg, aActor, aParam.mLoadType);
+ WriteIPDLParam(aMsg, aActor, aParam.mScrollPositionX);
+ WriteIPDLParam(aMsg, aActor, aParam.mScrollPositionY);
+ WriteIPDLParam(aMsg, aActor, stateData);
+ WriteIPDLParam(aMsg, aActor, aParam.mSrcdocData);
+ WriteIPDLParam(aMsg, aActor, aParam.mBaseURI);
+ WriteIPDLParam(aMsg, aActor, aParam.mLoadReplace);
+ WriteIPDLParam(aMsg, aActor, aParam.mURIWasModified);
+ WriteIPDLParam(aMsg, aActor, aParam.mIsSrcdocEntry);
+ WriteIPDLParam(aMsg, aActor, aParam.mScrollRestorationIsManual);
+ WriteIPDLParam(aMsg, aActor, aParam.mPersist);
+ WriteIPDLParam(aMsg, aActor, aParam.mHasUserInteraction);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mId);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mTriggeringPrincipal);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mPrincipalToInherit);
+ WriteIPDLParam(aMsg, aActor,
+ aParam.mSharedState.Get()->mPartitionedPrincipalToInherit);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mCsp);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mContentType);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mLayoutHistoryState);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mCacheKey);
+ WriteIPDLParam(aMsg, aActor, aParam.mSharedState.Get()->mIsFrameNavigation);
+}
+
+bool IPDLParamTraits<dom::SessionHistoryInfo>::Read(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ dom::SessionHistoryInfo* aResult) {
+ Maybe<Tuple<uint32_t, dom::ClonedMessageData>> stateData;
+ uint64_t sharedId;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mURI) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mOriginalURI) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResultPrincipalURI) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mReferrerInfo) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mTitle) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mName) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mPostData) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLoadType) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mScrollPositionX) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mScrollPositionY) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &stateData) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSrcdocData) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mBaseURI) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLoadReplace) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mURIWasModified) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mIsSrcdocEntry) ||
+ !ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mScrollRestorationIsManual) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mPersist) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mHasUserInteraction) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &sharedId)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsCString contentType;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &triggeringPrincipal) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &principalToInherit) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &partitionedPrincipalToInherit) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &csp) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &contentType)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ dom::SHEntrySharedParentState* sharedState = nullptr;
+ if (XRE_IsParentProcess()) {
+ sharedState = dom::SHEntrySharedParentState::Lookup(sharedId);
+ }
+
+ if (sharedState) {
+ aResult->mSharedState.Set(sharedState);
+
+ MOZ_ASSERT(triggeringPrincipal
+ ? triggeringPrincipal->Equals(
+ aResult->mSharedState.Get()->mTriggeringPrincipal)
+ : !aResult->mSharedState.Get()->mTriggeringPrincipal,
+ "We don't expect this to change!");
+ MOZ_ASSERT(principalToInherit
+ ? principalToInherit->Equals(
+ aResult->mSharedState.Get()->mPrincipalToInherit)
+ : !aResult->mSharedState.Get()->mPrincipalToInherit,
+ "We don't expect this to change!");
+ MOZ_ASSERT(
+ partitionedPrincipalToInherit
+ ? partitionedPrincipalToInherit->Equals(
+ aResult->mSharedState.Get()->mPartitionedPrincipalToInherit)
+ : !aResult->mSharedState.Get()->mPartitionedPrincipalToInherit,
+ "We don't expect this to change!");
+ MOZ_ASSERT(
+ csp ? nsCSPContext::Equals(csp, aResult->mSharedState.Get()->mCsp)
+ : !aResult->mSharedState.Get()->mCsp,
+ "We don't expect this to change!");
+ MOZ_ASSERT(contentType.Equals(aResult->mSharedState.Get()->mContentType),
+ "We don't expect this to change!");
+ } else {
+ aResult->mSharedState.ChangeId(sharedId);
+ aResult->mSharedState.Get()->mTriggeringPrincipal =
+ triggeringPrincipal.forget();
+ aResult->mSharedState.Get()->mPrincipalToInherit =
+ principalToInherit.forget();
+ aResult->mSharedState.Get()->mPartitionedPrincipalToInherit =
+ partitionedPrincipalToInherit.forget();
+ aResult->mSharedState.Get()->mCsp = csp.forget();
+ aResult->mSharedState.Get()->mContentType = contentType;
+ }
+
+ if (!ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mSharedState.Get()->mLayoutHistoryState) ||
+ !ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mSharedState.Get()->mCacheKey) ||
+ !ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mSharedState.Get()->mIsFrameNavigation)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ if (stateData.isSome()) {
+ uint32_t version = Get<0>(*stateData);
+ aResult->mStateData = new nsStructuredCloneContainer(version);
+ if (aActor->GetSide() == ChildSide) {
+ aResult->mStateData->StealFromClonedMessageDataForChild(
+ Get<1>(*stateData));
+ } else {
+ aResult->mStateData->StealFromClonedMessageDataForParent(
+ Get<1>(*stateData));
+ }
+ }
+ MOZ_ASSERT_IF(stateData.isNothing(), !aResult->mStateData);
+ return true;
+}
+
+void IPDLParamTraits<dom::LoadingSessionHistoryInfo>::Write(
+ IPC::Message* aMsg, IProtocol* aActor,
+ const dom::LoadingSessionHistoryInfo& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mInfo);
+ WriteIPDLParam(aMsg, aActor, aParam.mLoadId);
+ WriteIPDLParam(aMsg, aActor, aParam.mLoadIsFromSessionHistory);
+ WriteIPDLParam(aMsg, aActor, aParam.mRequestedIndex);
+ WriteIPDLParam(aMsg, aActor, aParam.mSessionHistoryLength);
+}
+
+bool IPDLParamTraits<dom::LoadingSessionHistoryInfo>::Read(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ dom::LoadingSessionHistoryInfo* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mInfo) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLoadId) ||
+ !ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mLoadIsFromSessionHistory) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRequestedIndex) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSessionHistoryLength)) {
+ aActor->FatalError("Error reading fields for LoadingSessionHistoryInfo");
+ return false;
+ }
+
+ return true;
+}
+
+void IPDLParamTraits<nsILayoutHistoryState*>::Write(
+ IPC::Message* aMsg, IProtocol* aActor, nsILayoutHistoryState* aParam) {
+ if (aParam) {
+ WriteIPDLParam(aMsg, aActor, true);
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> states;
+ aParam->GetContents(&scrollPositionOnly, keys, states);
+ WriteIPDLParam(aMsg, aActor, scrollPositionOnly);
+ WriteIPDLParam(aMsg, aActor, keys);
+ WriteIPDLParam(aMsg, aActor, states);
+ } else {
+ WriteIPDLParam(aMsg, aActor, false);
+ }
+}
+
+bool IPDLParamTraits<nsILayoutHistoryState*>::Read(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ RefPtr<nsILayoutHistoryState>* aResult) {
+ bool hasLayoutHistoryState = false;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &hasLayoutHistoryState)) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ return false;
+ }
+
+ if (hasLayoutHistoryState) {
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> states;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &scrollPositionOnly) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &keys) ||
+ !ReadIPDLParam(aMsg, aIter, aActor, &states)) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ }
+
+ if (keys.Length() != states.Length()) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ return false;
+ }
+
+ *aResult = NS_NewLayoutHistoryState();
+ (*aResult)->SetScrollPositionOnly(scrollPositionOnly);
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ PresState& state = states[i];
+ UniquePtr<PresState> newState = MakeUnique<PresState>(state);
+ (*aResult)->AddState(keys[i], std::move(newState));
+ }
+ }
+ return true;
+}
+
+} // namespace ipc
+
+} // namespace mozilla
diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h
new file mode 100644
index 0000000000..1b42975390
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -0,0 +1,337 @@
+/* -*- 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_SessionHistoryEntry_h
+#define mozilla_dom_SessionHistoryEntry_h
+
+#include "mozilla/UniquePtr.h"
+#include "nsILayoutHistoryState.h"
+#include "nsISHEntry.h"
+#include "nsSHEntryShared.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsDataHashtable.h"
+
+class nsDocShellLoadState;
+class nsIChannel;
+class nsIInputStream;
+class nsIReferrerInfo;
+class nsISHistory;
+class nsIURI;
+
+namespace mozilla::ipc {
+template <typename P>
+struct IPDLParamTraits;
+}
+
+namespace mozilla {
+namespace dom {
+
+struct LoadingSessionHistoryInfo;
+class SessionHistoryEntry;
+class SHEntrySharedParentState;
+
+// SessionHistoryInfo stores session history data for a load. It can be sent
+// over IPC and is used in both the parent and the child processes.
+class SessionHistoryInfo {
+ public:
+ SessionHistoryInfo() = default;
+ SessionHistoryInfo(const SessionHistoryInfo& aInfo) = default;
+ SessionHistoryInfo(nsDocShellLoadState* aLoadState, nsIChannel* aChannel);
+ SessionHistoryInfo(const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI);
+ SessionHistoryInfo(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+ SessionHistoryInfo(nsIChannel* aChannel, uint32_t aLoadType,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp);
+
+ void Reset(nsIURI* aURI, const nsID& aDocShellID, bool aDynamicCreation,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType);
+
+ bool operator==(const SessionHistoryInfo& aInfo) const {
+ return false; // FIXME
+ }
+
+ nsIURI* GetURI() const { return mURI; }
+ void SetURI(nsIURI* aURI) { mURI = aURI; }
+
+ void SetOriginalURI(nsIURI* aOriginalURI) { mOriginalURI = aOriginalURI; }
+
+ void SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+ }
+
+ nsIInputStream* GetPostData() const { return mPostData; }
+ void SetPostData(nsIInputStream* aPostData) { mPostData = aPostData; }
+
+ void GetScrollPosition(int32_t* aScrollPositionX, int32_t* aScrollPositionY) {
+ *aScrollPositionX = mScrollPositionX;
+ *aScrollPositionY = mScrollPositionY;
+ }
+ bool GetScrollRestorationIsManual() const {
+ return mScrollRestorationIsManual;
+ }
+ const nsAString& GetTitle() { return mTitle; }
+ void SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ MaybeUpdateTitleFromURI();
+ }
+
+ const nsAString& GetName() { return mName; }
+ void SetName(const nsAString& aName) { mName = aName; }
+
+ void SetScrollRestorationIsManual(bool aIsManual) {
+ mScrollRestorationIsManual = aIsManual;
+ }
+
+ nsStructuredCloneContainer* GetStateData() const { return mStateData; }
+ void SetStateData(nsStructuredCloneContainer* aStateData) {
+ mStateData = aStateData;
+ }
+
+ void SetLoadReplace(bool aLoadReplace) { mLoadReplace = aLoadReplace; }
+
+ void SetURIWasModified(bool aURIWasModified) {
+ mURIWasModified = aURIWasModified;
+ }
+ bool GetURIWasModified() const { return mURIWasModified; }
+
+ void SetHasUserInteraction(bool aHasUserInteraction) {
+ mHasUserInteraction = aHasUserInteraction;
+ }
+ bool GetHasUserInteraction() const { return mHasUserInteraction; }
+
+ uint64_t SharedId() const;
+
+ nsILayoutHistoryState* GetLayoutHistoryState();
+ void SetLayoutHistoryState(nsILayoutHistoryState* aState);
+
+ nsIPrincipal* GetTriggeringPrincipal() const;
+
+ nsIPrincipal* GetPrincipalToInherit() const;
+
+ nsIPrincipal* GetPartitionedPrincipalToInherit() const;
+
+ nsIContentSecurityPolicy* GetCsp() const;
+
+ uint32_t GetCacheKey() const;
+ void SetCacheKey(uint32_t aCacheKey);
+
+ bool IsSubFrame() const;
+
+ bool SharesDocumentWith(const SessionHistoryInfo& aOther) const {
+ return SharedId() == aOther.SharedId();
+ }
+
+ void FillLoadInfo(nsDocShellLoadState& aLoadState) const;
+
+ uint32_t LoadType() { return mLoadType; }
+
+ private:
+ friend class SessionHistoryEntry;
+ friend struct mozilla::ipc::IPDLParamTraits<SessionHistoryInfo>;
+
+ void MaybeUpdateTitleFromURI();
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType = 0;
+ int32_t mScrollPositionX = 0;
+ int32_t mScrollPositionY = 0;
+ RefPtr<nsStructuredCloneContainer> mStateData;
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ bool mLoadReplace = false;
+ bool mURIWasModified = false;
+ bool mIsSrcdocEntry = false;
+ bool mScrollRestorationIsManual = false;
+ bool mPersist = true;
+ bool mHasUserInteraction = false;
+
+ union SharedState {
+ SharedState();
+ explicit SharedState(const SharedState& aOther);
+ explicit SharedState(const Maybe<const SharedState&>& aOther);
+ ~SharedState();
+
+ SharedState& operator=(const SharedState& aOther);
+
+ SHEntrySharedState* Get() const;
+
+ void Set(SHEntrySharedParentState* aState) { mParent = aState; }
+
+ void ChangeId(uint64_t aId);
+
+ static SharedState Create(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+
+ private:
+ explicit SharedState(SHEntrySharedParentState* aParent)
+ : mParent(aParent) {}
+ explicit SharedState(UniquePtr<SHEntrySharedState>&& aChild)
+ : mChild(std::move(aChild)) {}
+
+ void Init();
+ void Init(const SharedState& aOther);
+
+ // In the parent process this holds a strong reference to the refcounted
+ // SHEntrySharedParentState. In the child processes this holds an owning
+ // pointer to a SHEntrySharedState.
+ RefPtr<SHEntrySharedParentState> mParent;
+ UniquePtr<SHEntrySharedState> mChild;
+ };
+
+ SharedState mSharedState;
+};
+
+struct LoadingSessionHistoryInfo {
+ LoadingSessionHistoryInfo() = default;
+ explicit LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry);
+ LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry, uint64_t aLoadId);
+
+ already_AddRefed<nsDocShellLoadState> CreateLoadInfo() const;
+
+ SessionHistoryInfo mInfo;
+
+ uint64_t mLoadId = 0;
+
+ // The following three member variables are used to inform about a load from
+ // the session history. The session-history-in-child approach has just
+ // an nsISHEntry in the nsDocShellLoadState and access to the nsISHistory,
+ // but session-history-in-parent needs to pass needed information explicitly
+ // to the relevant child process.
+ bool mLoadIsFromSessionHistory = false;
+ // mRequestedIndex, mSessionHistoryLength and mLoadingCurrentActiveEntry are
+ // relevant only if mLoadIsFromSessionHistory is true.
+ int32_t mRequestedIndex = -1;
+ int32_t mSessionHistoryLength = 0;
+ // If we're loading from the current active entry we want to treat it as not
+ // a same-document navigation (see nsDocShell::IsSameDocumentNavigation).
+ bool mLoadingCurrentActiveEntry = false;
+};
+
+// SessionHistoryEntry is used to store session history data in the parent
+// process. It holds a SessionHistoryInfo, some state shared amongst multiple
+// SessionHistoryEntries, a parent and children.
+#define NS_SESSIONHISTORYENTRY_IID \
+ { \
+ 0x5b66a244, 0x8cec, 0x4caa, { \
+ 0xaa, 0x0a, 0x78, 0x92, 0xfd, 0x17, 0xa6, 0x67 \
+ } \
+ }
+
+class SessionHistoryEntry : public nsISHEntry {
+ public:
+ SessionHistoryEntry(nsDocShellLoadState* aLoadState, nsIChannel* aChannel);
+ SessionHistoryEntry();
+ explicit SessionHistoryEntry(SessionHistoryInfo* aInfo);
+ explicit SessionHistoryEntry(const SessionHistoryEntry& aEntry);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SESSIONHISTORYENTRY_IID)
+
+ void ReplaceWith(const SessionHistoryEntry& aSource);
+
+ const SessionHistoryInfo& Info() const { return *mInfo; }
+
+ SHEntrySharedParentState* SharedInfo() const;
+
+ void AddChild(SessionHistoryEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes);
+ void RemoveChild(SessionHistoryEntry* aChild);
+ // Finds the child with the same docshell ID as aNewChild, replaces it with
+ // aNewChild and returns true. If there is no child with the same docshell ID
+ // then it returns false.
+ bool ReplaceChild(SessionHistoryEntry* aNewChild);
+
+ void SetInfo(SessionHistoryInfo* aInfo);
+
+ bool ForInitialLoad() { return mForInitialLoad; }
+ void SetForInitialLoad(bool aForInitialLoad) {
+ mForInitialLoad = aForInitialLoad;
+ }
+
+ const nsID& DocshellID() const;
+
+ void SetIsDynamicallyAdded(bool aDynamic);
+
+ // Get an entry based on LoadingSessionHistoryInfo's mLoadId. Parent process
+ // only.
+ static SessionHistoryEntry* GetByLoadId(uint64_t aLoadId);
+ static void SetByLoadId(uint64_t aLoadId, SessionHistoryEntry* aEntry);
+ static void RemoveLoadId(uint64_t aLoadId);
+
+ const nsTArray<RefPtr<SessionHistoryEntry>>& Children() { return mChildren; }
+
+ private:
+ friend struct LoadingSessionHistoryInfo;
+ virtual ~SessionHistoryEntry();
+
+ UniquePtr<SessionHistoryInfo> mInfo;
+ nsISHEntry* mParent = nullptr;
+ uint32_t mID;
+ nsTArray<RefPtr<SessionHistoryEntry>> mChildren;
+
+ bool mForInitialLoad = false;
+
+ static nsDataHashtable<nsUint64HashKey, SessionHistoryEntry*>* sLoadIdToEntry;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SessionHistoryEntry, NS_SESSIONHISTORYENTRY_IID)
+
+} // namespace dom
+
+namespace ipc {
+
+class IProtocol;
+
+// Allow sending SessionHistoryInfo objects over IPC.
+template <>
+struct IPDLParamTraits<dom::SessionHistoryInfo> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const dom::SessionHistoryInfo& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, dom::SessionHistoryInfo* aResult);
+};
+
+// Allow sending LoadingSessionHistoryInfo objects over IPC.
+template <>
+struct IPDLParamTraits<dom::LoadingSessionHistoryInfo> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const dom::LoadingSessionHistoryInfo& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, dom::LoadingSessionHistoryInfo* aResult);
+};
+
+// Allow sending nsILayoutHistoryState objects over IPC.
+template <>
+struct IPDLParamTraits<nsILayoutHistoryState*> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ nsILayoutHistoryState* aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, RefPtr<nsILayoutHistoryState>* aResult);
+};
+
+} // namespace ipc
+
+} // namespace mozilla
+
+#endif /* mozilla_dom_SessionHistoryEntry_h */
diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build
new file mode 100644
index 0000000000..2c4187d070
--- /dev/null
+++ b/docshell/shistory/moz.build
@@ -0,0 +1,45 @@
+# -*- 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/.
+
+XPIDL_SOURCES += [
+ "nsIBFCacheEntry.idl",
+ "nsISHEntry.idl",
+ "nsISHistory.idl",
+ "nsISHistoryListener.idl",
+]
+
+XPIDL_MODULE = "shistory"
+
+EXPORTS += [
+ "nsSHEntry.h",
+ "nsSHEntryShared.h",
+ "nsSHistory.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "ChildSHistory.h",
+ "SessionHistoryEntry.h",
+]
+
+UNIFIED_SOURCES += [
+ "ChildSHistory.cpp",
+ "nsSHEntry.cpp",
+ "nsSHEntryShared.cpp",
+ "nsSHistory.cpp",
+ "SessionHistoryEntry.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/docshell/shistory/nsIBFCacheEntry.idl b/docshell/shistory/nsIBFCacheEntry.idl
new file mode 100644
index 0000000000..2e24c67e35
--- /dev/null
+++ b/docshell/shistory/nsIBFCacheEntry.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface lets you evict a document from the back/forward cache.
+ */
+[scriptable, builtinclass, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
+interface nsIBFCacheEntry : nsISupports
+{
+ void RemoveFromBFCacheSync();
+ void RemoveFromBFCacheAsync();
+};
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
new file mode 100644
index 0000000000..af5b3f4b4a
--- /dev/null
+++ b/docshell/shistory/nsISHEntry.idl
@@ -0,0 +1,450 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/**
+ * The interface to nsISHentry. Each document or subframe in
+ * Session History will have a nsISHEntry associated with it which will
+ * hold all information required to recreate the document from history
+ */
+
+#include "nsISupports.idl"
+
+interface nsIContentSecurityPolicy;
+interface nsIMutableArray;
+interface nsILayoutHistoryState;
+interface nsIContentViewer;
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDocShellTreeItem;
+interface nsIStructuredCloneContainer;
+interface nsIBFCacheEntry;
+interface nsIPrincipal;
+interface nsISHistory;
+interface nsIReferrerInfo;
+
+%{C++
+#include "nsRect.h"
+class nsDocShellEditorData;
+
+namespace mozilla {
+namespace dom {
+
+class SHEntrySharedParentState;
+
+}
+}
+class nsSHEntryShared;
+class nsDocShellLoadState;
+struct EntriesAndBrowsingContextData;
+%}
+[ref] native nsIntRect(nsIntRect);
+[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
+[ptr] native nsDocShellLoadStatePtr(nsDocShellLoadState);
+webidl BrowsingContext;
+
+[builtinclass, scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
+interface nsISHEntry : nsISupports
+{
+ /**
+ * The URI of the current entry.
+ */
+ [infallible] attribute nsIURI URI;
+
+ /**
+ * The original URI of the current entry. If an entry is the result of a
+ * redirect this attribute holds the original URI.
+ */
+ [infallible] attribute nsIURI originalURI;
+
+ /**
+ * URL as stored from nsILoadInfo.resultPrincipalURI. See nsILoadInfo
+ * for more details.
+ */
+ [infallible] attribute nsIURI resultPrincipalURI;
+
+ /**
+ * This flag remembers whether channel has LOAD_REPLACE set.
+ */
+ [infallible] attribute boolean loadReplace;
+
+ /**
+ * The title of the current entry.
+ */
+ // XXX: make it [infallible] when AString supports that (bug 1491187).
+ attribute AString title;
+
+ /**
+ * The name of the browsing context.
+ */
+ attribute AString name;
+
+ /**
+ * Was the entry created as a result of a subframe navigation?
+ * - Will be 'false' when a frameset page is visited for the first time.
+ * - Will be 'true' for all history entries created as a result of a
+ * subframe navigation.
+ */
+ [infallible] attribute boolean isSubFrame;
+
+ /**
+ * Whether the user interacted with the page while this entry was active.
+ * This includes interactions with subframe documents associated with
+ * child entries that are rooted at this entry.
+ * This field will only be set on top-level entries.
+ */
+ [infallible] attribute boolean hasUserInteraction;
+
+ /** Referrer Info*/
+ [infallible] attribute nsIReferrerInfo referrerInfo;
+
+ /** Content viewer, for fast restoration of presentation */
+ [infallible] attribute nsIContentViewer contentViewer;
+
+ /** Whether the content viewer is marked "sticky" */
+ [infallible] attribute boolean sticky;
+
+ /** Saved state of the global window object */
+ [infallible] attribute nsISupports windowState;
+
+ /** Saved refresh URI list for the content viewer */
+ [infallible] attribute nsIMutableArray refreshURIList;
+
+ /** Post Data for the document */
+ [infallible] attribute nsIInputStream postData;
+
+ /** LayoutHistoryState for scroll position and form values */
+ [infallible] attribute nsILayoutHistoryState layoutHistoryState;
+
+ /** parent of this entry */
+ [infallible] attribute nsISHEntry parent;
+
+ /**
+ * The loadType for this entry. This is typically loadHistory except
+ * when reload is pressed, it has the appropriate reload flag
+ */
+ [infallible] attribute unsigned long loadType;
+
+ /**
+ * An ID to help identify this entry from others during
+ * subframe navigation
+ */
+ [infallible] attribute unsigned long ID;
+
+ /** The cache key for the entry */
+ [infallible] attribute unsigned long cacheKey;
+
+ /** Should the layoutHistoryState be saved? */
+ [infallible] attribute boolean saveLayoutStateFlag;
+
+ /**
+ * attribute to indicate the content-type of the document that this
+ * is a session history entry for
+ */
+ // XXX: make it [infallible] when ACString supports that (bug 1491187).
+ attribute ACString contentType;
+
+ /**
+ * If we created this SHEntry via history.pushState or modified it via
+ * history.replaceState, and if we changed the SHEntry's URI via the
+ * push/replaceState call, and if the SHEntry's new URI differs from its
+ * old URI by more than just the hash, then we set this field to true.
+ *
+ * Additionally, if this SHEntry was created by calling pushState from a
+ * SHEntry whose URI was modified, this SHEntry's URIWasModified field is
+ * true.
+ */
+ [infallible] attribute boolean URIWasModified;
+
+ /**
+ * Get the principal, if any, that was associated with the channel
+ * that the document that was loaded to create this history entry
+ * came from.
+ */
+ [infallible] attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * Get the principal, if any, that is used when the inherit flag
+ * is set.
+ */
+ [infallible] attribute nsIPrincipal principalToInherit;
+
+ /**
+ * Get the storage principal, if any, that is used when the inherit flag is
+ * set.
+ */
+ [infallible] attribute nsIPrincipal partitionedPrincipalToInherit;
+
+ /**
+ * Get the csp, if any, that was used for this document load. That
+ * is not the CSP that was applied to subresource loads within the
+ * document, but the CSP that was applied to this document load.
+ */
+ [infallible] attribute nsIContentSecurityPolicy csp;
+
+ /**
+ * Get/set data associated with this history state via a pushState() call,
+ * serialized using structured clone.
+ **/
+ [infallible] attribute nsIStructuredCloneContainer stateData;
+
+ /**
+ * The history ID of the docshell.
+ */
+ // Would be [infallible], but we don't support that property for nsIDPtr.
+ attribute nsIDRef docshellID;
+
+ /**
+ * True if this SHEntry corresponds to a document created by a srcdoc
+ * iframe. Set when a value is assigned to srcdocData.
+ */
+ [infallible] readonly attribute boolean isSrcdocEntry;
+
+ /**
+ * Contents of the srcdoc attribute in a srcdoc iframe to be loaded instead
+ * of the URI. Similar to a Data URI, this information is needed to
+ * recreate the document at a later stage.
+ * Setting this sets isSrcdocEntry to true
+ */
+ // XXX: make it [infallible] when AString supports that (bug 1491187).
+ attribute AString srcdocData;
+
+ /**
+ * When isSrcdocEntry is true, this contains the baseURI of the srcdoc
+ * document for use in situations where it cannot otherwise be determined,
+ * for example with view-source.
+ */
+ [infallible] attribute nsIURI baseURI;
+
+ /**
+ * Sets/gets the current scroll restoration state,
+ * if true == "manual", false == "auto".
+ */
+ [infallible] attribute boolean scrollRestorationIsManual;
+
+ /**
+ * Flag to indicate that the history entry was originally loaded in the
+ * current process. This flag does not survive a browser process switch.
+ */
+ [infallible] readonly attribute boolean loadedInThisProcess;
+
+ /**
+ * The session history it belongs to. This is set only on the root entries.
+ */
+ [noscript, infallible] attribute nsISHistory shistory;
+
+ /**
+ * A number that is assigned by the sHistory when the entry is activated
+ */
+ [noscript, infallible] attribute unsigned long lastTouched;
+
+ /**
+ * The current number of nsISHEntries which are immediate children of this
+ * SHEntry.
+ */
+ [infallible] readonly attribute long childCount;
+
+ /**
+ * When an entry is serving is within nsISHistory's array of entries, this
+ * property specifies if it should persist. If not it will be replaced by
+ * new additions to the list.
+ */
+ [infallible] attribute boolean persist;
+
+ /**
+ * Set/Get the visual viewport scroll position if session history is
+ * changed through anchor navigation or pushState.
+ */
+ void setScrollPosition(in long x, in long y);
+ void getScrollPosition(out long x, out long y);
+
+ /**
+ * Saved position and dimensions of the content viewer; we must adjust the
+ * root view's widget accordingly if this has changed when the presentation
+ * is restored.
+ */
+ [noscript, notxpcom] void getViewerBounds(in nsIntRect bounds);
+ [noscript, notxpcom] void setViewerBounds([const] in nsIntRect bounds);
+
+ /**
+ * Saved child docshells corresponding to contentViewer. The child shells
+ * are restored as children of the parent docshell, in this order, when the
+ * parent docshell restores a saved presentation.
+ */
+
+ /** Append a child shell to the end of our list. */
+ [noscript, notxpcom] void addChildShell(in nsIDocShellTreeItem shell);
+
+ /**
+ * Get the child shell at |index|; returns null if |index| is out of bounds.
+ */
+ [noscript] nsIDocShellTreeItem childShellAt(in long index);
+
+ /**
+ * Clear the child shell list.
+ */
+ [noscript, notxpcom] void clearChildShells();
+
+ /**
+ * Ensure that the cached presentation members are self-consistent.
+ * If either |contentViewer| or |windowState| are null, then all of the
+ * following members are cleared/reset:
+ * contentViewer, sticky, windowState, viewerBounds, childShells,
+ * refreshURIList.
+ */
+ [noscript, notxpcom] void syncPresentationState();
+
+ /**
+ * Initialises `layoutHistoryState` if it doesn't already exist
+ * and returns a reference to it.
+ */
+ nsILayoutHistoryState initLayoutHistoryState();
+
+ /** Additional ways to create an entry */
+ [noscript] void create(in nsIURI URI, in AString title,
+ in nsIInputStream inputStream,
+ in unsigned long cacheKey,
+ in ACString contentType,
+ in nsIPrincipal triggeringPrincipal,
+ in nsIPrincipal principalToInherit,
+ in nsIPrincipal partitionedPrincipalToInherit,
+ in nsIContentSecurityPolicy aCsp,
+ in nsIDRef docshellID,
+ in boolean dynamicCreation,
+ in nsIURI originalURI,
+ in nsIURI resultPrincipalURI,
+ in bool loadReplace,
+ in nsIReferrerInfo referrerInfo,
+ in AString srcdoc,
+ in bool srcdocEntry,
+ in nsIURI baseURI,
+ in bool saveLayoutState,
+ in bool expired);
+
+ nsISHEntry clone();
+
+ /**
+ * Gets the owning pointer to the editor data assosicated with
+ * this shistory entry. This forgets its pointer, so free it when
+ * you're done.
+ */
+ [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData();
+
+ /**
+ * Sets the owning pointer to the editor data assosicated with
+ * this shistory entry. Unless forgetEditorData() is called, this
+ * shentry will destroy the editor data when it's destroyed.
+ */
+ [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData);
+
+ /** Returns true if this shistory entry is storing a detached editor. */
+ [noscript, notxpcom] boolean hasDetachedEditor();
+
+ /**
+ * Returns true if the related docshell was added because of
+ * dynamic addition of an iframe/frame.
+ */
+ [noscript, notxpcom] boolean isDynamicallyAdded();
+
+ /**
+ * Returns true if any of the child entries returns true
+ * when isDynamicallyAdded is called on it.
+ */
+ boolean hasDynamicallyAddedChild();
+
+ /**
+ * Does this SHEntry point to the given BFCache entry? If so, evicting
+ * the BFCache entry will evict the SHEntry, since the two entries
+ * correspond to the same document.
+ */
+ [noscript, notxpcom] boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
+ * aEntry's BFCacheEntry.
+ */
+ void adoptBFCacheEntry(in nsISHEntry aEntry);
+
+ /**
+ * Create a new BFCache entry and drop our reference to our old one. This
+ * call unlinks this SHEntry from any other SHEntries for its document.
+ */
+ void abandonBFCacheEntry();
+
+ /**
+ * Does this SHEntry correspond to the same document as aEntry? This is
+ * true iff the two SHEntries have the same BFCacheEntry. So in particular,
+ * sharesDocumentWith(aEntry) is guaranteed to return true if it's
+ * preceded by a call to adoptBFCacheEntry(aEntry).
+ */
+ boolean sharesDocumentWith(in nsISHEntry aEntry);
+
+ /**
+ * Sets an SHEntry to reflect that it is a history type load. As
+ * nsIDocShellLoadInfo and its LoadType enum were removed, this is the
+ * equivalent to doing
+ *
+ * shEntry.loadType = 4;
+ *
+ * in js, but easier to maintain and less opaque.
+ */
+ void setLoadTypeAsHistory();
+
+ /**
+ * Add a new child SHEntry. If offset is -1 adds to the end of the list.
+ */
+ void AddChild(in nsISHEntry aChild, in long aOffset,
+ [optional,default(false)] in bool aUseRemoteSubframes);
+
+ /**
+ * Remove a child SHEntry.
+ */
+ [noscript] void RemoveChild(in nsISHEntry aChild);
+
+ /**
+ * Get child at an index.
+ */
+ nsISHEntry GetChildAt(in long aIndex);
+
+ /**
+ * If this entry has no dynamically added child, get the child SHEntry
+ * at the given offset. The loadtype of the returned entry is set
+ * to its parent's loadtype.
+ */
+ [notxpcom] void GetChildSHEntryIfHasNoDynamicallyAddedChild(in long aChildOffset,
+ out nsISHEntry aChild);
+
+ /**
+ * Replaces a child which is for the same docshell as aNewChild
+ * with aNewChild.
+ * @throw if nothing was replaced.
+ */
+ [noscript] void ReplaceChild(in nsISHEntry aNewChild);
+
+ /**
+ * Remove all children of this entry and call abandonBFCacheEntry.
+ */
+ [notxpcom] void ClearEntry();
+
+ /**
+ * Create nsDocShellLoadState and fill it with information.
+ * Don't set nsSHEntry here to avoid serializing it.
+ */
+ [noscript] nsDocShellLoadStatePtr CreateLoadInfo();
+
+ [infallible] readonly attribute unsigned long long bfcacheID;
+
+ /**
+ * Sync up the docshell and session history trees for subframe navigation.
+ *
+ * @param aEntry new entry
+ * @param aTopBC top BC corresponding to the root ancestor
+ of the docshell that called this method
+ * @param aIgnoreBC current BC
+ */
+ [notxpcom] void SyncTreesForSubframeNavigation(in nsISHEntry aEntry,
+ in BrowsingContext aTopBC,
+ in BrowsingContext aIgnoreBC);
+};
+
diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl
new file mode 100644
index 0000000000..1f5b9c5477
--- /dev/null
+++ b/docshell/shistory/nsISHistory.idl
@@ -0,0 +1,284 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIBFCacheEntry;
+interface nsISHEntry;
+interface nsISHistoryListener;
+interface nsIURI;
+webidl BrowsingContext;
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+#include "mozilla/Maybe.h"
+struct EntriesAndBrowsingContextData;
+%}
+
+[ref] native nsDocshellIDArray(nsTArray<nsID>);
+native MaybeInt32(mozilla::Maybe<int32_t>);
+/**
+ * An interface to the primary properties of the Session History
+ * component. In an embedded browser environment, the nsIWebBrowser
+ * object creates an instance of session history for each open window.
+ * A handle to the session history object can be obtained from
+ * nsIWebNavigation. In a non-embedded situation, the owner of the
+ * session history component must create a instance of it and set
+ * it in the nsIWebNavigation object.
+ * This interface is accessible from javascript.
+ */
+
+[builtinclass, scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
+interface nsISHistory: nsISupports
+{
+ /**
+ * A readonly property of the interface that returns
+ * the number of toplevel documents currently available
+ * in session history.
+ */
+ [infallible] readonly attribute long count;
+
+ /**
+ * The index of the current document in session history. Not infallible
+ * because setting can fail if the assigned value is out of range.
+ */
+ attribute long index;
+
+ /**
+ * A readonly property of the interface that returns
+ * the index of the last document that started to load and
+ * didn't finished yet. When document finishes the loading
+ * value -1 is returned.
+ */
+ [infallible] readonly attribute long requestedIndex;
+
+ /**
+ * Artifically set the |requestedIndex| for this nsISHEntry to the given
+ * index. This is used when resuming a cross-process load from a different
+ * process.
+ */
+ [noscript, notxpcom]
+ void internalSetRequestedIndex(in long aRequestedIndex);
+
+ /**
+ * Get the history entry at a given index. Returns non-null on success.
+ *
+ * @param index The index value whose entry is requested.
+ * The oldest entry is located at index == 0.
+ * @return The found entry; never null.
+ */
+ nsISHEntry getEntryAtIndex(in long aIndex);
+
+ /**
+ * Called to purge older documents from history.
+ * Documents can be removed from session history for various
+ * reasons. For example to control memory usage of the browser, to
+ * prevent users from loading documents from history, to erase evidence of
+ * prior page loads etc...
+ *
+ * @param numEntries The number of toplevel documents to be
+ * purged from history. During purge operation,
+ * the latest documents are maintained and older
+ * 'numEntries' documents are removed from history.
+ * @throws <code>NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA</code>
+ * Purge was vetod.
+ * @throws <code>NS_ERROR_FAILURE</code> numEntries is
+ * invalid or out of bounds with the size of history.
+ */
+ void purgeHistory(in long aNumEntries);
+
+ /**
+ * Called to register a listener for the session history component.
+ * Listeners are notified when pages are loaded or purged from history.
+ *
+ * @param aListener Listener object to be notified for all
+ * page loads that initiate in session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ *
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void addSHistoryListener(in nsISHistoryListener aListener);
+
+ /**
+ * Called to remove a listener for the session history component.
+ * Listeners are notified when pages are loaded from history.
+ *
+ * @param aListener Listener object to be removed from
+ * session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void removeSHistoryListener(in nsISHistoryListener aListener);
+
+ void reloadCurrentEntry();
+
+ /**
+ * Load the entry at the particular index.
+ */
+ [noscript]
+ void gotoIndex(in long aIndex);
+
+ /**
+ * If an element exists at the particular index and
+ * whether it has user interaction.
+ */
+ [noscript,notxpcom]
+ boolean hasUserInteractionAtIndex(in long aIndex);
+
+ /**
+ * Called to obtain the index to a given history entry.
+ *
+ * @param aEntry The entry to obtain the index of.
+ *
+ * @return <code>NS_OK</code> index for the history entry
+ * is obtained successfully.
+ * <code>NS_ERROR_FAILURE</code> Error in obtaining
+ * index for the given history entry.
+ */
+ [noscript, notxpcom]
+ long getIndexOfEntry(in nsISHEntry aEntry);
+
+ /**
+ * Add a new Entry to the History List.
+ *
+ * @param aEntry The entry to add.
+ * @param aPersist If true this specifies that the entry should
+ * persist in the list. If false, this means that
+ * when new entries are added this element will not
+ * appear in the session history list.
+ */
+ void addEntry(in nsISHEntry aEntry, in boolean aPersist);
+
+ /**
+ * Update the index maintained by sessionHistory
+ */
+ void updateIndex();
+
+ /**
+ * Replace the nsISHEntry at a particular index
+ *
+ * @param aIndex The index at which the entry should be replaced.
+ * @param aReplaceEntry The replacement entry for the index.
+ */
+ void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
+
+ /**
+ * Notifies all registered session history listeners about an impending
+ * reload.
+ *
+ * @return Whether the operation can proceed.
+ */
+ boolean notifyOnHistoryReload();
+
+ /**
+ * Evict content viewers which don't lie in the "safe" range around aIndex.
+ * In practice, this should leave us with no more than gHistoryMaxViewers
+ * viewers associated with this SHistory object.
+ *
+ * Also make sure that the total number of content viewers in all windows is
+ * not greater than our global max; if it is, evict viewers as appropriate.
+ *
+ * @param aIndex The index around which the "safe" range is
+ * centered. In general, if you just navigated the
+ * history, aIndex should be the index history was
+ * navigated to.
+ */
+ void evictOutOfRangeContentViewers(in long aIndex);
+
+ /**
+ * Evict the content viewer associated with a bfcache entry that has timed
+ * out.
+ */
+ void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Evict all the content viewers in this session history
+ */
+ void evictAllContentViewers();
+
+ /**
+ * Add a BFCache entry to expiration tracker so it gets evicted on
+ * expiration.
+ */
+ [noscript, notxpcom]
+ void addToExpirationTracker(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Remove a BFCache entry from expiration tracker.
+ */
+ [noscript, notxpcom]
+ void removeFromExpirationTracker(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Remove dynamic entries found at given index.
+ *
+ * @param aIndex Index to remove dynamic entries from. It will be
+ * passed to RemoveEntries as aStartIndex.
+ * @param aEntry (optional) The entry to start looking in for dynamic
+ * entries. Only the dynamic descendants of the
+ * entry will be removed. If not given, all dynamic
+ * entries at the index will be removed.
+ */
+ [noscript, notxpcom]
+ void RemoveDynEntries(in long aIndex, in nsISHEntry aEntry);
+
+ /**
+ * Similar to RemoveDynEntries, but instead of specifying an index, use the
+ * given BFCacheEntry to find the index and remove dynamic entries from the
+ * index.
+ *
+ * The method takes no effect if the bfcache entry is not or no longer hold
+ * by the SHistory instance.
+ *
+ * @param aEntry The bfcache entry to look up for index to remove
+ * dynamic entries from.
+ */
+ [noscript, notxpcom]
+ void RemoveDynEntriesForBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Removes entries from the history if their docshellID is in
+ * aIDs array.
+ */
+ [noscript, notxpcom]
+ void RemoveEntries(in nsDocshellIDArray aIDs, in long aStartIndex);
+
+ /**
+ * Collect docshellIDs from aEntry's children and remove those
+ * entries from history.
+ *
+ * @param aEntry Children docshellID's will be collected from
+ * this entry and passed to RemoveEntries as aIDs.
+ */
+ [noscript, notxpcom]
+ void RemoveFrameEntries(in nsISHEntry aEntry);
+
+ [noscript]
+ void Reload(in unsigned long aReloadFlags);
+
+ [notxpcom] void EnsureCorrectEntryAtCurrIndex(in nsISHEntry aEntry);
+
+ [notxpcom] void EvictContentViewersOrReplaceEntry(in nsISHEntry aNewSHEntry, in bool aReplace);
+
+ nsISHEntry createEntry();
+
+ [noscript] void AddToRootSessionHistory(in bool aCloneChildren, in nsISHEntry aOSHE,
+ in BrowsingContext aRootBC, in nsISHEntry aEntry,
+ in unsigned long aLoadType,
+ in bool aShouldPersist,
+ out MaybeInt32 aPreviousEntryIndex,
+ out MaybeInt32 aLoadedEntryIndex);
+
+ [noscript] void AddChildSHEntryHelper(in nsISHEntry aCloneRef, in nsISHEntry aNewEntry,
+ in BrowsingContext aRootBC, in bool aCloneChildren);
+
+ [noscript, notxpcom] boolean isEmptyOrHasEntriesForSingleTopLevelPage();
+};
diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl
new file mode 100644
index 0000000000..a273da2182
--- /dev/null
+++ b/docshell/shistory/nsISHistoryListener.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsISHistoryListener defines the interface one can implement to receive
+ * notifications about activities in session history and (for reloads) to be
+ * able to cancel them.
+ *
+ * A session history listener will be notified when pages are added, removed
+ * and loaded from session history. In the case of reloads, it can prevent them
+ * from happening by returning false from the corresponding callback method.
+ *
+ * A session history listener can be registered on a particular nsISHistory
+ * instance via the nsISHistory::addSHistoryListener() method.
+ *
+ * Listener methods should not alter the session history. Things are likely to
+ * go haywire if they do.
+ */
+[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)]
+interface nsISHistoryListener : nsISupports
+{
+ /**
+ * Called when a new document is added to session history. New documents are
+ * added to session history by docshell when new pages are loaded in a frame
+ * or content area, for example via nsIWebNavigation::loadURI()
+ *
+ * @param aNewURI The URI of the document to be added to session history.
+ * @param aOldIndex The index of the current history item before the
+ * operation.
+ */
+ void OnHistoryNewEntry(in nsIURI aNewURI, in long aOldIndex);
+
+ /**
+ * Called before the current document is reloaded, for example due to a
+ * nsIWebNavigation::reload() call.
+ */
+ boolean OnHistoryReload();
+
+ /**
+ * Called before navigating to a session history entry by index, for example,
+ * when nsIWebNavigation::gotoIndex() is called.
+ */
+ void OnHistoryGotoIndex();
+
+ /**
+ * Called before entries are removed from session history. Entries can be
+ * removed from session history for various reasons, for example to control
+ * the memory usage of the browser, to prevent users from loading documents
+ * from history, to erase evidence of prior page loads, etc.
+ *
+ * To purge documents from session history call nsISHistory::PurgeHistory().
+ */
+ void OnHistoryPurge();
+
+ /**
+ * Called before an entry is replaced in the session history. Entries are
+ * replaced when navigating away from non-persistent history entries (such as
+ * about pages) and when history.replaceState is called.
+ */
+ void OnHistoryReplaceEntry();
+
+
+ /**
+ * Called whenever a content viewer is evicted. A content viewer is evicted
+ * whenever a bfcache entry has timed out or the number of total content
+ * viewers has exceeded the global max. This is used for testing only.
+ *
+ * @param aNumEvicted - number of content viewers evicted
+ */
+ void OnContentViewerEvicted(in unsigned long aNumEvicted);
+};
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
new file mode 100644
index 0000000000..0b4af1b95b
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -0,0 +1,1076 @@
+/* -*- 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 "nsSHEntry.h"
+
+#include <algorithm>
+
+#include "nsDocShell.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInputStream.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIMutableArray.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIURI.h"
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+
+#include "mozilla/Logging.h"
+#include "nsIReferrerInfo.h"
+
+extern mozilla::LazyLogModule gPageCacheLog;
+
+static uint32_t gEntryID = 0;
+
+nsSHEntry::nsSHEntry()
+ : mShared(new nsSHEntryShared()),
+ mLoadType(0),
+ mID(++gEntryID), // SessionStore has special handling for 0 values.
+ mScrollPositionX(0),
+ mScrollPositionY(0),
+ mParent(nullptr),
+ mLoadReplace(false),
+ mURIWasModified(false),
+ mIsSrcdocEntry(false),
+ mScrollRestorationIsManual(false),
+ mLoadedInThisProcess(false),
+ mPersist(true),
+ mHasUserInteraction(false) {}
+
+nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
+ : mShared(aOther.mShared),
+ mURI(aOther.mURI),
+ mOriginalURI(aOther.mOriginalURI),
+ mResultPrincipalURI(aOther.mResultPrincipalURI),
+ mReferrerInfo(aOther.mReferrerInfo),
+ mTitle(aOther.mTitle),
+ mPostData(aOther.mPostData),
+ mLoadType(0) // XXX why not copy?
+ ,
+ mID(aOther.mID),
+ mScrollPositionX(0) // XXX why not copy?
+ ,
+ mScrollPositionY(0) // XXX why not copy?
+ ,
+ mParent(aOther.mParent),
+ mStateData(aOther.mStateData),
+ mSrcdocData(aOther.mSrcdocData),
+ mBaseURI(aOther.mBaseURI),
+ mLoadReplace(aOther.mLoadReplace),
+ mURIWasModified(aOther.mURIWasModified),
+ mIsSrcdocEntry(aOther.mIsSrcdocEntry),
+ mScrollRestorationIsManual(false),
+ mLoadedInThisProcess(aOther.mLoadedInThisProcess),
+ mPersist(aOther.mPersist),
+ mHasUserInteraction(false) {}
+
+nsSHEntry::~nsSHEntry() {
+ // Null out the mParent pointers on all our kids.
+ for (nsISHEntry* entry : mChildren) {
+ if (entry) {
+ entry->SetParent(nullptr);
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSHEntry, nsISHEntry)
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY) {
+ mScrollPositionX = aX;
+ mScrollPositionY = aY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
+ *aX = mScrollPositionX;
+ *aY = mScrollPositionY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURIWasModified(bool* aOut) {
+ *aOut = mURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURIWasModified(bool aIn) {
+ mURIWasModified = aIn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURI(nsIURI** aURI) {
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURI(nsIURI* aURI) {
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI) {
+ *aOriginalURI = mOriginalURI;
+ NS_IF_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) {
+ *aResultPrincipalURI = mResultPrincipalURI;
+ NS_IF_ADDREF(*aResultPrincipalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadReplace(bool* aLoadReplace) {
+ *aLoadReplace = mLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadReplace(bool aLoadReplace) {
+ mLoadReplace = aLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ *aReferrerInfo = mReferrerInfo;
+ NS_IF_ADDREF(*aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSticky(bool aSticky) {
+ mShared->mSticky = aSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSticky(bool* aSticky) {
+ *aSticky = mShared->mSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTitle(nsAString& aTitle) {
+ // Check for empty title...
+ if (mTitle.IsEmpty() && mURI) {
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ AppendUTF8toUTF16(spec, mTitle);
+ }
+ }
+
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetName(nsAString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetName(const nsAString& aName) {
+ mName = aName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPostData(nsIInputStream** aResult) {
+ *aResult = mPostData;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPostData(nsIInputStream* aPostData) {
+ mPostData = aPostData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
+ *aResult = mShared->mLayoutHistoryState;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
+ mShared->mLayoutHistoryState = aState;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(
+ !mShared->mSaveLayoutState);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
+ if (!mShared->mLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ historyState = NS_NewLayoutHistoryState();
+ SetLayoutHistoryState(historyState);
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> state = GetLayoutHistoryState();
+ state.forget(aState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadType(uint32_t* aResult) {
+ *aResult = mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetID(uint32_t* aResult) {
+ *aResult = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetID(uint32_t aID) {
+ mID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSubFrame(bool* aFlag) {
+ *aFlag = mShared->mIsFrameNavigation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetIsSubFrame(bool aFlag) {
+ mShared->mIsFrameNavigation = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetHasUserInteraction(bool* aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ *aFlag = mHasUserInteraction;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->GetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetHasUserInteraction(bool aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ mHasUserInteraction = aFlag;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->SetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetCacheKey(uint32_t* aResult) {
+ *aResult = mShared->mCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetCacheKey(uint32_t aCacheKey) {
+ mShared->mCacheKey = aCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentType(nsACString& aContentType) {
+ aContentType = mShared->mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentType(const nsACString& aContentType) {
+ mShared->mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle,
+ nsIInputStream* aInputStream, uint32_t aCacheKey,
+ const nsACString& aContentType,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID,
+ bool aDynamicCreation, nsIURI* aOriginalURI,
+ nsIURI* aResultPrincipalURI, bool aLoadReplace,
+ nsIReferrerInfo* aReferrerInfo, const nsAString& aSrcdocData,
+ bool aSrcdocEntry, nsIURI* aBaseURI, bool aSaveLayoutState,
+ bool aExpired) {
+ MOZ_ASSERT(
+ aTriggeringPrincipal,
+ "need a valid triggeringPrincipal to create a session history entry");
+
+ mURI = aURI;
+ mTitle = aTitle;
+ mPostData = aInputStream;
+
+ // Set the LoadType by default to loadHistory during creation
+ mLoadType = LOAD_HISTORY;
+
+ mShared->mCacheKey = aCacheKey;
+ mShared->mContentType = aContentType;
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ mShared->mCsp = aCsp;
+ mShared->mDocShellID = aDocShellID;
+ mShared->mDynamicallyCreated = aDynamicCreation;
+
+ // By default all entries are set false for subframe flag.
+ // nsDocShell::CloneAndReplace() which creates entries for
+ // all subframe navigations, sets the flag to true.
+ mShared->mIsFrameNavigation = false;
+
+ mHasUserInteraction = false;
+
+ mShared->mExpired = aExpired;
+
+ mIsSrcdocEntry = aSrcdocEntry;
+ mSrcdocData = aSrcdocData;
+
+ mBaseURI = aBaseURI;
+
+ mLoadedInThisProcess = true;
+
+ mOriginalURI = aOriginalURI;
+ mResultPrincipalURI = aResultPrincipalURI;
+ mLoadReplace = aLoadReplace;
+ mReferrerInfo = aReferrerInfo;
+
+ mShared->mLayoutHistoryState = nullptr;
+
+ mShared->mSaveLayoutState = aSaveLayoutState;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetParent(nsISHEntry** aResult) {
+ *aResult = mParent;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetParent(nsISHEntry* aParent) {
+ /* parent not Addrefed on purpose to avoid cyclic reference
+ * Null parent is OK
+ *
+ * XXX this method should not be scriptable if this is the case!!
+ */
+ mParent = aParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) {
+ mShared->mViewerBounds = aBounds;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::GetViewerBounds(nsIntRect& aBounds) {
+ aBounds = mShared->mViewerBounds;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) {
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPartitionedPrincipalToInherit(
+ nsIPrincipal** aPartitionedPrincipalToInherit) {
+ NS_IF_ADDREF(*aPartitionedPrincipalToInherit =
+ mShared->mPartitionedPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ NS_IF_ADDREF(*aCsp = mShared->mCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mShared->mCsp = aCsp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) {
+ nsSHEntryShared* shared = static_cast<nsSHEntry*>(aEntry)->mShared;
+ NS_ENSURE_STATE(shared);
+
+ mShared = shared;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) {
+ NS_ENSURE_ARG_POINTER(aOut);
+
+ *aOut = mShared == static_cast<nsSHEntry*>(aEntry)->mShared;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+ *aIsSrcdocEntry = mIsSrcdocEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocEntry = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBaseURI(nsIURI** aBaseURI) {
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual) {
+ *aIsManual = mScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollRestorationIsManual(bool aIsManual) {
+ mScrollRestorationIsManual = aIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+ *aLoadedInThisProcess = mLoadedInThisProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildCount(int32_t* aCount) {
+ *aCount = mChildren.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ if (aChild) {
+ NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE);
+ }
+
+ if (aOffset < 0) {
+ mChildren.AppendObject(aChild);
+ return NS_OK;
+ }
+
+ //
+ // Bug 52670: Ensure children are added in order.
+ //
+ // Later frames in the child list may load faster and get appended
+ // before earlier frames, causing session history to be scrambled.
+ // By growing the list here, they are added to the right position.
+ //
+ // Assert that aOffset will not be so high as to grow us a lot.
+ //
+ NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n");
+
+ bool newChildIsDyn = aChild ? aChild->IsDynamicallyAdded() : false;
+
+ // If the new child is dynamically added, try to add it to aOffset, but if
+ // there are non-dynamically added children, the child must be after those.
+ if (newChildIsDyn) {
+ int32_t lastNonDyn = aOffset - 1;
+ for (int32_t i = aOffset; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ break;
+ } else {
+ lastNonDyn = i;
+ }
+ }
+ }
+ // InsertObjectAt allows only appending one object.
+ // If aOffset is larger than Count(), we must first manually
+ // set the capacity.
+ if (aOffset > mChildren.Count()) {
+ mChildren.SetCount(aOffset);
+ }
+ if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) {
+ NS_WARNING("Adding a child failed!");
+ aChild->SetParent(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // If the new child isn't dynamically added, it should be set to aOffset.
+ // If there are dynamically added children before that, those must be
+ // moved to be after aOffset.
+ if (mChildren.Count() > 0) {
+ int32_t start = std::min(mChildren.Count() - 1, aOffset);
+ int32_t dynEntryIndex = -1;
+ nsISHEntry* dynEntry = nullptr;
+ for (int32_t i = start; i >= 0; --i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ dynEntryIndex = i;
+ dynEntry = entry;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (dynEntry) {
+ nsCOMArray<nsISHEntry> tmp;
+ tmp.SetCount(aOffset - dynEntryIndex + 1);
+ mChildren.InsertObjectsAt(tmp, dynEntryIndex);
+ NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
+ }
+ }
+
+ // Make sure there isn't anything at aOffset.
+ if (aOffset < mChildren.Count()) {
+ nsISHEntry* oldChild = mChildren[aOffset];
+ if (oldChild && oldChild != aChild) {
+ // Under Fission, this can happen when a network-created iframe starts
+ // out in-process, moves out-of-process, and then switches back. At that
+ // point, we'll create a new network-created DocShell at the same index
+ // where we already have an entry for the original network-created
+ // DocShell.
+ //
+ // This should ideally stop being an issue once the Fission-aware
+ // session history rewrite is complete.
+ NS_ASSERTION(
+ aUseRemoteSubframes,
+ "Adding a child where we already have a child? This may misbehave");
+ oldChild->SetParent(nullptr);
+ }
+ }
+
+ mChildren.ReplaceObjectAt(aChild, aOffset);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::RemoveChild(nsISHEntry* aChild) {
+ NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
+ bool childRemoved = false;
+ if (aChild->IsDynamicallyAdded()) {
+ childRemoved = mChildren.RemoveObject(aChild);
+ } else {
+ int32_t index = mChildren.IndexOfObject(aChild);
+ if (index >= 0) {
+ // Other alive non-dynamic child docshells still keep mChildOffset,
+ // so we don't want to change the indices here.
+ mChildren.ReplaceObjectAt(nullptr, index);
+ childRemoved = true;
+ }
+ }
+ if (childRemoved) {
+ aChild->SetParent(nullptr);
+
+ // reduce the child count, i.e. remove empty children at the end
+ for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) {
+ if (!mChildren.RemoveObjectAt(i)) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult) {
+ if (aIndex >= 0 && aIndex < mChildren.Count()) {
+ *aResult = mChildren[aIndex];
+ // yes, mChildren can have holes in it. AddChild's offset parameter makes
+ // that possible.
+ NS_IF_ADDREF(*aResult);
+ } else {
+ *aResult = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild(int32_t aChildOffset,
+ nsISHEntry** aChild) {
+ *aChild = nullptr;
+
+ bool dynamicallyAddedChild = false;
+ HasDynamicallyAddedChild(&dynamicallyAddedChild);
+ if (dynamicallyAddedChild) {
+ return;
+ }
+
+ // If the user did a shift-reload on this frameset page,
+ // we don't want to load the subframes from history.
+ if (IsForceReloadType(mLoadType) || mLoadType == LOAD_REFRESH) {
+ return;
+ }
+
+ /* Before looking for the subframe's url, check
+ * the expiration status of the parent. If the parent
+ * has expired from cache, then subframes will not be
+ * loaded from history in certain situations.
+ * If the user pressed reload and the parent frame has expired
+ * from cache, we do not want to load the child frame from history.
+ */
+ if (mShared->mExpired && (mLoadType == LOAD_RELOAD_NORMAL)) {
+ // The parent has expired. Return null.
+ *aChild = nullptr;
+ return;
+ }
+ // Get the child subframe from session history.
+ GetChildAt(aChildOffset, aChild);
+ if (*aChild) {
+ // Set the parent's Load Type on the child
+ (*aChild)->SetLoadType(mLoadType);
+ }
+}
+
+NS_IMETHODIMP
+nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) {
+ NS_ENSURE_STATE(aNewEntry);
+
+ nsID docshellID;
+ aNewEntry->GetDocshellID(docshellID);
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]) {
+ nsID childDocshellID;
+ nsresult rv = mChildren[i]->GetDocshellID(childDocshellID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (docshellID == childDocshellID) {
+ mChildren[i]->SetParent(nullptr);
+ mChildren.ReplaceObjectAt(aNewEntry, i);
+ return aNewEntry->SetParent(this);
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void) nsSHEntry::ClearEntry() {
+ int32_t childCount = GetChildCount();
+ // Remove all children of this entry
+ for (int32_t i = childCount - 1; i >= 0; i--) {
+ nsCOMPtr<nsISHEntry> child;
+ GetChildAt(i, getter_AddRefs(child));
+ RemoveChild(child);
+ }
+ AbandonBFCacheEntry();
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) {
+ NS_IF_ADDREF(*aContainer = mStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer) {
+ mStateData = aContainer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSHEntry::IsDynamicallyAdded() { return mShared->mDynamicallyCreated; }
+
+NS_IMETHODIMP
+nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) {
+ *aAdded = false;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ *aAdded = entry->IsDynamicallyAdded();
+ if (*aAdded) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetDocshellID(nsID& aID) {
+ aID = mShared->mDocShellID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetDocshellID(const nsID& aID) {
+ mShared->mDocShellID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLastTouched(uint32_t* aLastTouched) {
+ *aLastTouched = mShared->mLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLastTouched(uint32_t aLastTouched) {
+ mShared->mLastTouched = aLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetShistory(nsISHistory** aSHistory) {
+ nsCOMPtr<nsISHistory> shistory(do_QueryReferent(mShared->mSHistory));
+ shistory.forget(aSHistory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetShistory(nsISHistory* aSHistory) {
+ nsWeakPtr shistory = do_GetWeakReference(aSHistory);
+ // mSHistory can not be changed once it's set
+ MOZ_ASSERT(!mShared->mSHistory || (mShared->mSHistory == shistory));
+ mShared->mSHistory = shistory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadTypeAsHistory() {
+ // Set the LoadType by default to loadHistory during creation
+ mLoadType = LOAD_HISTORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPersist(bool* aPersist) {
+ *aPersist = mPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPersist(bool aPersist) {
+ mPersist = aPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
+ nsCOMPtr<nsIURI> uri = GetURI();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(uri));
+
+ nsCOMPtr<nsIURI> originalURI = GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ mozilla::Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI = GetResultPrincipalURI();
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+ loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
+
+ loadState->SetLoadReplace(GetLoadReplace());
+ nsCOMPtr<nsIInputStream> postData = GetPostData();
+ loadState->SetPostDataStream(postData);
+
+ nsAutoCString contentType;
+ GetContentType(contentType);
+ loadState->SetTypeHint(contentType);
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit = GetPrincipalToInherit();
+ loadState->SetPrincipalToInherit(principalToInherit);
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ GetPartitionedPrincipalToInherit();
+ loadState->SetPartitionedPrincipalToInherit(partitionedPrincipalToInherit);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = GetCsp();
+ loadState->SetCsp(csp);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = GetReferrerInfo();
+ loadState->SetReferrerInfo(referrerInfo);
+
+ // Do not inherit principal from document (security-critical!);
+ uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE;
+
+ // Passing nullptr as aSourceDocShell gives the same behaviour as before
+ // aSourceDocShell was introduced. According to spec we should be passing
+ // the source browsing context that was used when the history entry was
+ // first created. bug 947716 has been created to address this issue.
+ nsAutoString srcdoc;
+ nsCOMPtr<nsIURI> baseURI;
+ if (GetIsSrcdocEntry()) {
+ GetSrcdocData(srcdoc);
+ baseURI = GetBaseURI();
+ flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ } else {
+ srcdoc = VoidString();
+ }
+ loadState->SetSrcdocData(srcdoc);
+ loadState->SetBaseURI(baseURI);
+ loadState->SetLoadFlags(flags);
+
+ loadState->SetFirstParty(true);
+ loadState->SetSHEntry(this);
+
+ loadState.forget(aLoadState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SyncTreesForSubframeNavigation(
+ nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC,
+ mozilla::dom::BrowsingContext* aIgnoreBC) {
+ // XXX Keep this in sync with
+ // SessionHistoryEntry::SyncTreesForSubframeNavigation
+ //
+ // We need to sync up the browsing context and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the top browsing context, which will then recursively sync up all browsing
+ // contexts to their corresponding entries in the new session history tree. If
+ // we don't do this, then we can cache a content viewer on the wrong cloned
+ // entry, and subsequently restore it at the wrong time.
+ nsCOMPtr<nsISHEntry> newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
+ if (newRootEntry) {
+ // newRootEntry is now the new root entry.
+ // Find the old root entry as well.
+
+ // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
+ // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
+ nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(this);
+
+ if (oldRootEntry) {
+ nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr};
+ nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data);
+ }
+ }
+}
+
+void nsSHEntry::EvictContentViewer() {
+ nsCOMPtr<nsIContentViewer> viewer = GetContentViewer();
+ if (viewer) {
+ mShared->NotifyListenersContentViewerEvicted();
+ // Drop the presentation state before destroying the viewer, so that
+ // document teardown is able to correctly persist the state.
+ SetContentViewer(nullptr);
+ SyncPresentationState();
+ viewer->Destroy();
+ }
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) {
+ return GetState()->SetContentViewer(aViewer);
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentViewer(nsIContentViewer** aResult) {
+ *aResult = GetState()->mContentViewer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Clone(nsISHEntry** aResult) {
+ nsCOMPtr<nsISHEntry> entry = new nsSHEntry(*this);
+ entry.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag) {
+ *aFlag = mShared->mSaveLayoutState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) {
+ mShared->mSaveLayoutState = aFlag;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetWindowState(nsISupports* aState) {
+ GetState()->mWindowState = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetWindowState(nsISupports** aState) {
+ NS_IF_ADDREF(*aState = GetState()->mWindowState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetRefreshURIList(nsIMutableArray** aList) {
+ NS_IF_ADDREF(*aList = GetState()->mRefreshURIList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetRefreshURIList(nsIMutableArray* aList) {
+ GetState()->mRefreshURIList = aList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) {
+ MOZ_ASSERT(aShell, "Null child shell added to history entry");
+ GetState()->mChildShells.AppendObject(aShell);
+}
+
+NS_IMETHODIMP
+nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) {
+ NS_IF_ADDREF(*aShell = GetState()->mChildShells.SafeObjectAt(aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::ClearChildShells() { GetState()->mChildShells.Clear(); }
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SyncPresentationState() { GetState()->SyncPresentationState(); }
+
+nsDocShellEditorData* nsSHEntry::ForgetEditorData() {
+ // XXX jlebar Check how this is used.
+ return GetState()->mEditorData.release();
+}
+
+void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) {
+ NS_ASSERTION(!(aData && GetState()->mEditorData),
+ "We're going to overwrite an owning ref!");
+ if (GetState()->mEditorData != aData) {
+ GetState()->mEditorData = mozilla::WrapUnique(aData);
+ }
+}
+
+bool nsSHEntry::HasDetachedEditor() {
+ return GetState()->mEditorData != nullptr;
+}
+
+bool nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) {
+ return static_cast<nsIBFCacheEntry*>(GetState()) == aEntry;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AbandonBFCacheEntry() {
+ mShared = GetState()->Duplicate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBfcacheID(uint64_t* aBFCacheID) {
+ *aBFCacheID = mShared->GetId();
+ return NS_OK;
+}
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
new file mode 100644
index 0000000000..8c94726f09
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.h
@@ -0,0 +1,70 @@
+/* -*- 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 nsSHEntry_h
+#define nsSHEntry_h
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsISHEntry.h"
+#include "nsString.h"
+
+#include "mozilla/Attributes.h"
+
+class nsSHEntryShared;
+class nsIInputStream;
+class nsIURI;
+class nsIReferrerInfo;
+
+class nsSHEntry : public nsISHEntry {
+ public:
+ nsSHEntry();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+
+ virtual void EvictContentViewer();
+
+ static nsresult Startup();
+ static void Shutdown();
+
+ nsSHEntryShared* GetState() { return mShared; }
+
+ protected:
+ explicit nsSHEntry(const nsSHEntry& aOther);
+ virtual ~nsSHEntry();
+
+ // We share the state in here with other SHEntries which correspond to the
+ // same document.
+ RefPtr<nsSHEntryShared> mShared;
+
+ // See nsSHEntry.idl for comments on these members.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType;
+ uint32_t mID;
+ int32_t mScrollPositionX;
+ int32_t mScrollPositionY;
+ nsISHEntry* mParent;
+ nsCOMArray<nsISHEntry> mChildren;
+ nsCOMPtr<nsIStructuredCloneContainer> mStateData;
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+ bool mLoadReplace;
+ bool mURIWasModified;
+ bool mIsSrcdocEntry;
+ bool mScrollRestorationIsManual;
+ bool mLoadedInThisProcess;
+ bool mPersist;
+ bool mHasUserInteraction;
+};
+
+#endif /* nsSHEntry_h */
diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp
new file mode 100644
index 0000000000..2ee39b1d90
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -0,0 +1,311 @@
+/* -*- 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 "nsSHEntryShared.h"
+
+#include "nsArray.h"
+#include "nsContentUtils.h"
+#include "nsDocShellEditorData.h"
+#include "nsIContentViewer.h"
+#include "nsISHistory.h"
+#include "mozilla/dom/Document.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIWebNavigation.h"
+#include "nsSHistory.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+
+namespace dom = mozilla::dom;
+
+namespace {
+uint64_t gSHEntrySharedID = 0;
+nsDataHashtable<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>*
+ sIdToSharedState = nullptr;
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+/* static */
+uint64_t SHEntrySharedState::GenerateId() {
+ return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID);
+}
+
+/* static */
+SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) {
+ MOZ_ASSERT(aId != 0);
+
+ return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr;
+}
+
+static void AddSHEntrySharedParentState(
+ SHEntrySharedParentState* aSharedState) {
+ MOZ_ASSERT(aSharedState->mId != 0);
+
+ if (!sIdToSharedState) {
+ sIdToSharedState =
+ new nsDataHashtable<nsUint64HashKey, SHEntrySharedParentState*>();
+ }
+ sIdToSharedState->Put(aSharedState->mId, aSharedState);
+}
+
+SHEntrySharedParentState::SHEntrySharedParentState() {
+ AddSHEntrySharedParentState(this);
+}
+
+SHEntrySharedParentState::SHEntrySharedParentState(
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType)
+ : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType) {
+ AddSHEntrySharedParentState(this);
+}
+
+SHEntrySharedParentState::~SHEntrySharedParentState() {
+ MOZ_ASSERT(mId != 0);
+
+ sIdToSharedState->Remove(mId);
+ if (sIdToSharedState->IsEmpty()) {
+ delete sIdToSharedState;
+ sIdToSharedState = nullptr;
+ }
+}
+
+void SHEntrySharedParentState::ChangeId(uint64_t aId) {
+ MOZ_ASSERT(aId != 0);
+
+ sIdToSharedState->Remove(mId);
+ mId = aId;
+ sIdToSharedState->Put(mId, this);
+}
+
+void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) {
+ mDocShellID = aEntry->mDocShellID;
+ mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
+ mPrincipalToInherit = aEntry->mPrincipalToInherit;
+ mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit;
+ mCsp = aEntry->mCsp;
+ mSaveLayoutState = aEntry->mSaveLayoutState;
+ mContentType.Assign(aEntry->mContentType);
+ mIsFrameNavigation = aEntry->mIsFrameNavigation;
+ mSticky = aEntry->mSticky;
+ mDynamicallyCreated = aEntry->mDynamicallyCreated;
+ mCacheKey = aEntry->mCacheKey;
+ mLastTouched = aEntry->mLastTouched;
+}
+
+void dom::SHEntrySharedParentState::NotifyListenersContentViewerEvicted() {
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get());
+ nsshistory->NotifyListenersContentViewerEvicted(1);
+ }
+}
+
+void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) {
+ mChildShells.AppendObjects(aEntry->mChildShells);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+void nsSHEntryShared::Shutdown() {}
+
+nsSHEntryShared::~nsSHEntryShared() {
+ // The destruction can be caused by either the entry is removed from session
+ // history and no one holds the reference, or the whole session history is on
+ // destruction. We want to ensure that we invoke
+ // shistory->RemoveFromExpirationTracker for the former case.
+ RemoveFromExpirationTracker();
+
+ // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
+ // there couldn't be any SHEntry holding this shared entry, and we noticed
+ // that calling RemoveDynEntriesForBFCacheEntry in the middle of
+ // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
+ // before RemoveFromBFCacheSync.
+ mSHistory = nullptr;
+ if (mContentViewer) {
+ RemoveFromBFCacheSync();
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
+NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
+
+already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate() {
+ RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+
+ newEntry->dom::SHEntrySharedParentState::CopyFrom(this);
+ newEntry->dom::SHEntrySharedChildState::CopyFrom(this);
+
+ return newEntry.forget();
+}
+
+void nsSHEntryShared::RemoveFromExpirationTracker() {
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory && GetExpirationState()->IsTracked()) {
+ shistory->RemoveFromExpirationTracker(this);
+ }
+}
+
+void nsSHEntryShared::SyncPresentationState() {
+ if (mContentViewer && mWindowState) {
+ // If we have a content viewer and a window state, we should be ok.
+ return;
+ }
+
+ DropPresentationState();
+}
+
+void nsSHEntryShared::DropPresentationState() {
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(nullptr);
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+ if (mContentViewer) {
+ mContentViewer->ClearHistoryEntry();
+ }
+
+ RemoveFromExpirationTracker();
+ mContentViewer = nullptr;
+ mSticky = true;
+ mWindowState = nullptr;
+ mViewerBounds.SetRect(0, 0, 0, 0);
+ mChildShells.Clear();
+ mRefreshURIList = nullptr;
+ mEditorData = nullptr;
+}
+
+nsresult nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) {
+ MOZ_ASSERT(!aViewer || !mContentViewer,
+ "SHEntryShared already contains viewer");
+
+ if (mContentViewer || !aViewer) {
+ DropPresentationState();
+ }
+
+ // If we're setting mContentViewer to null, state should already be cleared
+ // in the DropPresentationState() call above; If we're setting it to a
+ // non-null content viewer, the entry shouldn't have been tracked either.
+ MOZ_ASSERT(!GetExpirationState()->IsTracked());
+ mContentViewer = aViewer;
+
+ if (mContentViewer) {
+ // mSHistory is only set for root entries, but in general bfcache only
+ // applies to root entries as well. BFCache for subframe navigation has been
+ // disabled since 2005 in bug 304860.
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->AddToExpirationTracker(this);
+ }
+
+ // Store observed document in strong pointer in case it is removed from
+ // the contentviewer
+ mDocument = mContentViewer->GetDocument();
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(this);
+ mDocument->AddMutationObserver(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
+ MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+ // The call to DropPresentationState could drop the last reference, so hold
+ // |this| until RemoveDynEntriesForBFCacheEntry finishes.
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ // DropPresentationState would clear mContentViewer.
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ DropPresentationState();
+
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ // Now that we've dropped the viewer, we have to clear associated dynamic
+ // subframe entries.
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory) {
+ shistory->RemoveDynEntriesForBFCacheEntry(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
+ MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+ // Check it again to play safe in release builds.
+ if (!mDocument) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // DropPresentationState would clear mContentViewer & mDocument. Capture and
+ // release the references asynchronously so that the document doesn't get
+ // nuked mid-mutation.
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ RefPtr<dom::Document> document = mDocument;
+ RefPtr<nsSHEntryShared> self = this;
+ nsresult rv = mDocument->Dispatch(
+ mozilla::TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "nsSHEntryShared::RemoveFromBFCacheAsync",
+ [self, viewer, document]() {
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(self->mSHistory);
+ if (shistory) {
+ shistory->RemoveDynEntriesForBFCacheEntry(self);
+ }
+ }));
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
+ } else {
+ // Drop presentation. Only do this if we succeeded in posting the event
+ // since otherwise the document could be torn down mid-mutation, causing
+ // crashes.
+ DropPresentationState();
+ }
+
+ return NS_OK;
+}
+
+void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentInserted(nsIContent* aChild) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ RemoveFromBFCacheAsync();
+}
diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h
new file mode 100644
index 0000000000..28e559024a
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -0,0 +1,212 @@
+/* -*- 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 nsSHEntryShared_h__
+#define nsSHEntryShared_h__
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsExpirationTracker.h"
+#include "nsIBFCacheEntry.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsStubMutationObserver.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+class nsSHEntry;
+class nsISHEntry;
+class nsISHistory;
+class nsIContentSecurityPolicy;
+class nsIContentViewer;
+class nsIDocShellTreeItem;
+class nsILayoutHistoryState;
+class nsIPrincipal;
+class nsDocShellEditorData;
+class nsIMutableArray;
+class nsSHistory;
+
+// A document may have multiple SHEntries, either due to hash navigations or
+// calls to history.pushState. SHEntries corresponding to the same document
+// share many members; in particular, they share state related to the
+// back/forward cache.
+//
+// The classes defined here are the vehicle for this sharing.
+//
+// Some of the state can only be stored in the process where we did the actual
+// load, because that's where the objects live (eg. the content viewer).
+
+namespace mozilla {
+namespace dom {
+class Document;
+
+/**
+ * SHEntrySharedState holds shared state both in the child process and in the
+ * parent process.
+ */
+struct SHEntrySharedState {
+ SHEntrySharedState() : mId(GenerateId()) {}
+ SHEntrySharedState(const SHEntrySharedState& aState) = default;
+ SHEntrySharedState(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType)
+ : mId(GenerateId()),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPrincipalToInherit(aPrincipalToInherit),
+ mPartitionedPrincipalToInherit(aPartitionedPrincipalToInherit),
+ mCsp(aCsp),
+ mContentType(aContentType) {}
+
+ // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ uint64_t mId = 0;
+
+ // These members are copied by SHEntrySharedParentState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+ nsCString mContentType;
+ // Child side updates layout history state when page is being unloaded or
+ // moved to bfcache.
+ nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+ uint32_t mCacheKey = 0;
+ bool mIsFrameNavigation = false;
+
+ protected:
+ static uint64_t GenerateId();
+};
+
+/**
+ * SHEntrySharedParentState holds the shared state that can live in the parent
+ * process.
+ */
+class SHEntrySharedParentState : public SHEntrySharedState {
+ public:
+ friend class SessionHistoryInfo;
+
+ uint64_t GetId() const { return mId; }
+ void ChangeId(uint64_t aId);
+
+ void NotifyListenersContentViewerEvicted();
+
+ SHEntrySharedParentState();
+ SHEntrySharedParentState(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+
+ // This returns the existing SHEntrySharedParentState that was registered for
+ // aId, if one exists.
+ static SHEntrySharedParentState* Lookup(uint64_t aId);
+
+ protected:
+ virtual ~SHEntrySharedParentState();
+ NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(SHEntrySharedParentState,
+ Destroy())
+
+ virtual void Destroy() { delete this; }
+
+ void CopyFrom(SHEntrySharedParentState* aSource);
+
+ // These members are copied by SHEntrySharedParentState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsID mDocShellID{};
+
+ nsIntRect mViewerBounds{0, 0, 0, 0};
+
+ uint32_t mLastTouched = 0;
+
+ // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ nsWeakPtr mSHistory;
+
+ bool mSticky = true;
+ bool mDynamicallyCreated = false;
+
+ // This flag is about necko cache, not bfcache.
+ bool mExpired = false;
+
+ bool mSaveLayoutState = true;
+};
+
+/**
+ * SHEntrySharedChildState holds the shared state that needs to live in the
+ * process where the document was loaded.
+ */
+class SHEntrySharedChildState {
+ protected:
+ void CopyFrom(SHEntrySharedChildState* aSource);
+
+ public:
+ // These members are copied by SHEntrySharedChildState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsCOMArray<nsIDocShellTreeItem> mChildShells;
+
+ // These members aren't copied by SHEntrySharedChildState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsISupports> mWindowState;
+ // FIXME Move to parent?
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ nsExpirationState mExpirationState;
+ UniquePtr<nsDocShellEditorData> mEditorData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsSHEntryShared holds the shared state if the session history is not stored
+ * in the parent process, or if the load itself happens in the parent process.
+ * Note, since nsSHEntryShared inherits both SHEntrySharedParentState and
+ * SHEntrySharedChildState and those have some same member variables,
+ * the ones from SHEntrySharedParentState should be used.
+ */
+class nsSHEntryShared final : public nsIBFCacheEntry,
+ public nsStubMutationObserver,
+ public mozilla::dom::SHEntrySharedParentState,
+ public mozilla::dom::SHEntrySharedChildState {
+ public:
+ static void EnsureHistoryTracker();
+ static void Shutdown();
+
+ using SHEntrySharedParentState::SHEntrySharedParentState;
+
+ already_AddRefed<nsSHEntryShared> Duplicate();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIBFCACHEENTRY
+
+ // The nsIMutationObserver bits we actually care about.
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+ private:
+ ~nsSHEntryShared();
+
+ friend class nsSHEntry;
+
+ void RemoveFromExpirationTracker();
+ void SyncPresentationState();
+ void DropPresentationState();
+
+ nsresult SetContentViewer(nsIContentViewer* aViewer);
+};
+
+#endif
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
new file mode 100644
index 0000000000..1581cd55c2
--- /dev/null
+++ b/docshell/shistory/nsSHistory.cpp
@@ -0,0 +1,1977 @@
+/* -*- 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 "nsSHistory.h"
+
+#include <algorithm>
+
+#include "nsContentUtils.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDocShell.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIObserverService.h"
+#include "nsISHEntry.h"
+#include "nsISHistoryListener.h"
+#include "nsIURI.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsSHEntry.h"
+#include "SessionHistoryEntry.h"
+#include "nsTArray.h"
+#include "prsystem.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsIWebNavigation.h"
+#include "nsDocShellLoadTypes.h"
+#include "base/process.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
+#define PREF_SHISTORY_MAX_TOTAL_VIEWERS \
+ "browser.sessionhistory.max_total_viewers"
+#define CONTENT_VIEWER_TIMEOUT_SECONDS \
+ "browser.sessionhistory.contentViewerTimeout"
+
+// Default this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
+
+static const char* kObservedPrefs[] = {
+ PREF_SHISTORY_SIZE, PREF_SHISTORY_MAX_TOTAL_VIEWERS, nullptr};
+
+static int32_t gHistoryMaxSize = 50;
+// List of all SHistory objects, used for content viewer cache eviction
+static LinkedList<nsSHistory> gSHistoryList;
+// Max viewers allowed total, across all SHistory objects - negative default
+// means we will calculate how many viewers to cache based on total memory
+int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
+
+// A counter that is used to be able to know the order in which
+// entries were touched, so that we can evict older entries first.
+static uint32_t gTouchCounter = 0;
+
+extern mozilla::LazyLogModule gSHLog;
+
+LazyLogModule gSHistoryLog("nsSHistory");
+
+#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
+
+extern mozilla::LazyLogModule gPageCacheLog;
+
+// This macro makes it easier to print a log message which includes a URI's
+// spec. Example use:
+//
+// nsIURI *uri = [...];
+// LOG_SPEC(("The URI is %s.", _spec), uri);
+//
+#define LOG_SPEC(format, uri) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsAutoCString _specStr("(null)"_ns); \
+ if (uri) { \
+ _specStr = uri->GetSpecOrDefault(); \
+ } \
+ const char* _spec = _specStr.get(); \
+ LOG(format); \
+ } \
+ PR_END_MACRO
+
+// This macro makes it easy to log a message including an SHEntry's URI.
+// For example:
+//
+// nsCOMPtr<nsISHEntry> shentry = [...];
+// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
+//
+#define LOG_SHENTRY_SPEC(format, shentry) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsCOMPtr<nsIURI> uri = shentry->GetURI(); \
+ LOG_SPEC(format, uri); \
+ } \
+ PR_END_MACRO
+
+// Iterates over all registered session history listeners.
+#define ITERATE_LISTENERS(body) \
+ PR_BEGIN_MACRO { \
+ for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) { \
+ nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr); \
+ if (listener) { \
+ body \
+ } \
+ } \
+ } \
+ PR_END_MACRO
+
+// Calls a given method on all registered session history listeners.
+#define NOTIFY_LISTENERS(method, args) \
+ ITERATE_LISTENERS(listener->method args;);
+
+// Calls a given method on all registered session history listeners.
+// Listeners may return 'false' to cancel an action so make sure that we
+// set the return value to 'false' if one of the listeners wants to cancel.
+#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \
+ PR_BEGIN_MACRO { \
+ bool canceled = false; \
+ retval = true; \
+ ITERATE_LISTENERS(listener->method args; \
+ if (!retval) { canceled = true; }); \
+ if (canceled) { \
+ retval = false; \
+ } \
+ } \
+ PR_END_MACRO
+
+class MOZ_STACK_CLASS SHistoryChangeNotifier {
+ public:
+ explicit SHistoryChangeNotifier(nsSHistory* aHistory) {
+ // If we're already in an update, the outermost change notifier will
+ // update browsing context in the destructor.
+ if (!aHistory->HasOngoingUpdate()) {
+ aHistory->SetHasOngoingUpdate(true);
+ mSHistory = aHistory;
+ mInitialIndex = aHistory->Index();
+ mInitialLength = aHistory->Length();
+ }
+ }
+
+ ~SHistoryChangeNotifier() {
+ if (mSHistory) {
+ MOZ_ASSERT(mSHistory->HasOngoingUpdate());
+ mSHistory->SetHasOngoingUpdate(false);
+ if (mSHistory->GetBrowsingContext()) {
+ mSHistory->GetBrowsingContext()->SessionHistoryChanged(
+ mSHistory->Index() - mInitialIndex,
+ mSHistory->Length() - mInitialLength);
+ }
+
+ if (mozilla::SessionHistoryInParent() &&
+ mSHistory->GetBrowsingContext()) {
+ mSHistory->GetBrowsingContext()
+ ->Canonical()
+ ->HistoryCommitIndexAndLength();
+ }
+ }
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+ int32_t mInitialIndex;
+ int32_t mInitialLength;
+};
+
+enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD };
+
+class nsSHistoryObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsSHistoryObserver() {}
+
+ static void PrefChanged(const char* aPref, void* aSelf);
+ void PrefChanged(const char* aPref);
+
+ protected:
+ ~nsSHistoryObserver() {}
+};
+
+StaticRefPtr<nsSHistoryObserver> gObserver;
+
+NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
+
+// static
+void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
+ static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
+}
+
+void nsSHistoryObserver::PrefChanged(const char* aPref) {
+ nsSHistory::UpdatePrefs();
+ nsSHistory::GloballyEvictContentViewers();
+}
+
+NS_IMETHODIMP
+nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "cacheservice:empty-cache") ||
+ !strcmp(aTopic, "memory-pressure")) {
+ nsSHistory::GloballyEvictAllContentViewers();
+ }
+
+ return NS_OK;
+}
+
+void nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsIContentViewer> viewer = aEntry->GetContentViewer();
+ if (viewer) {
+ LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
+ "owning SHEntry 0x%p at %s.",
+ viewer.get(), aEntry, _spec),
+ aEntry);
+
+ // Drop the presentation state before destroying the viewer, so that
+ // document teardown is able to correctly persist the state.
+ NotifyListenersContentViewerEvicted(1);
+ aEntry->SetContentViewer(nullptr);
+ aEntry->SyncPresentationState();
+ viewer->Destroy();
+ }
+
+ // When dropping bfcache, we have to remove associated dynamic entries as
+ // well.
+ int32_t index = GetIndexOfEntry(aEntry);
+ if (index != -1) {
+ RemoveDynEntries(index, aEntry);
+ }
+}
+
+nsSHistory::nsSHistory(BrowsingContext* aRootBC)
+ : mRootBC(aRootBC),
+ mHasOngoingUpdate(false),
+ mIsRemote(false),
+ mIndex(-1),
+ mRequestedIndex(-1),
+ mRootDocShellID(aRootBC->GetHistoryID()) {
+ static bool sCalledStartup = false;
+ if (!sCalledStartup) {
+ Startup();
+ sCalledStartup = true;
+ }
+
+ // Add this new SHistory object to the list
+ gSHistoryList.insertBack(this);
+
+ // Init mHistoryTracker on setting mRootBC so we can bind its event
+ // target to the tabGroup.
+ nsPIDOMWindowOuter* win;
+ if (mRootBC && (win = mRootBC->GetDOMWindow())) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(win);
+ mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
+ this,
+ mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+ CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
+ global->EventTargetFor(mozilla::TaskCategory::Other));
+ }
+}
+
+nsSHistory::~nsSHistory() {}
+
+NS_IMPL_ADDREF(nsSHistory)
+NS_IMPL_RELEASE(nsSHistory)
+
+NS_INTERFACE_MAP_BEGIN(nsSHistory)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+// static
+uint32_t nsSHistory::CalcMaxTotalViewers() {
+// This value allows tweaking how fast the allowed amount of content viewers
+// grows with increasing amounts of memory. Larger values mean slower growth.
+#ifdef ANDROID
+# define MAX_TOTAL_VIEWERS_BIAS 15.9
+#else
+# define MAX_TOTAL_VIEWERS_BIAS 14
+#endif
+
+ // Calculate an estimate of how many ContentViewers we should cache based
+ // on RAM. This assumes that the average ContentViewer is 4MB (conservative)
+ // and caps the max at 8 ContentViewers
+ //
+ // TODO: Should we split the cache memory betw. ContentViewer caching and
+ // nsCacheService?
+ //
+ // RAM | ContentViewers | on Android
+ // -------------------------------------
+ // 32 Mb 0 0
+ // 64 Mb 1 0
+ // 128 Mb 2 0
+ // 256 Mb 3 1
+ // 512 Mb 5 2
+ // 768 Mb 6 2
+ // 1024 Mb 8 3
+ // 2048 Mb 8 5
+ // 3072 Mb 8 7
+ // 4096 Mb 8 8
+ uint64_t bytes = PR_GetPhysicalMemorySize();
+
+ if (bytes == 0) {
+ return 0;
+ }
+
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX) {
+ bytes = INT64_MAX;
+ }
+
+ double kBytesD = (double)(bytes >> 10);
+
+ // This is essentially the same calculation as for nsCacheService,
+ // except that we divide the final memory calculation by 4, since
+ // we assume each ContentViewer takes on average 4MB
+ uint32_t viewers = 0;
+ double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS;
+ if (x > 0) {
+ viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
+ viewers /= 4;
+ }
+
+ // Cap it off at 8 max
+ if (viewers > 8) {
+ viewers = 8;
+ }
+ return viewers;
+}
+
+// static
+void nsSHistory::UpdatePrefs() {
+ Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
+ if (mozilla::SessionHistoryInParent()) {
+ sHistoryMaxTotalViewers = 0;
+ return;
+ }
+
+ Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+ &sHistoryMaxTotalViewers);
+ // If the pref is negative, that means we calculate how many viewers
+ // we think we should cache, based on total memory
+ if (sHistoryMaxTotalViewers < 0) {
+ sHistoryMaxTotalViewers = CalcMaxTotalViewers();
+ }
+}
+
+// static
+nsresult nsSHistory::Startup() {
+ UpdatePrefs();
+
+ // The goal of this is to unbreak users who have inadvertently set their
+ // session history size to less than the default value.
+ int32_t defaultHistoryMaxSize =
+ Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default);
+ if (gHistoryMaxSize < defaultHistoryMaxSize) {
+ gHistoryMaxSize = defaultHistoryMaxSize;
+ }
+
+ // Allow the user to override the max total number of cached viewers,
+ // but keep the per SHistory cached viewer limit constant
+ if (!gObserver) {
+ gObserver = new nsSHistoryObserver();
+ Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged,
+ kObservedPrefs, gObserver.get());
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ // Observe empty-cache notifications so tahat clearing the disk/memory
+ // cache will also evict all content viewers.
+ obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
+
+ // Same for memory-pressure notifications
+ obsSvc->AddObserver(gObserver, "memory-pressure", false);
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+void nsSHistory::Shutdown() {
+ if (gObserver) {
+ Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged,
+ kObservedPrefs, gObserver.get());
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
+ obsSvc->RemoveObserver(gObserver, "memory-pressure");
+ }
+ gObserver = nullptr;
+ }
+}
+
+// static
+already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsISHEntry> rootEntry = aEntry;
+ nsCOMPtr<nsISHEntry> result = nullptr;
+ while (rootEntry) {
+ result = rootEntry;
+ rootEntry = result->GetParent();
+ }
+
+ return result.forget();
+}
+
+// static
+nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
+ BrowsingContext* aBC,
+ WalkHistoryEntriesFunc aCallback,
+ void* aData) {
+ NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
+
+ int32_t childCount = aRootEntry->GetChildCount();
+ for (int32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsISHEntry> childEntry;
+ aRootEntry->GetChildAt(i, getter_AddRefs(childEntry));
+ if (!childEntry) {
+ // childEntry can be null for valid reasons, for example if the
+ // docshell at index i never loaded anything useful.
+ // Remember to clone also nulls in the child array (bug 464064).
+ aCallback(nullptr, nullptr, i, aData);
+ continue;
+ }
+
+ BrowsingContext* childBC = nullptr;
+ if (aBC) {
+ for (BrowsingContext* child : aBC->Children()) {
+ // If the SH pref is on and we are in the parent process, update
+ // canonical BC directly
+ bool foundChild = false;
+ if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) {
+ if (child->Canonical()->HasHistoryEntry(childEntry)) {
+ childBC = child;
+ foundChild = true;
+ }
+ }
+
+ nsDocShell* docshell = static_cast<nsDocShell*>(child->GetDocShell());
+ if (docshell && docshell->HasHistoryEntry(childEntry)) {
+ childBC = docshell->GetBrowsingContext();
+ foundChild = true;
+ }
+
+ // XXX Simplify this once the old and new session history
+ // implementations don't run at the same time.
+ if (foundChild) {
+ break;
+ }
+ }
+ }
+
+ nsresult rv = aCallback(childEntry, childBC, i, aData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// callback data for WalkHistoryEntries
+struct MOZ_STACK_CLASS CloneAndReplaceData {
+ CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+ bool aCloneChildren, nsISHEntry* aDestTreeParent)
+ : cloneID(aCloneID),
+ cloneChildren(aCloneChildren),
+ replaceEntry(aReplaceEntry),
+ destTreeParent(aDestTreeParent) {}
+
+ uint32_t cloneID;
+ bool cloneChildren;
+ nsISHEntry* replaceEntry;
+ nsISHEntry* destTreeParent;
+ nsCOMPtr<nsISHEntry> resultEntry;
+};
+
+nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
+ BrowsingContext* aOwnerBC,
+ int32_t aChildIndex, void* aData) {
+ nsCOMPtr<nsISHEntry> dest;
+
+ CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
+ uint32_t cloneID = data->cloneID;
+ nsISHEntry* replaceEntry = data->replaceEntry;
+
+ if (!aEntry) {
+ if (data->destTreeParent) {
+ data->destTreeParent->AddChild(nullptr, aChildIndex);
+ }
+ return NS_OK;
+ }
+
+ uint32_t srcID = aEntry->GetID();
+
+ nsresult rv = NS_OK;
+ if (srcID == cloneID) {
+ // Replace the entry
+ dest = replaceEntry;
+ } else {
+ // Clone the SHEntry...
+ rv = aEntry->Clone(getter_AddRefs(dest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ dest->SetIsSubFrame(true);
+
+ if (srcID != cloneID || data->cloneChildren) {
+ // Walk the children
+ CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
+ dest);
+ rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
+ &childData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (srcID != cloneID && aOwnerBC) {
+ nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest);
+ }
+
+ if (data->destTreeParent) {
+ data->destTreeParent->AddChild(dest, aChildIndex);
+ }
+ data->resultEntry = dest;
+ return rv;
+}
+
+// static
+nsresult nsSHistory::CloneAndReplace(
+ nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID,
+ nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) {
+ NS_ENSURE_ARG_POINTER(aDestEntry);
+ NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
+ CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
+ nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data);
+ data.resultEntry.swap(*aDestEntry);
+ return rv;
+}
+
+// static
+void nsSHistory::WalkContiguousEntries(
+ nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback) {
+ MOZ_ASSERT(aEntry);
+
+ nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
+ if (!shistory) {
+ // If there is no session history in the entry, it means this is not a root
+ // entry. So, we can return from here.
+ return;
+ }
+
+ int32_t index = shistory->GetIndexOfEntry(aEntry);
+ int32_t count = shistory->GetCount();
+
+ nsCOMPtr<nsIURI> targetURI = aEntry->GetURI();
+
+ // First, call the callback on the input entry.
+ aCallback(aEntry);
+
+ // Walk backward to find the entries that have the same origin as the
+ // input entry.
+ for (int32_t i = index - 1; i >= 0; i--) {
+ RefPtr<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> uri = entry->GetURI();
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ targetURI, uri, false, false))) {
+ break;
+ }
+
+ aCallback(entry);
+ }
+ }
+
+ // Then, Walk forward.
+ for (int32_t i = index + 1; i < count; i++) {
+ RefPtr<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> uri = entry->GetURI();
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ targetURI, uri, false, false))) {
+ break;
+ }
+
+ aCallback(entry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+ BrowsingContext* aRootBC,
+ bool aCloneChildren) {
+ MOZ_ASSERT(aRootBC->IsTop());
+
+ /* You are currently in the rootDocShell.
+ * You will get here when a subframe has a new url
+ * to load and you have walked up the tree all the
+ * way to the top to clone the current SHEntry hierarchy
+ * and replace the subframe where a new url was loaded with
+ * a new entry.
+ */
+ nsCOMPtr<nsISHEntry> child;
+ nsCOMPtr<nsISHEntry> currentHE;
+ int32_t index = mIndex;
+ if (index < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GetEntryAtIndex(index, getter_AddRefs(currentHE));
+ NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
+
+ nsresult rv = NS_OK;
+ uint32_t cloneID = aCloneRef->GetID();
+ rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry,
+ aCloneChildren, getter_AddRefs(child));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = AddEntry(child, true);
+ if (NS_SUCCEEDED(rv)) {
+ child->SetDocshellID(aRootBC->GetHistoryID());
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry,
+ BrowsingContext* aBC,
+ int32_t aEntryIndex, void* aData) {
+ SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
+ if (!aBC || aBC == data->ignoreBC) {
+ return NS_OK;
+ }
+
+ nsISHEntry* destTreeRoot = data->destTreeRoot;
+
+ nsCOMPtr<nsISHEntry> destEntry;
+
+ if (data->destTreeParent) {
+ // aEntry is a clone of some child of destTreeParent, but since the
+ // trees aren't necessarily in sync, we'll have to locate it.
+ // Note that we could set aShell's entry to null if we don't find a
+ // corresponding entry under destTreeParent.
+
+ uint32_t targetID = aEntry->GetID();
+
+ // First look at the given index, since this is the common case.
+ nsCOMPtr<nsISHEntry> entry;
+ data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry));
+ if (entry && entry->GetID() == targetID) {
+ destEntry.swap(entry);
+ } else {
+ int32_t childCount;
+ data->destTreeParent->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ data->destTreeParent->GetChildAt(i, getter_AddRefs(entry));
+ if (!entry) {
+ continue;
+ }
+
+ if (entry->GetID() == targetID) {
+ destEntry.swap(entry);
+ break;
+ }
+ }
+ }
+ } else {
+ destEntry = destTreeRoot;
+ }
+
+ nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry);
+ // Now handle the children of aEntry.
+ SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry};
+ return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry,
+ &childData);
+}
+
+// static
+void nsSHistory::HandleEntriesToSwapInDocShell(
+ mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ bool shPref = mozilla::SessionHistoryInParent();
+ if (aBC->IsInProcess() || !shPref) {
+ nsDocShell* docshell = static_cast<nsDocShell*>(aBC->GetDocShell());
+ if (docshell) {
+ docshell->SwapHistoryEntries(aOldEntry, aNewEntry);
+ }
+ } else {
+ // FIXME Bug 1633988: Need to update entries?
+ }
+
+ // XXX Simplify this once the old and new session history implementations
+ // don't run at the same time.
+ if (shPref && XRE_IsParentProcess()) {
+ aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
+ }
+}
+
+void nsSHistory::UpdateRootBrowsingContextState() {
+ if (mRootBC) {
+ bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage();
+ if (sameDocument != mRootBC->GetIsSingleToplevelInHistory()) {
+ // If the browsing context is discarded then its session history is
+ // invalid and will go away.
+ Unused << mRootBC->SetIsSingleToplevelInHistory(sameDocument);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
+ BrowsingContext* aRootBC,
+ nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aShouldPersist,
+ Maybe<int32_t>* aPreviousEntryIndex,
+ Maybe<int32_t>* aLoadedEntryIndex) {
+ MOZ_ASSERT(aRootBC->IsTop());
+
+ nsresult rv = NS_OK;
+
+ // If we need to clone our children onto the new session
+ // history entry, do so now.
+ if (aCloneChildren && aOSHE) {
+ uint32_t cloneID = aOSHE->GetID();
+ nsCOMPtr<nsISHEntry> newEntry;
+ nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true,
+ getter_AddRefs(newEntry));
+ NS_ASSERTION(aEntry == newEntry,
+ "The new session history should be in the new entry");
+ }
+ // This is the root docshell
+ bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY);
+ if (!addToSHistory) {
+ // Replace current entry in session history; If the requested index is
+ // valid, it indicates the loading was triggered by a history load, and
+ // we should replace the entry at requested index instead.
+ int32_t index = GetIndexForReplace();
+
+ // Replace the current entry with the new entry
+ if (index >= 0) {
+ rv = ReplaceEntry(index, aEntry);
+ } else {
+ // If we're trying to replace an inexistant shistory entry, append.
+ addToSHistory = true;
+ }
+ }
+ if (addToSHistory) {
+ // Add to session history
+ *aPreviousEntryIndex = Some(mIndex);
+ rv = AddEntry(aEntry, aShouldPersist);
+ *aLoadedEntryIndex = Some(mIndex);
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d",
+ aPreviousEntryIndex->value(), aLoadedEntryIndex->value()));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ aEntry->SetDocshellID(aRootBC->GetHistoryID());
+ }
+ return rv;
+}
+
+/* Add an entry to the History list at mIndex and
+ * increment the index to point to the new entry
+ */
+NS_IMETHODIMP
+nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) {
+ NS_ENSURE_ARG(aSHEntry);
+
+ nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetShistory();
+ if (shistoryOfEntry && shistoryOfEntry != this) {
+ NS_WARNING(
+ "The entry has been associated to another nsISHistory instance. "
+ "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
+ "first if you're copying an entry from another nsISHistory.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aSHEntry->SetShistory(this);
+
+ // If we have a root docshell, update the docshell id of the root shentry to
+ // match the id of that docshell
+ if (mRootBC) {
+ aSHEntry->SetDocshellID(mRootDocShellID);
+ }
+
+ if (mIndex >= 0) {
+ MOZ_ASSERT(mIndex < Length(), "Index out of range!");
+ if (mIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) {
+ NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
+ aSHEntry->SetPersist(aPersist);
+ mEntries[mIndex] = aSHEntry;
+ UpdateRootBrowsingContextState();
+ return NS_OK;
+ }
+ }
+ SHistoryChangeNotifier change(this);
+
+ nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
+ NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, mIndex));
+
+ // Remove all entries after the current one, add the new one, and set the
+ // new one as the current one.
+ MOZ_ASSERT(mIndex >= -1);
+ aSHEntry->SetPersist(aPersist);
+ mEntries.TruncateLength(mIndex + 1);
+ mEntries.AppendElement(aSHEntry);
+ mIndex++;
+
+ // Purge History list if it is too long
+ if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
+ PurgeHistory(Length() - gHistoryMaxSize);
+ }
+
+ UpdateRootBrowsingContextState();
+
+ return NS_OK;
+}
+
+void nsSHistory::NotifyOnHistoryReplaceEntry() {
+ NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
+}
+
+/* Get size of the history list */
+NS_IMETHODIMP
+nsSHistory::GetCount(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetIndex(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = mIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::SetIndex(int32_t aIndex) {
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIndex = aIndex;
+ return NS_OK;
+}
+
+/* Get the requestedIndex */
+NS_IMETHODIMP
+nsSHistory::GetRequestedIndex(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = mRequestedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) {
+ MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length());
+ mRequestedIndex = aRequestedIndex;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mEntries[aIndex];
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(int32_t)
+nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) {
+ for (int32_t i = 0; i < Length(); i++) {
+ if (aSHEntry == mEntries[i]) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal,
+ const nsCString& aPrefix, bool aIsCurrent) {
+ if (!aEntry) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s+- %i SH Entry null\n", aPrefix.get(), aIndex));
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = aEntry->GetURI();
+ nsAutoString title;
+ aEntry->GetTitle(title);
+
+ SHEntrySharedParentState* shared;
+ if (mozilla::SessionHistoryInParent()) {
+ shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
+ } else {
+ shared = static_cast<nsSHEntry*>(aEntry)->GetState();
+ }
+
+ nsID docShellId;
+ aEntry->GetDocshellID(docShellId);
+
+ int32_t childCount = aEntry->GetChildCount();
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ",
+ aPrefix.get(), aIndex, aEntry, shared->GetId(),
+ nsIDToCString(docShellId).get()));
+
+ nsCString prefix(aPrefix);
+ if (aIndex < aTotal - 1) {
+ prefix.AppendLiteral("| ");
+ } else {
+ prefix.AppendLiteral(" ");
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s%s URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ uri->GetSpecOrDefault().get()));
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s%s Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ NS_LossyConvertUTF16toASCII(title).get()));
+
+ nsCOMPtr<nsISHEntry> prevChild;
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ LogEntry(child, i, childCount, prefix, false);
+ child.swap(prevChild);
+ }
+}
+
+void nsSHistory::LogHistory() {
+ if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) {
+ return;
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this));
+ int32_t length = Length();
+ for (int32_t i = 0; i < length; i++) {
+ LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex);
+ }
+}
+
+void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+ int32_t* aOutEndIndex) {
+ *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
+ *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
+}
+
+NS_IMETHODIMP
+nsSHistory::PurgeHistory(int32_t aNumEntries) {
+ if (Length() <= 0 || aNumEntries <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SHistoryChangeNotifier change(this);
+
+ aNumEntries = std::min(aNumEntries, Length());
+
+ NOTIFY_LISTENERS(OnHistoryPurge, ());
+
+ // Remove the first `aNumEntries` entries.
+ mEntries.RemoveElementsAt(0, aNumEntries);
+
+ // Adjust the indices, but don't let them go below -1.
+ mIndex -= aNumEntries;
+ mIndex = std::max(mIndex, -1);
+ mRequestedIndex -= aNumEntries;
+ mRequestedIndex = std::max(mRequestedIndex, -1);
+
+ if (mRootBC && mRootBC->GetDocShell()) {
+ mRootBC->GetDocShell()->HistoryPurged(aNumEntries);
+ }
+
+ UpdateRootBrowsingContextState();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ // Check if the listener supports Weak Reference. This is a must.
+ // This listener functionality is used by embedders and we want to
+ // have the right ownership with who ever listens to SHistory
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mListeners.AppendElementUnlessExists(listener);
+ return NS_OK;
+}
+
+void nsSHistory::NotifyListenersContentViewerEvicted(uint32_t aNumEvicted) {
+ NOTIFY_LISTENERS(OnContentViewerEvicted, (aNumEvicted));
+}
+
+NS_IMETHODIMP
+nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) {
+ // Make sure the listener that wants to be removed is the
+ // one we have in store.
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+/* Replace an entry in the History list at a particular index.
+ * Do not update index or count.
+ */
+NS_IMETHODIMP
+nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
+ NS_ENSURE_ARG(aReplaceEntry);
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetShistory();
+ if (shistoryOfEntry && shistoryOfEntry != this) {
+ NS_WARNING(
+ "The entry has been associated to another nsISHistory instance. "
+ "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
+ "first if you're copying an entry from another nsISHistory.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aReplaceEntry->SetShistory(this);
+
+ NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
+
+ aReplaceEntry->SetPersist(true);
+ mEntries[aIndex] = aReplaceEntry;
+
+ UpdateRootBrowsingContextState();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload, (aCanReload));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) {
+ // Check our per SHistory object limit in the currently navigated SHistory
+ EvictOutOfRangeWindowContentViewers(aIndex);
+ // Check our total limit across all SHistory objects
+ GloballyEvictContentViewers();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EvictContentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
+ bool aReplace) {
+ if (!aReplace) {
+ int32_t curIndex;
+ GetIndex(&curIndex);
+ if (curIndex > -1) {
+ EvictOutOfRangeContentViewers(curIndex);
+ }
+ } else {
+ nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
+
+ int32_t index = GetIndexOfEntry(rootSHEntry);
+ if (index > -1) {
+ ReplaceEntry(index, rootSHEntry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictAllContentViewers() {
+ // XXXbz we don't actually do a good job of evicting things as we should, so
+ // we might have viewers quite far from mIndex. So just evict everything.
+ for (int32_t i = 0; i < Length(); i++) {
+ EvictContentViewerForEntry(mEntries[i]);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsSHistory::LoadURIs(nsTArray<LoadEntryResult>& aLoadResults) {
+ for (LoadEntryResult& loadEntry : aLoadResults) {
+ loadEntry.mBrowsingContext->LoadURI(loadEntry.mLoadState, false);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::Reload(uint32_t aReloadFlags) {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = Reload(aReloadFlags, loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (loadResults.IsEmpty()) {
+ return NS_OK;
+ }
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+nsresult nsSHistory::Reload(uint32_t aReloadFlags,
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ MOZ_ASSERT(aLoadResults.IsEmpty());
+
+ uint32_t loadType;
+ if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
+ loadType = LOAD_RELOAD_BYPASS_PROXY;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = LOAD_RELOAD_BYPASS_CACHE;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
+ loadType = LOAD_RELOAD_CHARSET_CHANGE;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
+ loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT;
+ } else {
+ loadType = LOAD_RELOAD_NORMAL;
+ }
+
+ // We are reloading. Send Reload notifications.
+ // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
+ // is public. So send the reload notifications with the
+ // nsIWebNavigation flags.
+ bool canNavigate = true;
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, (&canNavigate));
+ if (!canNavigate) {
+ return NS_OK;
+ }
+
+ nsresult rv = LoadEntry(mIndex, loadType, HIST_CMD_RELOAD, aLoadResults);
+ if (NS_FAILED(rv)) {
+ aLoadResults.Clear();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::ReloadCurrentEntry() {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = ReloadCurrentEntry(loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+nsresult nsSHistory::ReloadCurrentEntry(
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ // Notify listeners
+ NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
+
+ return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResults);
+}
+
+void nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) {
+ // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
+
+ // We need to release all content viewers that are no longer in the range
+ //
+ // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
+ //
+ // to ensure that this SHistory object isn't responsible for more than
+ // VIEWER_WINDOW content viewers. But our job is complicated by the
+ // fact that two entries which are related by either hash navigations or
+ // history.pushState will have the same content viewer.
+ //
+ // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four
+ // linked entries in our history. Suppose we then add a new content
+ // viewer and call into this function. So the history looks like:
+ //
+ // A A A A B
+ // + *
+ //
+ // where the letters are content viewers and + and * denote the beginning and
+ // end of the range aIndex +/- VIEWER_WINDOW.
+ //
+ // Although one copy of the content viewer A exists outside the range, we
+ // don't want to evict A, because it has other copies in range!
+ //
+ // We therefore adjust our eviction strategy to read:
+ //
+ // Evict each content viewer outside the range aIndex -/+
+ // VIEWER_WINDOW, unless that content viewer also appears within the
+ // range.
+ //
+ // (Note that it's entirely legal to have two copies of one content viewer
+ // separated by a different content viewer -- call pushState twice, go back
+ // once, and refresh -- so we can't rely on identical viewers only appearing
+ // adjacent to one another.)
+
+ if (aIndex < 0) {
+ return;
+ }
+ NS_ENSURE_TRUE_VOID(aIndex < Length());
+
+ // Calculate the range that's safe from eviction.
+ int32_t startSafeIndex, endSafeIndex;
+ WindowIndices(aIndex, &startSafeIndex, &endSafeIndex);
+
+ LOG(
+ ("EvictOutOfRangeWindowContentViewers(index=%d), "
+ "Length()=%d. Safe range [%d, %d]",
+ aIndex, Length(), startSafeIndex, endSafeIndex));
+
+ // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be
+ // evicted. Collect a set of them so we don't accidentally evict one of them
+ // if it appears outside this range.
+ nsCOMArray<nsIContentViewer> safeViewers;
+ for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
+ nsCOMPtr<nsIContentViewer> viewer = mEntries[i]->GetContentViewer();
+ safeViewers.AppendObject(viewer);
+ }
+
+ // Walk the SHistory list and evict any content viewers that aren't safe.
+ // (It's important that the condition checks Length(), rather than a cached
+ // copy of Length(), because the length might change between iterations.)
+ for (int32_t i = 0; i < Length(); i++) {
+ nsCOMPtr<nsISHEntry> entry = mEntries[i];
+ nsCOMPtr<nsIContentViewer> viewer = entry->GetContentViewer();
+ if (safeViewers.IndexOf(viewer) == -1) {
+ EvictContentViewerForEntry(entry);
+ }
+ }
+}
+
+namespace {
+
+class EntryAndDistance {
+ public:
+ EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
+ : mSHistory(aSHistory),
+ mEntry(aEntry),
+ mViewer(aEntry->GetContentViewer()),
+ mLastTouched(mEntry->GetLastTouched()),
+ mDistance(aDist) {
+ NS_ASSERTION(mViewer, "Entry should have a content viewer");
+ }
+
+ bool operator<(const EntryAndDistance& aOther) const {
+ // Compare distances first, and fall back to last-accessed times.
+ if (aOther.mDistance != this->mDistance) {
+ return this->mDistance < aOther.mDistance;
+ }
+
+ return this->mLastTouched < aOther.mLastTouched;
+ }
+
+ bool operator==(const EntryAndDistance& aOther) const {
+ // This is a little silly; we need == so the default comaprator can be
+ // instantiated, but this function is never actually called when we sort
+ // the list of EntryAndDistance objects.
+ return aOther.mDistance == this->mDistance &&
+ aOther.mLastTouched == this->mLastTouched;
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+ nsCOMPtr<nsISHEntry> mEntry;
+ nsCOMPtr<nsIContentViewer> mViewer;
+ uint32_t mLastTouched;
+ int32_t mDistance;
+};
+
+} // namespace
+
+// static
+void nsSHistory::GloballyEvictContentViewers() {
+ // First, collect from each SHistory object the entries which have a cached
+ // content viewer. Associate with each entry its distance from its SHistory's
+ // current index.
+
+ nsTArray<EntryAndDistance> entries;
+
+ for (auto shist : gSHistoryList) {
+ // FIXME Bug 1546348: Make global eviction work for session history in the
+ // parent and remove mIsRemote.
+ if (shist->mIsRemote) {
+ continue;
+ }
+
+ // Maintain a list of the entries which have viewers and belong to
+ // this particular shist object. We'll add this list to the global list,
+ // |entries|, eventually.
+ nsTArray<EntryAndDistance> shEntries;
+
+ // Content viewers are likely to exist only within shist->mIndex -/+
+ // VIEWER_WINDOW, so only search within that range.
+ //
+ // A content viewer might exist outside that range due to either:
+ //
+ // * history.pushState or hash navigations, in which case a copy of the
+ // content viewer should exist within the range, or
+ //
+ // * bugs which cause us not to call nsSHistory::EvictContentViewers()
+ // often enough. Once we do call EvictContentViewers() for the
+ // SHistory object in question, we'll do a full search of its history
+ // and evict the out-of-range content viewers, so we don't bother here.
+ //
+ int32_t startIndex, endIndex;
+ shist->WindowIndices(shist->mIndex, &startIndex, &endIndex);
+ for (int32_t i = startIndex; i <= endIndex; i++) {
+ nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
+ nsCOMPtr<nsIContentViewer> contentViewer = entry->GetContentViewer();
+
+ if (contentViewer) {
+ // Because one content viewer might belong to multiple SHEntries, we
+ // have to search through shEntries to see if we already know
+ // about this content viewer. If we find the viewer, update its
+ // distance from the SHistory's index and continue.
+ bool found = false;
+ for (uint32_t j = 0; j < shEntries.Length(); j++) {
+ EntryAndDistance& container = shEntries[j];
+ if (container.mViewer == contentViewer) {
+ container.mDistance =
+ std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
+ found = true;
+ break;
+ }
+ }
+
+ // If we didn't find a EntryAndDistance for this content viewer, make a
+ // new one.
+ if (!found) {
+ EntryAndDistance container(shist, entry,
+ DeprecatedAbs(i - shist->mIndex));
+ shEntries.AppendElement(container);
+ }
+ }
+ }
+
+ // We've found all the entries belonging to shist which have viewers.
+ // Add those entries to our global list and move on.
+ entries.AppendElements(shEntries);
+ }
+
+ // We now have collected all cached content viewers. First check that we
+ // have enough that we actually need to evict some.
+ if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) {
+ return;
+ }
+
+ // If we need to evict, sort our list of entries and evict the largest
+ // ones. (We could of course get better algorithmic complexity here by using
+ // a heap or something more clever. But sHistoryMaxTotalViewers isn't large,
+ // so let's not worry about it.)
+ entries.Sort();
+
+ for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) {
+ (entries[i].mSHistory)->EvictContentViewerForEntry(entries[i].mEntry);
+ }
+}
+
+nsresult nsSHistory::FindEntryForBFCache(nsIBFCacheEntry* aBFEntry,
+ nsISHEntry** aResult,
+ int32_t* aResultIndex) {
+ *aResult = nullptr;
+ *aResultIndex = -1;
+
+ int32_t startIndex, endIndex;
+ WindowIndices(mIndex, &startIndex, &endIndex);
+
+ for (int32_t i = startIndex; i <= endIndex; ++i) {
+ nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
+
+ // Does shEntry have the same BFCacheEntry as the argument to this method?
+ if (shEntry->HasBFCacheEntry(aBFEntry)) {
+ shEntry.forget(aResult);
+ *aResultIndex = i;
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSHistory::EvictExpiredContentViewerForEntry(
+ nsIBFCacheEntry* aBFEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index);
+
+ if (index == mIndex) {
+ NS_WARNING("How did the current SHEntry expire?");
+ return NS_OK;
+ }
+
+ if (shEntry) {
+ EvictContentViewerForEntry(shEntry);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aBFEntry) {
+ RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
+ if (!mHistoryTracker || !entry) {
+ return;
+ }
+
+ mHistoryTracker->AddObject(entry);
+ return;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aBFEntry) {
+ RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
+ MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
+ if (!mHistoryTracker || !entry) {
+ return;
+ }
+
+ mHistoryTracker->RemoveObject(entry);
+}
+
+// Evicts all content viewers in all history objects. This is very
+// inefficient, because it requires a linear search through all SHistory
+// objects for each viewer to be evicted. However, this method is called
+// infrequently -- only when the disk or memory cache is cleared.
+
+// static
+void nsSHistory::GloballyEvictAllContentViewers() {
+ int32_t maxViewers = sHistoryMaxTotalViewers;
+ sHistoryMaxTotalViewers = 0;
+ GloballyEvictContentViewers();
+ sHistoryMaxTotalViewers = maxViewers;
+}
+
+void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
+ int32_t count = aEntry->GetChildCount();
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ if (child->IsDynamicallyAdded()) {
+ child->GetDocshellID(*aDocshellIDs.AppendElement());
+ } else {
+ GetDynamicChildren(child, aDocshellIDs);
+ }
+ }
+ }
+}
+
+bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
+ nsTArray<nsID>& aDocshellIDs) {
+ bool didRemove = false;
+ int32_t childCount = aRoot->GetChildCount();
+ for (int32_t i = childCount - 1; i >= 0; --i) {
+ nsCOMPtr<nsISHEntry> child;
+ aRoot->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ nsID docshelldID;
+ child->GetDocshellID(docshelldID);
+ if (aDocshellIDs.Contains(docshelldID)) {
+ didRemove = true;
+ aRoot->RemoveChild(child);
+ } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
+ didRemove = true;
+ }
+ }
+ }
+ return didRemove;
+}
+
+bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
+ nsTArray<nsID>& aEntryIDs) {
+ nsCOMPtr<nsISHEntry> root;
+ aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
+ return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
+}
+
+bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) {
+ if (!aEntry1 && !aEntry2) {
+ return true;
+ }
+ if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
+ return false;
+ }
+ uint32_t id1 = aEntry1->GetID();
+ uint32_t id2 = aEntry2->GetID();
+ if (id1 != id2) {
+ return false;
+ }
+
+ int32_t count1 = aEntry1->GetChildCount();
+ int32_t count2 = aEntry2->GetChildCount();
+ // We allow null entries in the end of the child list.
+ int32_t count = std::max(count1, count2);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child1, child2;
+ aEntry1->GetChildAt(i, getter_AddRefs(child1));
+ aEntry2->GetChildAt(i, getter_AddRefs(child2));
+ if (!IsSameTree(child1, child2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
+ NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
+ NS_ASSERTION(aIndex != 0 || aKeepNext,
+ "If we're removing index 0 we must be keeping the next");
+ NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
+
+ int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
+
+ nsresult rv;
+ nsCOMPtr<nsISHEntry> root1, root2;
+ rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ SHistoryChangeNotifier change(this);
+
+ if (IsSameTree(root1, root2)) {
+ mEntries.RemoveElementAt(aIndex);
+
+ // FIXME Bug 1546350: Reimplement history listeners.
+ // if (mRootBC && mRootBC->GetDocShell()) {
+ // static_cast<nsDocShell*>(mRootBC->GetDocShell())
+ // ->HistoryEntryRemoved(aIndex);
+ //}
+
+ // Adjust our indices to reflect the removed entry.
+ if (mIndex > aIndex) {
+ mIndex = mIndex - 1;
+ }
+
+ // NB: If the entry we are removing is the entry currently
+ // being navigated to (mRequestedIndex) then we adjust the index
+ // only if we're not keeping the next entry (because if we are keeping
+ // the next entry (because the current is a duplicate of the next), then
+ // that entry slides into the spot that we're currently pointing to.
+ // We don't do this adjustment for mIndex because mIndex cannot equal
+ // aIndex.
+
+ // NB: We don't need to guard on mRequestedIndex being nonzero here,
+ // because either they're strictly greater than aIndex which is at least
+ // zero, or they are equal to aIndex in which case aKeepNext must be true
+ // if aIndex is zero.
+ if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
+ mRequestedIndex = mRequestedIndex - 1;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
+ bool didRemove;
+ RemoveEntries(aIDs, aStartIndex, &didRemove);
+ if (didRemove && mRootBC && mRootBC->GetDocShell()) {
+ mRootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+}
+
+void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+ bool* aDidRemove) {
+ SHistoryChangeNotifier change(this);
+
+ int32_t index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
+ }
+ int32_t minIndex = index;
+ index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
+ }
+
+ // We need to remove duplicate nsSHEntry trees.
+ *aDidRemove = false;
+ while (index > minIndex) {
+ if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
+ *aDidRemove = true;
+ }
+ --index;
+ }
+
+ UpdateRootBrowsingContextState();
+}
+
+void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
+ int32_t count = aEntry->GetChildCount();
+ AutoTArray<nsID, 16> ids;
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ child->GetDocshellID(*ids.AppendElement());
+ }
+ }
+ RemoveEntries(ids, mIndex);
+}
+
+void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
+ // Remove dynamic entries which are at the index and belongs to the container.
+ nsCOMPtr<nsISHEntry> entry(aEntry);
+ if (!entry) {
+ GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+ }
+
+ if (entry) {
+ AutoTArray<nsID, 16> toBeRemovedEntries;
+ GetDynamicChildren(entry, toBeRemovedEntries);
+ if (toBeRemovedEntries.Length()) {
+ RemoveEntries(toBeRemovedEntries, aIndex);
+ }
+ }
+}
+
+void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index);
+ if (shEntry) {
+ RemoveDynEntries(index, shEntry);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::UpdateIndex() {
+ SHistoryChangeNotifier change(this);
+
+ // Update the actual index with the right value.
+ if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
+ mIndex = mRequestedIndex;
+ }
+
+ mRequestedIndex = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GotoIndex(int32_t aIndex) {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = GotoIndex(aIndex, loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) {
+ int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex;
+ if (index > -1 && (mEntries[index] != aEntry)) {
+ ReplaceEntry(index, aEntry);
+ }
+}
+
+nsresult nsSHistory::GotoIndex(int32_t aIndex,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch) {
+ return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResults,
+ aSameEpoch);
+}
+
+NS_IMETHODIMP_(bool)
+nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
+ nsCOMPtr<nsISHEntry> entry;
+ GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+ if (!entry) {
+ return false;
+ }
+ return entry->GetHasUserInteraction();
+}
+
+nsresult nsSHistory::LoadNextPossibleEntry(
+ int32_t aNewIndex, long aLoadType, uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ mRequestedIndex = -1;
+ if (aNewIndex < mIndex) {
+ return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResults);
+ }
+ if (aNewIndex > mIndex) {
+ return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResults);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType,
+ uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch) {
+ MOZ_LOG(gSHistoryLog, LogLevel::Debug,
+ ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
+ if (!mRootBC) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
+ // The index is out of range
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is a normal local history navigation.
+
+ nsCOMPtr<nsISHEntry> prevEntry;
+ nsCOMPtr<nsISHEntry> nextEntry;
+ GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
+ GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry));
+ if (!nextEntry || !prevEntry) {
+ mRequestedIndex = -1;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (aHistCmd == HIST_CMD_GOTOINDEX) {
+ // https://html.spec.whatwg.org/#history-traversal:
+ // To traverse the history
+ // "If entry has a different Document object than the current entry, then
+ // run the following substeps: Remove any tasks queued by the history
+ // traversal task source..."
+ //
+ // aSameEpoch is true only if the navigations would have been
+ // generated in the same spin of the event loop (i.e. history.go(-2);
+ // history.go(-1)) and from the same content process.
+ if (aSameEpoch) {
+ bool same_doc = false;
+ prevEntry->SharesDocumentWith(nextEntry, &same_doc);
+ if (!same_doc) {
+ MOZ_LOG(
+ gSHistoryLog, LogLevel::Debug,
+ ("Aborting GotoIndex %d - same epoch and not same doc", aIndex));
+ // Ignore this load. Before SessionHistoryInParent, this would
+ // have been dropped in InternalLoad after we filter out SameDoc
+ // loads.
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ // Keep note of requested history index in mRequestedIndex; after all bailouts
+ mRequestedIndex = aIndex;
+
+ // Remember that this entry is getting loaded at this point in the sequence
+
+ nextEntry->SetLastTouched(++gTouchCounter);
+
+ // Get the uri for the entry we are about to visit
+ nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
+
+ MOZ_ASSERT(nextURI, "nextURI can't be null");
+
+ // Send appropriate listener notifications.
+ if (aHistCmd == HIST_CMD_GOTOINDEX) {
+ // We are going somewhere else. This is not reload either
+ NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
+ }
+
+ if (mRequestedIndex == mIndex) {
+ // Possibly a reload case
+ InitiateLoad(nextEntry, mRootBC, aLoadType, aLoadResults);
+ return NS_OK;
+ }
+
+ // Going back or forward.
+ bool differenceFound = LoadDifferingEntries(prevEntry, nextEntry, mRootBC,
+ aLoadType, aLoadResults);
+ if (!differenceFound) {
+ // We did not find any differences. Go further in the history.
+ return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResults);
+ }
+
+ return NS_OK;
+}
+
+bool nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry,
+ nsISHEntry* aNextEntry,
+ BrowsingContext* aParent, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
+
+ uint32_t prevID = aPrevEntry->GetID();
+ uint32_t nextID = aNextEntry->GetID();
+
+ // Check the IDs to verify if the pages are different.
+ if (prevID != nextID) {
+ // Set the Subframe flag if not navigating the root docshell.
+ aNextEntry->SetIsSubFrame(aParent != mRootBC);
+ InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResults);
+ return true;
+ }
+
+ // The entries are the same, so compare any child frames
+ int32_t pcnt = aPrevEntry->GetChildCount();
+ int32_t ncnt = aNextEntry->GetChildCount();
+
+ // Create an array for child browsing contexts.
+ nsTArray<RefPtr<BrowsingContext>> browsingContexts;
+ aParent->GetChildren(browsingContexts);
+
+ // Search for something to load next.
+ bool differenceFound = false;
+ for (int32_t i = 0; i < ncnt; ++i) {
+ // First get an entry which may cause a new page to be loaded.
+ nsCOMPtr<nsISHEntry> nChild;
+ aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
+ if (!nChild) {
+ continue;
+ }
+ nsID docshellID;
+ nChild->GetDocshellID(docshellID);
+
+ // Then find the associated docshell.
+ RefPtr<BrowsingContext> bcChild;
+ for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
+ if (bc->GetHistoryID() == docshellID) {
+ bcChild = bc;
+ break;
+ }
+ }
+ if (!bcChild) {
+ continue;
+ }
+
+ // Then look at the previous entries to see if there was
+ // an entry for the docshell.
+ nsCOMPtr<nsISHEntry> pChild;
+ for (int32_t k = 0; k < pcnt; ++k) {
+ nsCOMPtr<nsISHEntry> child;
+ aPrevEntry->GetChildAt(k, getter_AddRefs(child));
+ if (child) {
+ nsID dID;
+ child->GetDocshellID(dID);
+ if (dID == docshellID) {
+ pChild = child;
+ break;
+ }
+ }
+ }
+ if (!pChild) {
+ continue;
+ }
+
+ // Finally recursively call this method.
+ // This will either load a new page to shell or some subshell or
+ // do nothing.
+ if (LoadDifferingEntries(pChild, nChild, bcChild, aLoadType,
+ aLoadResults)) {
+ differenceFound = true;
+ }
+ }
+ return differenceFound;
+}
+
+void nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry,
+ BrowsingContext* aFrameBC, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ MOZ_ASSERT(aFrameBC && aFrameEntry);
+
+ LoadEntryResult* loadResult = aLoadResults.AppendElement();
+ loadResult->mBrowsingContext = aFrameBC;
+
+ nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
+
+ /* Set the loadType in the SHEntry too to what was passed on.
+ * This will be passed on to child subframes later in nsDocShell,
+ * so that proper loadType is maintained through out a frameset
+ */
+ aFrameEntry->SetLoadType(aLoadType);
+
+ loadState->SetLoadType(aLoadType);
+
+ loadState->SetSHEntry(aFrameEntry);
+
+ // If we're loading from the current active entry we want to treat it as not
+ // a same-document navigation (see nsDocShell::IsSameDocumentNavigation), so
+ // record that here in the LoadingSessionHistoryEntry.
+ bool loadingFromActiveEntry;
+ if (mozilla::SessionHistoryInParent()) {
+ loadingFromActiveEntry =
+ aFrameBC->Canonical()->GetActiveSessionHistoryEntry() == aFrameEntry;
+ } else {
+ loadingFromActiveEntry =
+ aFrameBC->GetDocShell() &&
+ nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry);
+ }
+ loadState->SetLoadIsFromSessionHistory(mRequestedIndex, Length(),
+ loadingFromActiveEntry);
+
+ if (mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
+ aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
+ loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
+ }
+
+ nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
+
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aFrameEntry->GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetFirstParty(false);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
+ loadState->SetCsp(csp);
+
+ loadResult->mLoadState = std::move(loadState);
+}
+
+NS_IMETHODIMP
+nsSHistory::CreateEntry(nsISHEntry** aEntry) {
+ nsCOMPtr<nsISHEntry> entry;
+ if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
+ entry = new SessionHistoryEntry();
+ } else {
+ entry = new nsSHEntry();
+ }
+ entry.forget(aEntry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
+ if (mEntries.IsEmpty()) {
+ return true;
+ }
+
+ nsISHEntry* entry = mEntries[0];
+ size_t length = mEntries.Length();
+ for (size_t i = 1; i < length; ++i) {
+ bool sharesDocument = false;
+ mEntries[i]->SharesDocumentWith(entry, &sharesDocument);
+ if (!sharesDocument) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h
new file mode 100644
index 0000000000..f579eac999
--- /dev/null
+++ b/docshell/shistory/nsSHistory.h
@@ -0,0 +1,321 @@
+/* -*- 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 nsSHistory_h
+#define nsSHistory_h
+
+#include "nsCOMPtr.h"
+#include "nsDocShellLoadState.h"
+#include "nsExpirationTracker.h"
+#include "nsISHistory.h"
+#include "nsSHEntryShared.h"
+#include "nsSimpleEnumerator.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIDocShell;
+class nsDocShell;
+class nsSHistoryObserver;
+class nsISHEntry;
+
+namespace mozilla {
+namespace dom {
+class LoadSHEntryResult;
+}
+} // namespace mozilla
+
+class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
+ public nsISHistory,
+ public nsSupportsWeakReference {
+ public:
+ // The timer based history tracker is used to evict bfcache on expiration.
+ class HistoryTracker final : public nsExpirationTracker<nsSHEntryShared, 3> {
+ public:
+ explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout,
+ nsIEventTarget* aEventTarget)
+ : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker",
+ aEventTarget) {
+ MOZ_ASSERT(aSHistory);
+ mSHistory = aSHistory;
+ }
+
+ protected:
+ virtual void NotifyExpired(nsSHEntryShared* aObj) override {
+ RemoveObject(aObj);
+ mSHistory->EvictExpiredContentViewerForEntry(aObj);
+ }
+
+ private:
+ // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
+ // so it's safe to use raw pointer here.
+ nsSHistory* mSHistory;
+ };
+
+ // Structure used in SetChildHistoryEntry
+ struct SwapEntriesData {
+ mozilla::dom::BrowsingContext*
+ ignoreBC; // constant; the browsing context to ignore
+ nsISHEntry* destTreeRoot; // constant; the root of the dest tree
+ nsISHEntry* destTreeParent; // constant; the node under destTreeRoot
+ // whose children will correspond to aEntry
+ };
+
+ explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHISTORY
+
+ // One time initialization method
+ static nsresult Startup();
+ static void Shutdown();
+ static void UpdatePrefs();
+
+ // Max number of total cached content viewers. If the pref
+ // browser.sessionhistory.max_total_viewers is negative, then
+ // this value is calculated based on the total amount of memory.
+ // Otherwise, it comes straight from the pref.
+ static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
+
+ // Get the root SHEntry from a given entry.
+ static already_AddRefed<nsISHEntry> GetRootSHEntry(nsISHEntry* aEntry);
+
+ // Callback prototype for WalkHistoryEntries.
+ // `aEntry` is the child history entry, `aBC` is its corresponding browsing
+ // context, `aChildIndex` is the child's index in its parent entry, and
+ // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs
+ // that are passed as `aData` to this function have a field
+ // `aEntriesToUpdate`, which is an array of entries we need to update in
+ // docshell, if the 'SH in parent' pref is on (which implies that this method
+ // is executed in the parent)
+ typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ int32_t aChildIndex, void* aData);
+
+ // Clone a session history tree for subframe navigation.
+ // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
+ // for the entry with id |aCloneID|, which will be replaced with
+ // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
+ // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
+ // have that pointer updated to point to the cloned history entry.
+ // If aCloneChildren is true then the children of the entry with id
+ // |aCloneID| will be cloned into |aReplaceEntry|.
+ static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
+ mozilla::dom::BrowsingContext* aOwnerBC,
+ uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+ bool aCloneChildren, nsISHEntry** aDestEntry);
+
+ // Child-walking callback for CloneAndReplace
+ static nsresult CloneAndReplaceChild(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aOwnerBC,
+ int32_t aChildIndex, void* aData);
+
+ // Child-walking callback for SetHistoryEntry
+ static nsresult SetChildHistoryEntry(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ int32_t aEntryIndex, void* aData);
+
+ // For each child of aRootEntry, find the corresponding shell which is
+ // a child of aBC, and call aCallback. The opaque pointer aData
+ // is passed to the callback.
+ static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ WalkHistoryEntriesFunc aCallback,
+ void* aData);
+
+ // This function finds all entries that are contiguous and same-origin with
+ // the aEntry. And call the aCallback on them, including the aEntry. This only
+ // works for the root entries. It will do nothing for non-root entries.
+ static void WalkContiguousEntries(
+ nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback);
+
+ nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; }
+
+ void NotifyOnHistoryReplaceEntry();
+
+ void RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+ bool* aDidRemove);
+
+ // The size of the window of SHEntries which can have alive viewers in the
+ // bfcache around the currently active SHEntry.
+ //
+ // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
+ // index + VIEWER_WINDOW alive.
+ static const int32_t VIEWER_WINDOW = 3;
+
+ struct LoadEntryResult {
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ };
+
+ static void LoadURIs(nsTArray<LoadEntryResult>& aLoadResults);
+
+ // If this doesn't return an error then either aLoadResult is set to nothing,
+ // in which case the caller should ignore the load, or it returns a valid
+ // LoadEntryResult in aLoadResult which the caller should use to do the load.
+ nsresult Reload(uint32_t aReloadFlags,
+ nsTArray<LoadEntryResult>& aLoadResults);
+ nsresult ReloadCurrentEntry(nsTArray<LoadEntryResult>& aLoadResults);
+ nsresult GotoIndex(int32_t aIndex, nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch = false);
+
+ void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+ int32_t* aOutEndIndex);
+ void NotifyListenersContentViewerEvicted(uint32_t aNumEvicted);
+
+ int32_t Length() { return int32_t(mEntries.Length()); }
+ int32_t Index() { return mIndex; }
+ mozilla::dom::BrowsingContext* GetBrowsingContext() { return mRootBC; }
+ bool HasOngoingUpdate() { return mHasOngoingUpdate; }
+ void SetHasOngoingUpdate(bool aVal) { mHasOngoingUpdate = aVal; }
+
+ void SetBrowsingContext(mozilla::dom::BrowsingContext* aRootBC) {
+ mRootBC = aRootBC;
+ }
+
+ int32_t GetIndexForReplace() {
+ // Replace current entry in session history; If the requested index is
+ // valid, it indicates the loading was triggered by a history load, and
+ // we should replace the entry at requested index instead.
+ return mRequestedIndex == -1 ? mIndex : mRequestedIndex;
+ }
+
+ // Update the root browsing context state when adding, removing or
+ // replacing entries.
+ void UpdateRootBrowsingContextState();
+
+ void GetEpoch(uint64_t& aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId>& aId) const {
+ aEpoch = mEpoch;
+ aId = mEpochParentId;
+ }
+ void SetEpoch(uint64_t aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId> aId) {
+ mEpoch = aEpoch;
+ mEpochParentId = aId;
+ }
+
+ void LogHistory();
+
+ protected:
+ virtual ~nsSHistory();
+
+ // Weak reference. Do not refcount this.
+ mozilla::dom::BrowsingContext* mRootBC;
+
+ private:
+ friend class nsSHistoryObserver;
+
+ bool LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
+ mozilla::dom::BrowsingContext* aParent,
+ long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults);
+ void InitiateLoad(nsISHEntry* aFrameEntry,
+ mozilla::dom::BrowsingContext* aFrameBC, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResult);
+
+ nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch = false);
+
+ // Find the history entry for a given bfcache entry. It only looks up between
+ // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW).
+ nsresult FindEntryForBFCache(nsIBFCacheEntry* aBFEntry, nsISHEntry** aResult,
+ int32_t* aResultIndex);
+
+ // Evict content viewers in this window which don't lie in the "safe" range
+ // around aIndex.
+ virtual void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
+ void EvictContentViewerForEntry(nsISHEntry* aEntry);
+ static void GloballyEvictContentViewers();
+ static void GloballyEvictAllContentViewers();
+
+ // Calculates a max number of total
+ // content viewers to cache, based on amount of total memory
+ static uint32_t CalcMaxTotalViewers();
+
+ nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
+ uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults);
+
+ // aIndex is the index of the entry which may be removed.
+ // If aKeepNext is true, aIndex is compared to aIndex + 1,
+ // otherwise comparison is done to aIndex - 1.
+ bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
+
+ // We need to update entries in docshell and browsing context.
+ // If our docshell is located in parent or 'SH in parent' pref is off we can
+ // update it directly, Otherwise, we have two choices. If the browsing context
+ // that owns the docshell is in the same process as the process who called us
+ // over IPC, then we save entries that need to be updated in a list, and once
+ // we have returned from the IPC call, we update the docshell in the child
+ // process. Otherwise, if the browsing context is in a different process, we
+ // do a nested IPC call to that process to update the docshell in that
+ // process.
+ static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext* aBC,
+ nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry);
+
+ protected:
+ bool mHasOngoingUpdate;
+ bool mIsRemote;
+ nsTArray<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null
+ private:
+ // Track all bfcache entries and evict on expiration.
+ mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
+ int32_t mIndex; // -1 means "no index"
+ int32_t mRequestedIndex; // -1 means "no requested index"
+
+ // Session History listeners
+ nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
+
+ nsID mRootDocShellID;
+
+ // Max viewers allowed total, across all SHistory objects
+ static int32_t sHistoryMaxTotalViewers;
+
+ // The epoch (and id) tell us what navigations occured within the same
+ // event-loop spin in the child. We need to know this in order to
+ // implement spec requirements for dropping pending navigations when we
+ // do a history navigation, if it's not same-document. Content processes
+ // update the epoch via a runnable on each ::Go (including AsyncGo).
+ uint64_t mEpoch = 0;
+ mozilla::Maybe<mozilla::dom::ContentParentId> mEpochParentId;
+};
+
+// CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent
+// SHistoryChangeNotifier to send automatic index and length updates.
+// When that is done, it is up to the caller to explicitly send those updates.
+// This is needed in cases when the update is a reaction to some change in a
+// child process and child process passes a changeId to the parent side.
+class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges {
+ public:
+ explicit CallerWillNotifyHistoryIndexAndLengthChanges(
+ nsISHistory* aSHistory) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(aSHistory);
+ if (shistory && !shistory->HasOngoingUpdate()) {
+ shistory->SetHasOngoingUpdate(true);
+ mSHistory = shistory;
+ }
+ }
+
+ ~CallerWillNotifyHistoryIndexAndLengthChanges() {
+ if (mSHistory) {
+ mSHistory->SetHasOngoingUpdate(false);
+ }
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+};
+
+inline nsISupports* ToSupports(nsSHistory* aObj) {
+ return static_cast<nsISHistory*>(aObj);
+}
+
+#endif /* nsSHistory */
diff --git a/docshell/test/browser/Bug1622420Child.jsm b/docshell/test/browser/Bug1622420Child.jsm
new file mode 100644
index 0000000000..69a8f88e09
--- /dev/null
+++ b/docshell/test/browser/Bug1622420Child.jsm
@@ -0,0 +1,11 @@
+var EXPORTED_SYMBOLS = ["Bug1622420Child"];
+
+class Bug1622420Child extends JSWindowActorChild {
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "hasWindowContextForTopBC":
+ return !!this.browsingContext.top.currentWindowContext;
+ }
+ return null;
+ }
+}
diff --git a/docshell/test/browser/Bug422543Child.jsm b/docshell/test/browser/Bug422543Child.jsm
new file mode 100644
index 0000000000..b46fa589fd
--- /dev/null
+++ b/docshell/test/browser/Bug422543Child.jsm
@@ -0,0 +1,100 @@
+var EXPORTED_SYMBOLS = ["Bug422543Child"];
+
+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;
+
+class Bug422543Child extends JSWindowActorChild {
+ constructor() {
+ super();
+ }
+
+ actorCreated() {
+ if (listeners) {
+ return;
+ }
+
+ this.shistory = this.docShell.nsIWebNavigation.sessionHistory;
+ listeners = [new SHistoryListener(), new SHistoryListener()];
+
+ for (let listener of listeners) {
+ this.shistory.legacySHistory.addSHistoryListener(listener);
+ }
+ }
+
+ cleanup() {
+ for (let listener of listeners) {
+ this.shistory.legacySHistory.removeSHistoryListener(listener);
+ }
+ this.shistory = null;
+ listeners = null;
+ return {};
+ }
+
+ getListenerStatus() {
+ return listeners.map(l => l.last);
+ }
+
+ resetListeners() {
+ for (let listener of listeners) {
+ listener.last = "initial";
+ }
+
+ return {};
+ }
+
+ notifyReload() {
+ let history = this.shistory.legacySHistory;
+ let rval = history.notifyOnHistoryReload();
+ return { rval };
+ }
+
+ setRetval({ num, val }) {
+ listeners[num].retval = val;
+ return {};
+ }
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "cleanup":
+ return this.cleanup();
+ case "getListenerStatus":
+ return this.getListenerStatus();
+ case "notifyReload":
+ return this.notifyReload();
+ case "resetListeners":
+ return this.resetListeners();
+ case "setRetval":
+ return this.setRetval(msg.data);
+ }
+ return null;
+ }
+}
diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini
new file mode 100644
index 0000000000..f47f772731
--- /dev/null
+++ b/docshell/test/browser/browser.ini
@@ -0,0 +1,183 @@
+[DEFAULT]
+support-files =
+ Bug422543Child.jsm
+ dummy_page.html
+ favicon_bug655270.ico
+ file_bug234628-1-child.html
+ file_bug234628-1.html
+ file_bug234628-10-child.xhtml
+ file_bug234628-10.html
+ file_bug234628-11-child.xhtml
+ file_bug234628-11-child.xhtml^headers^
+ file_bug234628-11.html
+ file_bug234628-2-child.html
+ file_bug234628-2.html
+ file_bug234628-3-child.html
+ file_bug234628-3.html
+ file_bug234628-4-child.html
+ file_bug234628-4.html
+ file_bug234628-5-child.html
+ file_bug234628-5.html
+ file_bug234628-6-child.html
+ file_bug234628-6-child.html^headers^
+ file_bug234628-6.html
+ file_bug234628-7-child.html
+ file_bug234628-7-child.html^headers^
+ file_bug234628-7.html
+ file_bug234628-8-child.html
+ file_bug234628-8.html
+ file_bug234628-9-child.html
+ file_bug234628-9.html
+ file_bug420605.html
+ file_bug503832.html
+ file_bug655270.html
+ file_bug670318.html
+ file_bug852909.pdf
+ file_bug852909.png
+ file_bug1046022.html
+ file_bug1206879.html
+ file_bug1328501.html
+ file_bug1328501_frame.html
+ file_bug1328501_framescript.js
+ file_bug1543077-1-child.html
+ file_bug1543077-1.html
+ file_bug1543077-2-child.html
+ file_bug1543077-2.html
+ file_bug1543077-3-child.html
+ file_bug1543077-3.html
+ file_bug1543077-4-child.html
+ file_bug1543077-4.html
+ file_multiple_pushState.html
+ file_onbeforeunload_0.html
+ file_onbeforeunload_1.html
+ file_onbeforeunload_2.html
+ file_onbeforeunload_3.html
+ print_postdata.sjs
+ test-form_sjis.html
+ timelineMarkers-04.html
+ browser_timelineMarkers-frame-02.js
+ browser_timelineMarkers-frame-03.js
+ browser_timelineMarkers-frame-04.js
+ browser_timelineMarkers-frame-05.js
+ head.js
+ frame-head.js
+ file_data_load_inherit_csp.html
+ file_click_link_within_view_source.html
+ onload_message.html
+ onpageshow_message.html
+ file_cross_process_csp_inheritance.html
+ file_open_about_blank.html
+ file_slow_load.sjs
+ file_bug1648464-1.html
+ file_bug1648464-1-child.html
+ file_bug1688368-1.sjs
+
+[browser_backforward_userinteraction.js]
+support-files =
+ dummy_iframe_page.html
+[browser_backforward_userinteraction_about.js]
+[browser_bug1543077-1.js]
+[browser_bug1543077-2.js]
+[browser_bug1543077-3.js]
+[browser_bug1543077-4.js]
+[browser_bug1594938.js]
+[browser_bug1206879.js]
+[browser_bug1309900_crossProcessHistoryNavigation.js]
+[browser_bug1328501.js]
+[browser_bug1347823.js]
+fail-if = sessionHistoryInParent
+[browser_bug134911.js]
+[browser_bug1415918_beforeunload_options.js]
+[browser_bug1622420.js]
+support-files =
+ file_bug1622420.html
+ Bug1622420Child.jsm
+[browser_bug1673702.js]
+skip-if =
+ os == "linux" && bits == 64 && os_version == "18.04" && debug # Bug 1674513
+ os == "win" # Bug 1674513
+support-files =
+ file_bug1673702.json
+ file_bug1673702.json^headers^
+[browser_bug234628-1.js]
+[browser_bug234628-10.js]
+[browser_bug234628-11.js]
+[browser_bug234628-2.js]
+[browser_bug234628-3.js]
+[browser_bug234628-4.js]
+[browser_bug234628-5.js]
+[browser_bug234628-6.js]
+[browser_bug234628-7.js]
+[browser_bug234628-8.js]
+[browser_bug234628-9.js]
+[browser_bug349769.js]
+[browser_bug388121-1.js]
+[browser_bug388121-2.js]
+[browser_bug420605.js]
+skip-if = verify
+[browser_bug422543.js]
+[browser_bug441169.js]
+[browser_bug503832.js]
+skip-if = verify
+[browser_bug554155.js]
+[browser_bug655270.js]
+[browser_bug655273.js]
+[browser_bug670318.js]
+[browser_bug673467.js]
+[browser_bug852909.js]
+skip-if = (verify && debug && (os == 'win'))
+[browser_bug92473.js]
+[browser_data_load_inherit_csp.js]
+[browser_dataURI_unique_opaque_origin.js]
+[browser_fission_maxOrigins.js]
+[browser_uriFixupIntegration.js]
+[browser_uriFixupAlternateRedirects.js]
+support-files =
+ redirect_to_example.sjs
+[browser_loadURI_postdata.js]
+[browser_multiple_pushState.js]
+[browser_onbeforeunload.js]
+skip-if = tsan # Bug 1683730
+[browser_onbeforeunload_navigation.js]
+skip-if = (os == 'win' && !debug) # bug 1300351
+[browser_onunload_stop.js]
+[browser_overlink.js]
+support-files =
+ overlink_test.html
+[browser_platform_emulation.js]
+[browser_search_notification.js]
+[browser_tab_touch_events.js]
+[browser_timelineMarkers-01.js]
+[browser_timelineMarkers-02.js]
+skip-if = true # Bug 1220415
+[browser_timelineMarkers-03.js]
+[browser_timelineMarkers-04.js]
+[browser_timelineMarkers-05.js]
+[browser_ua_emulation.js]
+[browser_history_triggeringprincipal_viewsource.js]
+[browser_click_link_within_view_source.js]
+[browser_browsingContext-01.js]
+skip-if = sessionHistoryInParent
+[browser_browsingContext-02.js]
+[browser_browsingContext-getAllBrowsingContextsInSubtree.js]
+[browser_browsingContext-getWindowByName.js]
+[browser_browsingContext-embedder.js]
+[browser_csp_uir.js]
+support-files =
+ file_csp_uir.html
+ file_csp_uir_dummy.html
+[browser_cross_process_csp_inheritance.js]
+skip-if = !e10s # e10s specific test.
+[browser_tab_replace_while_loading.js]
+skip-if = (os == 'linux' && bits == 64 && os_version == '18.04') || (os == "win") # Bug 1604237, Bug 1671794
+[browser_browsing_context_attached.js]
+[browser_browsing_context_discarded.js]
+[browser_fall_back_to_https.js]
+skip-if = (os == 'mac')
+[browser_badCertDomainFixup.js]
+[browser_viewsource_chrome_to_content.js]
+[browser_viewsource_multipart.js]
+support-files =
+ file_basic_multipart.sjs
+[browser_bug1648464-1.js]
+[browser_bug1688368-1.js]
diff --git a/docshell/test/browser/browser_backforward_userinteraction.js b/docshell/test/browser/browser_backforward_userinteraction.js
new file mode 100644
index 0000000000..a899004d34
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_userinteraction.js
@@ -0,0 +1,380 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+const IFRAME_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_iframe_page.html";
+
+async function assertMenulist(entries, baseURL = TEST_PAGE) {
+ // Wait for the session data to be flushed before continuing the test
+ await new Promise(resolve =>
+ SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
+ );
+
+ let backButton = document.getElementById("back-button");
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(backButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ let nodes = contextMenu.childNodes;
+
+ is(
+ nodes.length,
+ entries.length,
+ "Has the expected number of contextMenu entries"
+ );
+
+ for (let i = 0; i < entries.length; i++) {
+ let node = nodes[i];
+ is(
+ node.getAttribute("uri").replace(/[?|#]/, "!"),
+ baseURL + "!entry=" + entries[i],
+ "contextMenu node has the correct uri"
+ );
+ }
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+}
+
+// There are different ways of loading a page, but they should exhibit roughly the same
+// back-forward behavior for the purpose of requiring user interaction. Thus, we
+// have a utility function that runs the same test with a parameterized method of loading
+// new URLs.
+async function runTopLevelTest(loadMethod, useHashes = false) {
+ let p = useHashes ? "#" : "?";
+
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE + p + "entry=0"
+ );
+ let browser = tab.linkedBrowser;
+
+ assertBackForwardState(false, false);
+
+ await loadMethod(TEST_PAGE + p + "entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist([1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [2, 0] : [2, 1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=3");
+
+ info("Adding user interaction for entry=3");
+ // Add some user interaction to entry 3
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 0,
+ 0,
+ {},
+ browser.browsingContext,
+ true
+ );
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [3, 0] : [3, 2, 1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=4");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [4, 3, 0] : [4, 3, 2, 1, 0]);
+
+ info("Adding user interaction for entry=4");
+ // Add some user interaction to entry 4
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 0,
+ 0,
+ {},
+ browser.browsingContext,
+ true
+ );
+
+ await loadMethod(TEST_PAGE + p + "entry=5");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goBack(TEST_PAGE + p + "entry=4");
+ await goBack(TEST_PAGE + p + "entry=3");
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + p + "entry=2");
+ await goBack(TEST_PAGE + p + "entry=1");
+ }
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goBack(TEST_PAGE + p + "entry=0");
+
+ assertBackForwardState(false, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + p + "entry=1");
+ await goForward(TEST_PAGE + p + "entry=2");
+ }
+
+ await goForward(TEST_PAGE + p + "entry=3");
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goForward(TEST_PAGE + p + "entry=4");
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goForward(TEST_PAGE + p + "entry=5");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+}
+
+async function runIframeTest(loadMethod) {
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ // First test the boring case where we only have one iframe.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_PAGE + "?entry=0"
+ );
+ let browser = tab.linkedBrowser;
+
+ assertBackForwardState(false, false);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=1", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist([0, 0], IFRAME_PAGE);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=2", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0] : [0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ let bc = await SpecialPowers.spawn(browser, [], function() {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+
+ // Add some user interaction to sub entry 2
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=3", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ await loadMethod(TEST_PAGE + "?sub_entry=4", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?sub_entry=3", true);
+ }
+
+ await goBack(TEST_PAGE + "?sub_entry=2", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ await loadMethod(IFRAME_PAGE + "?entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 0, 0] : [1, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Two iframes, now we're talking.
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_PAGE + "?entry=0"
+ );
+ browser = tab.linkedBrowser;
+
+ await loadMethod(IFRAME_PAGE + "?entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [1, 0] : [1, 0], IFRAME_PAGE);
+
+ // Add some user interaction to frame 1.
+ bc = await SpecialPowers.spawn(browser, [], function() {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Add some user interaction to frame 2.
+ bc = await SpecialPowers.spawn(browser, [], function() {
+ return content.document.getElementById("frame2").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Navigate frame 2.
+ await loadMethod(TEST_PAGE + "?sub_entry=1", "frame2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 0] : [1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ // Add some user interaction to frame 1, again.
+ bc = await SpecialPowers.spawn(browser, [], function() {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Navigate frame 2, again.
+ await loadMethod(TEST_PAGE + "?sub_entry=2", "frame2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ await goBack(TEST_PAGE + "?sub_entry=1", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ await goBack(TEST_PAGE + "?sub_entry=0", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+}
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when following links with hash URIs.
+add_task(async function test_hashURI() {
+ async function followLinkHash(url) {
+ info(`Creating and following a link to ${url}`);
+ let browser = gBrowser.selectedBrowser;
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ await SpecialPowers.spawn(browser, [url], function(url) {
+ let a = content.document.createElement("a");
+ a.href = url;
+ content.document.body.appendChild(a);
+ a.click();
+ });
+ await loaded;
+ info(`Loaded ${url}`);
+ }
+
+ await runTopLevelTest(followLinkHash, true);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when using history.pushState.
+add_task(async function test_pushState() {
+ await runTopLevelTest(pushState);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when using loadURI.
+add_task(async function test_loadURI() {
+ await runTopLevelTest(loadURI);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when following a link.
+add_task(async function test_followLink() {
+ await runTopLevelTest(followLink);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when navigating inside an iframe
+// using history.pushState.
+add_task(async function test_iframe_pushState() {
+ await runIframeTest(pushState);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when navigating inside an iframe
+// by following links.
+add_task(async function test_iframe_followLink() {
+ await runIframeTest(followLink);
+});
diff --git a/docshell/test/browser/browser_backforward_userinteraction_about.js b/docshell/test/browser/browser_backforward_userinteraction_about.js
new file mode 100644
index 0000000000..d1d1b24d9e
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_userinteraction_about.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+
+// Regression test for navigating back after visiting an about: page
+// loaded in the parent process.
+add_task(async function test_about_back() {
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE + "?entry=0"
+ );
+ let browser = tab.linkedBrowser;
+ assertBackForwardState(false, false);
+
+ await loadURI(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, false);
+
+ await loadURI(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, false);
+
+ // Add some user interaction to entry 2
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, browser, true);
+
+ await loadURI("about:config");
+ assertBackForwardState(true, false);
+
+ await goBack(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, true);
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, true);
+ }
+
+ await goBack(TEST_PAGE + "?entry=0");
+ assertBackForwardState(false, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, true);
+ }
+
+ await goForward(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, true);
+
+ await goForward("about:config");
+ assertBackForwardState(true, false);
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+});
diff --git a/docshell/test/browser/browser_badCertDomainFixup.js b/docshell/test/browser/browser_badCertDomainFixup.js
new file mode 100644
index 0000000000..783360d7b7
--- /dev/null
+++ b/docshell/test/browser/browser_badCertDomainFixup.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test checks if we are correctly fixing https URLs by prefixing
+// with www. when we encounter a SSL_ERROR_BAD_CERT_DOMAIN error.
+// For example, https://example.com -> https://www.example.com.
+
+const PREF_BAD_CERT_DOMAIN_FIX_ENABLED =
+ "security.bad_cert_domain_error.url_fix_enabled";
+const PREF_ALLOW_HIJACKING_LOCALHOST =
+ "network.proxy.allow_hijacking_localhost";
+
+const BAD_CERT_DOMAIN_ERROR_URL = "https://badcertdomain.example.com:443";
+const FIXED_URL = "https://www.badcertdomain.example.com/";
+
+const BAD_CERT_DOMAIN_ERROR_URL2 =
+ "https://mismatch.badcertdomain.example.com:443";
+const IPV4_ADDRESS = "https://127.0.0.3:433";
+const BAD_CERT_DOMAIN_ERROR_PORT = "https://badcertdomain.example.com:82";
+
+async function verifyErrorPage(errorPageURL) {
+ let certErrorLoaded = BrowserTestUtils.waitForErrorPage(
+ gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.loadURI(gBrowser, errorPageURL);
+ await certErrorLoaded;
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() {
+ let ec;
+ await ContentTaskUtils.waitForCondition(() => {
+ ec = content.document.getElementById("errorCode");
+ return ec.textContent;
+ }, "Error code has been set inside the advanced button panel");
+ is(
+ ec.textContent,
+ "SSL_ERROR_BAD_CERT_DOMAIN",
+ "Correct error code is shown"
+ );
+ });
+}
+
+// Test that "www." is prefixed to a https url when we encounter a bad cert domain
+// error if the "www." form is included in the certificate's subjectAltNames.
+add_task(async function prefixBadCertDomain() {
+ // Turn off the pref and ensure that we show the error page as expected.
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, false);
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_URL);
+ info("Cert error is shown as expected when the fixup pref is disabled");
+
+ // Turn on the pref and test that we fix the HTTPS URL.
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, true);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ let loadSuccessful = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ FIXED_URL
+ );
+ BrowserTestUtils.loadURI(gBrowser, BAD_CERT_DOMAIN_ERROR_URL);
+ await loadSuccessful;
+
+ info("The URL was fixed as expected");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test that we don't prefix "www." to a https url when we encounter a bad cert domain
+// error under certain conditions.
+add_task(async function ignoreBadCertDomain() {
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, true);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ // Test for when "www." form is not present in the certificate.
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_URL2);
+ info("Certificate error was shown as expected");
+
+ // Test that urls with IP addresses are not fixed.
+ Services.prefs.setBoolPref(PREF_ALLOW_HIJACKING_LOCALHOST, true);
+ await verifyErrorPage(IPV4_ADDRESS);
+ Services.prefs.clearUserPref(PREF_ALLOW_HIJACKING_LOCALHOST);
+ info("Certificate error was shown as expected for an IP address");
+
+ // Test that urls with ports are not fixed.
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_PORT);
+ info("Certificate error was shown as expected for a host with port");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/docshell/test/browser/browser_browsingContext-01.js b/docshell/test/browser/browser_browsingContext-01.js
new file mode 100644
index 0000000000..d1d6f947ef
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-01.js
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "about:blank";
+
+async function getBrowsingContextId(browser, id) {
+ return SpecialPowers.spawn(browser, [id], async function(id) {
+ let contextId = content.window.docShell.browsingContext.id;
+
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(id);
+ if (target) {
+ contextId = target.docShell.browsingContext.id;
+ break;
+ }
+
+ frames = frames.concat(Array.from(frame.frames));
+ }
+
+ return contextId;
+ });
+}
+
+async function addFrame(browser, id, parentId) {
+ return SpecialPowers.spawn(browser, [{ parentId, id }], async function({
+ parentId,
+ id,
+ }) {
+ let parent = null;
+ if (parentId) {
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(parentId);
+ if (target) {
+ parent = target.contentWindow.document.body;
+ break;
+ }
+ frames = frames.concat(Array.from(frame.frames));
+ }
+ } else {
+ parent = content.document.body;
+ }
+
+ let frame = await new Promise(resolve => {
+ let frame = content.document.createElement("iframe");
+ frame.id = id || "";
+ frame.url = "about:blank";
+ frame.onload = () => resolve(frame);
+ parent.appendChild(frame);
+ });
+
+ return frame.contentWindow.docShell.browsingContext.id;
+ });
+}
+
+async function removeFrame(browser, id) {
+ return SpecialPowers.spawn(browser, [id], async function(id) {
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(id);
+ if (target) {
+ target.remove();
+ break;
+ }
+
+ frames = frames.concat(Array.from(frame.frames));
+ }
+ });
+}
+
+function getBrowsingContextById(id) {
+ return BrowsingContext.get(id);
+}
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function(
+ browser
+ ) {
+ let topId = await getBrowsingContextId(browser, "");
+ let topContext = getBrowsingContextById(topId);
+ isnot(topContext, null);
+ is(topContext.parent, null);
+ is(
+ topId,
+ browser.browsingContext.id,
+ "<browser> has the correct browsingContext"
+ );
+ is(
+ browser.browserId,
+ topContext.browserId,
+ "browsing context should have a correct <browser> id"
+ );
+
+ let id0 = await addFrame(browser, "frame0");
+ let browsingContext0 = getBrowsingContextById(id0);
+ isnot(browsingContext0, null);
+ is(browsingContext0.parent, topContext);
+
+ await removeFrame(browser, "frame0");
+
+ is(topContext.children.indexOf(browsingContext0), -1);
+
+ // TODO(farre): Handle browsingContext removal [see Bug 1486719].
+ todo_isnot(browsingContext0.parent, topContext);
+ });
+});
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url:
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "dummy_page.html",
+ },
+ async function(browser) {
+ let path = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ );
+ await SpecialPowers.spawn(browser, [path], async function(path) {
+ function waitForMessage(command) {
+ let r;
+ let p = new Promise(resolve => {
+ content.window.addEventListener(
+ "message",
+ e => resolve({ result: r, event: e }),
+ { once: true }
+ );
+ });
+ r = command();
+ return p;
+ }
+
+ // Open a new window and wait for the message.
+ let { result: win, event: e1 } = await waitForMessage(_ =>
+ content.window.open(path + "onpageshow_message.html")
+ );
+
+ is(e1.data, "pageshow");
+
+ {
+ // Create, attach and load an iframe into the window's document.
+ let frame = win.document.createElement("iframe");
+ win.document.body.appendChild(frame);
+ frame.src = "dummy_page.html";
+ await ContentTaskUtils.waitForEvent(frame, "load");
+ }
+
+ is(win.frames.length, 1, "Here we should have an iframe");
+
+ // The frame should have expected browsing context and docshell.
+ let frameBC = win.frames[0].docShell.browsingContext;
+ let winDocShell = win.frames[0].docShell;
+
+ // Navigate the window and wait for the message.
+ let { event: e2 } = await waitForMessage(
+ _ => (win.location = path + "onload_message.html")
+ );
+
+ is(e2.data, "load");
+ is(win.frames.length, 0, "Here there shouldn't be an iframe");
+
+ // Return to the previous document. N.B. we expect to trigger
+ // BFCache here, hence we wait for pageshow.
+ let { event: e3 } = await waitForMessage(_ => win.history.back());
+
+ is(e3.data, "pageshow");
+ is(win.frames.length, 1, "And again there should be an iframe");
+
+ is(winDocShell, win.frames[0].docShell, "BF cache cached docshell");
+ is(
+ frameBC,
+ win.frames[0].docShell.browsingContext,
+ "BF cache cached BC"
+ );
+ is(
+ frameBC.id,
+ win.frames[0].docShell.browsingContext.id,
+ "BF cached BC's have same id"
+ );
+ is(
+ win.docShell.browsingContext.children[0],
+ frameBC,
+ "BF cached BC's should still be a child of its parent"
+ );
+ is(
+ win.docShell.browsingContext,
+ frameBC.parent,
+ "BF cached BC's should still be a connected to its parent"
+ );
+
+ win.close();
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-02.js b/docshell/test/browser/browser_browsingContext-02.js
new file mode 100644
index 0000000000..f4be161fba
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-02.js
@@ -0,0 +1,231 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function(browser) {
+ const BASE1 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ );
+ const BASE2 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://test1.example.com"
+ );
+ const URL = BASE1 + "onload_message.html";
+ let sixth = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ URL + "#sixth",
+ true,
+ true
+ );
+ let seventh = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ URL + "#seventh",
+ true,
+ true
+ );
+ let browserIds = await SpecialPowers.spawn(
+ browser,
+ [{ base1: BASE1, base2: BASE2 }],
+ async function({ base1, base2 }) {
+ let top = content;
+ top.name = "top";
+ top.location.href += "#top";
+
+ let contexts = {
+ top: top.location.href,
+ first: base1 + "dummy_page.html#first",
+ third: base2 + "dummy_page.html#third",
+ second: base1 + "dummy_page.html#second",
+ fourth: base2 + "dummy_page.html#fourth",
+ fifth: base1 + "dummy_page.html#fifth",
+ sixth: base1 + "onload_message.html#sixth",
+ seventh: base1 + "onload_message.html#seventh",
+ };
+
+ function addFrame(target, name) {
+ return content.SpecialPowers.spawn(
+ target,
+ [name, contexts[name]],
+ async (name, context) => {
+ let doc = this.content.document;
+
+ let frame = doc.createElement("iframe");
+ doc.body.appendChild(frame);
+ frame.name = name;
+ frame.src = context;
+ await new Promise(resolve => {
+ frame.addEventListener("load", resolve, { once: true });
+ });
+ return frame.browsingContext;
+ }
+ );
+ }
+
+ function addWindow(target, name, { options, resolve }) {
+ return content.SpecialPowers.spawn(
+ target,
+ [name, contexts[name], options, resolve],
+ (name, context, options, resolve) => {
+ let win = this.content.open(context, name, options);
+ let bc = win && win.docShell.browsingContext;
+
+ if (resolve) {
+ return new Promise(resolve =>
+ this.content.addEventListener("message", () => resolve(bc))
+ );
+ }
+ return Promise.resolve({ name });
+ }
+ );
+ }
+
+ // We're going to create a tree that looks like the
+ // following.
+ //
+ // top sixth seventh
+ // / \
+ // / \ /
+ // first second
+ // / \ /
+ // / \
+ // third fourth - - -
+ // /
+ // /
+ // fifth
+ //
+ // The idea is to have one top level non-auxiliary browsing
+ // context, five nested, one top level auxiliary with an
+ // opener, and one top level without an opener. Given that
+ // set of related and one unrelated browsing contexts we
+ // wish to confirm that targeting is able to find
+ // appropriate browsing contexts.
+
+ // BrowsingContext.findWithName requires access checks, which
+ // can only be performed in the process of the accessor BC's
+ // docShell.
+ function findWithName(bc, name) {
+ return content.SpecialPowers.spawn(bc, [bc, name], (bc, name) => {
+ return bc.findWithName(name);
+ });
+ }
+
+ async function reachable(start, target) {
+ info(start.name, target.name);
+ is(
+ await findWithName(start, target.name),
+ target,
+ [start.name, "can reach", target.name].join(" ")
+ );
+ }
+
+ async function unreachable(start, target) {
+ is(
+ await findWithName(start, target.name),
+ null,
+ [start.name, "can't reach", target.name].join(" ")
+ );
+ }
+
+ let first = await addFrame(top, "first");
+ info("first");
+ let second = await addFrame(top, "second");
+ info("second");
+ let third = await addFrame(first, "third");
+ info("third");
+ let fourth = await addFrame(first, "fourth");
+ info("fourth");
+ let fifth = await addFrame(fourth, "fifth");
+ info("fifth");
+ let sixth = await addWindow(fourth, "sixth", { resolve: true });
+ info("sixth");
+ let seventh = await addWindow(fourth, "seventh", {
+ options: ["noopener"],
+ });
+ info("seventh");
+
+ let origin1 = [first, second, fifth, sixth];
+ let origin2 = [third, fourth];
+
+ let topBC = BrowsingContext.getFromWindow(top);
+ let frames = new Map([
+ [topBC, [topBC, first, second, third, fourth, fifth, sixth]],
+ [first, [topBC, ...origin1, third, fourth]],
+ [second, [topBC, ...origin1, third, fourth]],
+ [third, [topBC, ...origin2, fifth, sixth]],
+ [fourth, [topBC, ...origin2, fifth, sixth]],
+ [fifth, [topBC, ...origin1, third, fourth]],
+ [sixth, [...origin1, third, fourth]],
+ ]);
+
+ for (let [start, accessible] of frames) {
+ for (let frame of frames.keys()) {
+ if (accessible.includes(frame)) {
+ await reachable(start, frame);
+ } else {
+ await unreachable(start, frame);
+ }
+ }
+ await unreachable(start, seventh);
+ }
+
+ let topBrowserId = topBC.browserId;
+ ok(topBrowserId > 0, "Should have a browser ID.");
+ for (let [name, bc] of Object.entries({
+ first,
+ second,
+ third,
+ fourth,
+ fifth,
+ })) {
+ is(
+ bc.browserId,
+ topBrowserId,
+ `${name} frame should have the same browserId as top.`
+ );
+ }
+
+ ok(sixth.browserId > 0, "sixth should have a browserId.");
+ isnot(
+ sixth.browserId,
+ topBrowserId,
+ "sixth frame should have a different browserId to top."
+ );
+
+ return [topBrowserId, sixth.browserId];
+ }
+ );
+
+ [sixth, seventh] = await Promise.all([sixth, seventh]);
+
+ is(
+ browser.browserId,
+ browserIds[0],
+ "browser should have the right browserId."
+ );
+ is(
+ browser.browsingContext.browserId,
+ browserIds[0],
+ "browser's BrowsingContext should have the right browserId."
+ );
+ is(
+ sixth.linkedBrowser.browserId,
+ browserIds[1],
+ "sixth should have the right browserId."
+ );
+ is(
+ sixth.linkedBrowser.browsingContext.browserId,
+ browserIds[1],
+ "sixth's BrowsingContext should have the right browserId."
+ );
+
+ for (let tab of [sixth, seventh]) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-embedder.js b/docshell/test/browser/browser_browsingContext-embedder.js
new file mode 100644
index 0000000000..9473a46eb4
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-embedder.js
@@ -0,0 +1,156 @@
+"use strict";
+
+function observeOnce(topic) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (topic == aTopic) {
+ Services.obs.removeObserver(observer, topic);
+ setTimeout(() => resolve(aSubject), 0);
+ }
+ }, topic);
+ });
+}
+
+add_task(async function runTest() {
+ let fissionWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ remote: true,
+ });
+
+ info(`chrome, parent`);
+ let chromeBC = fissionWindow.docShell.browsingContext;
+ ok(chromeBC.currentWindowGlobal, "Should have a current WindowGlobal");
+ is(chromeBC.embedderWindowGlobal, null, "chrome has no embedder global");
+ is(chromeBC.embedderElement, null, "chrome has no embedder element");
+ is(chromeBC.parent, null, "chrome has no parent");
+
+ // Open a new tab, and check that basic frames work out.
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: fissionWindow.gBrowser,
+ });
+
+ info(`root, parent`);
+ let rootBC = tab.linkedBrowser.browsingContext;
+ ok(rootBC.currentWindowGlobal, "[parent] root has a window global");
+ is(
+ rootBC.embedderWindowGlobal,
+ chromeBC.currentWindowGlobal,
+ "[parent] root has chrome as embedder global"
+ );
+ is(
+ rootBC.embedderElement,
+ tab.linkedBrowser,
+ "[parent] root has browser as embedder element"
+ );
+ is(rootBC.parent, null, "[parent] root has no parent");
+
+ // Test with an in-process frame
+ let frameId = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ info(`root, child`);
+ let rootBC = content.docShell.browsingContext;
+ is(rootBC.embedderElement, null, "[child] root has no embedder");
+ is(rootBC.parent, null, "[child] root has no parent");
+
+ info(`frame, child`);
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+
+ let frameBC = iframe.contentWindow.docShell.browsingContext;
+ is(frameBC.embedderElement, iframe, "[child] frame embedded within iframe");
+ is(frameBC.parent, rootBC, "[child] frame has root as parent");
+
+ return frameBC.id;
+ });
+
+ info(`frame, parent`);
+ let frameBC = BrowsingContext.get(frameId);
+ ok(frameBC.currentWindowGlobal, "[parent] frame has a window global");
+ is(
+ frameBC.embedderWindowGlobal,
+ rootBC.currentWindowGlobal,
+ "[parent] frame has root as embedder global"
+ );
+ is(frameBC.embedderElement, null, "[parent] frame has no embedder element");
+ is(frameBC.parent, rootBC, "[parent] frame has root as parent");
+
+ // Test with an out-of-process iframe.
+
+ let oopID = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ info(`creating oop iframe`);
+ let oop = content.document.createElement("iframe");
+ oop.setAttribute("src", "https://example.com");
+ content.document.body.appendChild(oop);
+
+ await new Promise(resolve => {
+ oop.addEventListener("load", resolve, { once: true });
+ });
+
+ info(`oop frame, child`);
+ let oopBC = oop.frameLoader.browsingContext;
+ is(oopBC.embedderElement, oop, "[child] oop frame embedded within iframe");
+ is(
+ oopBC.parent,
+ content.docShell.browsingContext,
+ "[child] frame has root as parent"
+ );
+
+ return oopBC.id;
+ });
+
+ info(`oop frame, parent`);
+ let oopBC = BrowsingContext.get(oopID);
+ is(
+ oopBC.embedderWindowGlobal,
+ rootBC.currentWindowGlobal,
+ "[parent] oop frame has root as embedder global"
+ );
+ is(oopBC.embedderElement, null, "[parent] oop frame has no embedder element");
+ is(oopBC.parent, rootBC, "[parent] oop frame has root as parent");
+
+ info(`waiting for oop window global`);
+ ok(oopBC.currentWindowGlobal, "[parent] oop frame has a window global");
+
+ // Open a new window, and adopt |tab| into it.
+
+ let newWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ remote: true,
+ });
+
+ info(`new chrome, parent`);
+ let newChromeBC = newWindow.docShell.browsingContext;
+ ok(newChromeBC.currentWindowGlobal, "Should have a current WindowGlobal");
+ is(
+ newChromeBC.embedderWindowGlobal,
+ null,
+ "new chrome has no embedder global"
+ );
+ is(newChromeBC.embedderElement, null, "new chrome has no embedder element");
+ is(newChromeBC.parent, null, "new chrome has no parent");
+
+ isnot(newChromeBC, chromeBC, "different chrome browsing context");
+
+ info(`adopting tab`);
+ let newTab = newWindow.gBrowser.adoptTab(tab);
+
+ is(
+ newTab.linkedBrowser.browsingContext,
+ rootBC,
+ "[parent] root browsing context survived"
+ );
+ is(
+ rootBC.embedderWindowGlobal,
+ newChromeBC.currentWindowGlobal,
+ "[parent] embedder window global updated"
+ );
+ is(
+ rootBC.embedderElement,
+ newTab.linkedBrowser,
+ "[parent] embedder element updated"
+ );
+ is(rootBC.parent, null, "[parent] root has no parent");
+
+ info(`closing window`);
+ await BrowserTestUtils.closeWindow(newWindow);
+ await BrowserTestUtils.closeWindow(fissionWindow);
+});
diff --git a/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js b/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js
new file mode 100644
index 0000000000..98c285bcc2
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function addFrame(url) {
+ let iframe = content.document.createElement("iframe");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.src = url;
+ content.document.body.appendChild(iframe);
+ });
+ return iframe.browsingContext;
+}
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ // Add 15 example.com frames to the toplevel document.
+ let frames = await Promise.all(
+ Array.from({ length: 15 }).map(_ =>
+ SpecialPowers.spawn(browser, ["http://example.com/"], addFrame)
+ )
+ );
+
+ // Add an example.org subframe to each example.com frame.
+ let subframes = await Promise.all(
+ Array.from({ length: 15 }).map((_, i) =>
+ SpecialPowers.spawn(frames[i], ["http://example.org/"], addFrame)
+ )
+ );
+
+ Assert.deepEqual(
+ subframes[0].getAllBrowsingContextsInSubtree(),
+ [subframes[0]],
+ "Childless context only has self in subtree"
+ );
+ Assert.deepEqual(
+ frames[0].getAllBrowsingContextsInSubtree(),
+ [frames[0], subframes[0]],
+ "Single-child context has 2 contexts in subtree"
+ );
+ Assert.deepEqual(
+ browser.browsingContext.getAllBrowsingContextsInSubtree(),
+ [browser.browsingContext, ...frames, ...subframes],
+ "Toplevel context has all subtree contexts"
+ );
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-getWindowByName.js b/docshell/test/browser/browser_browsingContext-getWindowByName.js
new file mode 100644
index 0000000000..aa41ee49e4
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-getWindowByName.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function addWindow(name) {
+ var blank = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ blank.data = "about:blank";
+ let promise = BrowserTestUtils.waitForNewWindow({
+ anyWindow: true,
+ url: "about:blank",
+ });
+ Services.ww.openWindow(
+ null,
+ AppConstants.BROWSER_CHROME_URL,
+ name,
+ "chrome,dialog=no",
+ blank
+ );
+ return promise;
+}
+
+add_task(async function() {
+ let windows = [await addWindow("first"), await addWindow("second")];
+
+ for (let w of windows) {
+ isnot(w, null);
+ is(Services.ww.getWindowByName(w.name, null), w, `Found ${w.name}`);
+ }
+
+ await Promise.all(windows.map(BrowserTestUtils.closeWindow));
+});
diff --git a/docshell/test/browser/browser_browsing_context_attached.js b/docshell/test/browser/browser_browsing_context_attached.js
new file mode 100644
index 0000000000..64d299a2d8
--- /dev/null
+++ b/docshell/test/browser/browser_browsing_context_attached.js
@@ -0,0 +1,139 @@
+"use strict";
+
+const TEST_PATH =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "dummy_page.html";
+
+const TOPIC = "browsing-context-attached";
+
+async function observeAttached(callback) {
+ let attached = [];
+ function observer(subject, topic) {
+ is(topic, TOPIC, "observing correct topic");
+ ok(subject instanceof BrowsingContext, "subject to be a BrowsingContext");
+ info(`*** bc id: ${subject.id}`);
+ attached.push(subject);
+ }
+ Services.obs.addObserver(observer, TOPIC);
+ try {
+ await callback();
+ return attached;
+ } finally {
+ Services.obs.removeObserver(observer, TOPIC);
+ }
+}
+
+add_task(async function toplevelForNewWindow() {
+ let win;
+
+ let attached = await observeAttached(async () => {
+ win = await BrowserTestUtils.openNewBrowserWindow();
+ });
+
+ ok(
+ attached.includes(win.browsingContext),
+ "got notification for window's chrome browsing context"
+ );
+ ok(
+ attached.includes(win.gBrowser.selectedBrowser.browsingContext),
+ "got notification for toplevel browsing context"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function toplevelForNewTab() {
+ let tab;
+
+ let attached = await observeAttached(async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ });
+
+ ok(
+ !attached.includes(window.browsingContext),
+ "no notification for the current window's chrome browsing context"
+ );
+ ok(
+ attached.includes(tab.linkedBrowser.browsingContext),
+ "got notification for toplevel browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function subframe() {
+ let browsingContext;
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let attached = await observeAttached(async () => {
+ browsingContext = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+ iframe.contentWindow.location = "https://example.com/";
+ return iframe.browsingContext;
+ });
+ });
+
+ ok(
+ !attached.includes(window.browsingContext),
+ "no notification for the current window's chrome browsing context"
+ );
+ ok(
+ !attached.includes(tab.linkedBrowser.browsingContext),
+ "no notification for toplevel browsing context"
+ );
+ ok(
+ attached.includes(browsingContext),
+ "got notification for frame's browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function toplevelReplacedBy() {
+ let tab;
+
+ let attached = await observeAttached(async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ });
+
+ const firstContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.includes(firstContext),
+ "got notification for initial toplevel browsing context"
+ );
+
+ attached = await observeAttached(async () => {
+ await loadURI(TEST_PATH);
+ });
+ const secondContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.includes(secondContext),
+ "got notification for replaced toplevel browsing context"
+ );
+ isnot(secondContext, firstContext, "browsing context to be replaced");
+ is(
+ secondContext.browserId,
+ firstContext.browserId,
+ "browserId has been kept"
+ );
+
+ attached = await observeAttached(async () => {
+ await loadURI("about:robots");
+ });
+ const thirdContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.includes(thirdContext),
+ "got notification for replaced toplevel browsing context"
+ );
+ isnot(thirdContext, secondContext, "browsing context to be replaced");
+ is(
+ thirdContext.browserId,
+ secondContext.browserId,
+ "browserId has been kept"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_browsing_context_discarded.js b/docshell/test/browser/browser_browsing_context_discarded.js
new file mode 100644
index 0000000000..7c6e1e7b7f
--- /dev/null
+++ b/docshell/test/browser/browser_browsing_context_discarded.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const TOPIC = "browsing-context-discarded";
+
+async function observeDiscarded(browsingContexts, callback) {
+ let discarded = [];
+
+ let promise = BrowserUtils.promiseObserved(TOPIC, subject => {
+ ok(subject instanceof BrowsingContext, "subject to be a BrowsingContext");
+ discarded.push(subject);
+
+ return browsingContexts.every(item => discarded.includes(item));
+ });
+ await callback();
+ await promise;
+
+ return discarded;
+}
+
+add_task(async function toplevelForNewWindow() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let browsingContext = win.gBrowser.selectedBrowser.browsingContext;
+
+ await observeDiscarded([win.browsingContext, browsingContext], async () => {
+ await BrowserTestUtils.closeWindow(win);
+ });
+});
+
+add_task(async function toplevelForNewTab() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browsingContext = tab.linkedBrowser.browsingContext;
+
+ let discarded = await observeDiscarded([browsingContext], () => {
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ ok(
+ !discarded.includes(window.browsingContext),
+ "no notification for the current window's chrome browsing context"
+ );
+});
+
+add_task(async function subframe() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browsingContext = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+ iframe.contentWindow.location = "https://example.com/";
+ return iframe.browsingContext;
+ });
+
+ let discarded = await observeDiscarded([browsingContext], async () => {
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.querySelector("iframe");
+ iframe.remove();
+ });
+ });
+
+ ok(
+ !discarded.includes(tab.browsingContext),
+ "no notification for toplevel browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_bug1206879.js b/docshell/test/browser/browser_bug1206879.js
new file mode 100644
index 0000000000..38d7a0614b
--- /dev/null
+++ b/docshell/test/browser/browser_bug1206879.js
@@ -0,0 +1,49 @@
+add_task(async function() {
+ let url =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://example.com/"
+ ) + "file_bug1206879.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
+
+ let numLocationChanges = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function() {
+ let webprogress = content.docShell.QueryInterface(Ci.nsIWebProgress);
+ let locationChangeCount = 0;
+ let listener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ info("onLocationChange: " + aLocation.spec);
+ locationChangeCount++;
+ this.resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ let locationPromise = new Promise((resolve, reject) => {
+ listener.resolve = resolve;
+ });
+ webprogress.addProgressListener(
+ listener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+
+ content.frames[0].history.pushState(null, null, "foo");
+
+ await locationPromise;
+ webprogress.removeProgressListener(listener);
+
+ return locationChangeCount;
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ is(
+ numLocationChanges,
+ 1,
+ "pushState with a different URI should cause a LocationChange event."
+ );
+});
diff --git a/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js
new file mode 100644
index 0000000000..011811c74d
--- /dev/null
+++ b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js
@@ -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/. */
+
+add_task(async function runTests() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about"
+ );
+
+ registerCleanupFunction(function() {
+ gBrowser.removeTab(tab);
+ });
+
+ let browser = tab.linkedBrowser;
+
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURI(browser, "about:config");
+ let href = await loaded;
+ is(href, "about:config", "Check about:config loaded");
+
+ // Using a dummy onunload listener to disable the bfcache as that can prevent
+ // the test browser load detection mechanism from working.
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURI(
+ browser,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>"
+ );
+ href = await loaded;
+ is(
+ href,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>",
+ "Check data URL loaded"
+ );
+
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ browser.goBack();
+ href = await loaded;
+ is(href, "about:config", "Check we've gone back to about:config");
+
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ browser.goForward();
+ href = await loaded;
+ is(
+ href,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>",
+ "Check we've gone forward to data URL"
+ );
+});
diff --git a/docshell/test/browser/browser_bug1328501.js b/docshell/test/browser/browser_bug1328501.js
new file mode 100644
index 0000000000..15ee970fbe
--- /dev/null
+++ b/docshell/test/browser/browser_bug1328501.js
@@ -0,0 +1,58 @@
+const HTML_URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1328501.html";
+const FRAME_URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1328501_frame.html";
+const FRAME_SCRIPT_URL =
+ "chrome://mochitests/content/browser/docshell/test/browser/file_bug1328501_framescript.js";
+add_task(async function testMultiFrameRestore() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+ await BrowserTestUtils.withNewTab({ gBrowser, url: HTML_URL }, async function(
+ browser
+ ) {
+ // Navigate 2 subframes and load about:blank.
+ let browserLoaded = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [FRAME_URL], async function(FRAME_URL) {
+ function frameLoaded(frame) {
+ frame.contentWindow.location = FRAME_URL;
+ return new Promise(r => (frame.onload = r));
+ }
+ let frame1 = content.document.querySelector("#testFrame1");
+ let frame2 = content.document.querySelector("#testFrame2");
+ ok(frame1, "check found testFrame1");
+ ok(frame2, "check found testFrame2");
+ await frameLoaded(frame1);
+ await frameLoaded(frame2);
+ content.location = "dummy_page.html";
+ });
+ await browserLoaded;
+
+ // Load a frame script to query nsIDOMWindow on "http-on-opening-request",
+ // which will force about:blank content viewers being created.
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+
+ // The frame script also forwards frames-loaded.
+ let framesLoaded = BrowserTestUtils.waitForMessage(
+ browser.messageManager,
+ "test:frames-loaded"
+ );
+
+ browser.goBack();
+ await framesLoaded;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 1000));
+ await SpecialPowers.spawn(browser, [FRAME_URL], FRAME_URL => {
+ is(
+ content.document.querySelector("#testFrame1").contentWindow.location
+ .href,
+ FRAME_URL
+ );
+ is(
+ content.document.querySelector("#testFrame2").contentWindow.location
+ .href,
+ FRAME_URL
+ );
+ });
+ });
+});
diff --git a/docshell/test/browser/browser_bug1347823.js b/docshell/test/browser/browser_bug1347823.js
new file mode 100644
index 0000000000..551ddca8ab
--- /dev/null
+++ b/docshell/test/browser/browser_bug1347823.js
@@ -0,0 +1,77 @@
+/**
+ * Test that session history's expiration tracker would remove bfcache on
+ * expiration.
+ */
+
+// With bfcache not expired.
+add_task(async function testValidCache() {
+ // Make an unrealistic large timeout.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.contentViewerTimeout", 86400]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "data:text/html;charset=utf-8,page1" },
+ async function(browser) {
+ // Make a simple modification for bfcache testing.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.textContent = "modified";
+ });
+
+ // Load a random page.
+ BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Go back and verify text content.
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await awaitPageShow;
+ await SpecialPowers.spawn(browser, [], () => {
+ is(content.document.body.textContent, "modified");
+ });
+ }
+ );
+});
+
+// With bfcache expired.
+add_task(async function testExpiredCache() {
+ // Make bfcache timeout in 1 sec.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.contentViewerTimeout", 1]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "data:text/html;charset=utf-8,page1" },
+ async function(browser) {
+ // Make a simple modification for bfcache testing.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.textContent = "modified";
+ });
+
+ // Load a random page.
+ BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Wait for 3 times of expiration timeout, hopefully it's evicted...
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve => {
+ content.setTimeout(resolve, 3000);
+ });
+ });
+
+ // Go back and verify text content.
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await awaitPageShow;
+ await SpecialPowers.spawn(browser, [], () => {
+ is(content.document.body.textContent, "page1");
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug134911.js b/docshell/test/browser/browser_bug134911.js
new file mode 100644
index 0000000000..c4e2ab42a9
--- /dev/null
+++ b/docshell/test/browser/browser_bug134911.js
@@ -0,0 +1,57 @@
+const TEXT = {
+ /* The test text decoded correctly as Shift_JIS */
+ rightText:
+ "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059",
+
+ enteredText1: "The quick brown fox jumps over the lazy dog",
+ enteredText2:
+ "\u03BE\u03B5\u03C3\u03BA\u03B5\u03C0\u03AC\u03B6\u03C9\u0020\u03C4\u1F74\u03BD\u0020\u03C8\u03C5\u03C7\u03BF\u03C6\u03B8\u03CC\u03C1\u03B1\u0020\u03B2\u03B4\u03B5\u03BB\u03C5\u03B3\u03BC\u03AF\u03B1",
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "test-form_sjis.html"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
+}
+
+function afterOpen() {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [TEXT], function(TEXT) {
+ content.document.getElementById("testtextarea").value = TEXT.enteredText1;
+ content.document.getElementById("testinput").value = TEXT.enteredText2;
+ }).then(() => {
+ /* Force the page encoding to Shift_JIS */
+ BrowserSetForcedCharacterSet("Shift_JIS");
+ });
+}
+
+function afterChangeCharset() {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [TEXT], function(TEXT) {
+ is(
+ content.document.getElementById("testpar").innerHTML,
+ TEXT.rightText,
+ "encoding successfully changed"
+ );
+ is(
+ content.document.getElementById("testtextarea").value,
+ TEXT.enteredText1,
+ "text preserved in <textarea>"
+ );
+ is(
+ content.document.getElementById("testinput").value,
+ TEXT.enteredText2,
+ "text preserved in <input>"
+ );
+ }).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
diff --git a/docshell/test/browser/browser_bug1415918_beforeunload_options.js b/docshell/test/browser/browser_bug1415918_beforeunload_options.js
new file mode 100644
index 0000000000..5ce57cbb60
--- /dev/null
+++ b/docshell/test/browser/browser_bug1415918_beforeunload_options.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+SimpleTest.requestFlakyTimeout("Needs to test a timeout");
+
+function delay(msec) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+
+ const permitUnloadTimeout = Services.prefs.getIntPref(
+ "dom.beforeunload_timeout_ms"
+ );
+
+ let url = TEST_PATH + "dummy_page.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.addEventListener("beforeunload", event => {
+ event.preventDefault();
+ });
+ });
+
+ let allowNavigation;
+ let promptShown = false;
+ let promptDismissed = false;
+ let promptTimeout;
+
+ const DIALOG_TOPIC = "tabmodal-dialog-loaded";
+ async function observer(node) {
+ promptShown = true;
+
+ if (promptTimeout) {
+ await delay(promptTimeout);
+ }
+
+ let button = node.querySelector(
+ `.tabmodalprompt-button${allowNavigation ? 0 : 1}`
+ );
+ button.click();
+ promptDismissed = true;
+ }
+ Services.obs.addObserver(observer, DIALOG_TOPIC);
+
+ /*
+ * Check condition where beforeunload handlers request a prompt.
+ */
+
+ // Prompt is shown, user clicks OK.
+ allowNavigation = true;
+ promptShown = false;
+
+ ok(browser.permitUnload().permitUnload, "permit unload should be true");
+ ok(promptShown, "prompt should have been displayed");
+
+ // Prompt is shown, user clicks CANCEL.
+ allowNavigation = false;
+ promptShown = false;
+
+ ok(!browser.permitUnload().permitUnload, "permit unload should be false");
+ ok(promptShown, "prompt should have been displayed");
+
+ // Prompt is not shown, don't permit unload.
+ promptShown = false;
+ ok(
+ !browser.permitUnload("dontUnload").permitUnload,
+ "permit unload should be false"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ // Prompt is not shown, permit unload.
+ promptShown = false;
+ ok(
+ browser.permitUnload("unload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ promptShown = false;
+ promptDismissed = false;
+ promptTimeout = 3 * permitUnloadTimeout;
+ let promise = browser.asyncPermitUnload();
+
+ let promiseResolved = false;
+ promise.then(() => {
+ promiseResolved = true;
+ });
+
+ await TestUtils.waitForCondition(() => promptShown);
+ ok(!promptDismissed, "Should not have dismissed prompt yet");
+ ok(!promiseResolved, "Should not have resolved promise yet");
+
+ await delay(permitUnloadTimeout * 1.5);
+
+ ok(!promptDismissed, "Should not have dismissed prompt yet");
+ ok(!promiseResolved, "Should not have resolved promise yet");
+
+ let { permitUnload } = await promise;
+ ok(promptDismissed, "Should have dismissed prompt");
+ ok(!permitUnload, "Should not have permitted unload");
+
+ promptTimeout = null;
+
+ /*
+ * Check condition where no one requests a prompt. In all cases,
+ * permitUnload should be true, and all handlers fired.
+ */
+
+ allowNavigation = true;
+
+ url += "?1";
+ BrowserTestUtils.loadURI(browser, url);
+ await BrowserTestUtils.browserLoaded(browser, false, url);
+
+ promptShown = false;
+ ok(browser.permitUnload().permitUnload, "permit unload should be true");
+ ok(!promptShown, "prompt should not have been displayed");
+
+ promptShown = false;
+ ok(
+ browser.permitUnload("dontUnload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ promptShown = false;
+ ok(
+ browser.permitUnload("unload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ await BrowserTestUtils.removeTab(tab);
+
+ Services.obs.removeObserver(observer, DIALOG_TOPIC);
+});
diff --git a/docshell/test/browser/browser_bug1543077-1.js b/docshell/test/browser/browser_bug1543077-1.js
new file mode 100644
index 0000000000..61507158e8
--- /dev/null
+++ b/docshell/test/browser/browser_bug1543077-1.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1543077-1.html",
+ afterOpen,
+ "Japanese",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u0434"),
+ 131,
+ "Parent doc should be IBM866 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u0434"),
+ 87,
+ "Child doc should be IBM866 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 131,
+ "Parent doc should decode as EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 87,
+ "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_bug1543077-2.js b/docshell/test/browser/browser_bug1543077-2.js
new file mode 100644
index 0000000000..30736d7c76
--- /dev/null
+++ b/docshell/test/browser/browser_bug1543077-2.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1543077-2.html",
+ afterOpen,
+ "Japanese",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u0412"),
+ 134,
+ "Parent doc should be IBM866 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u0412"),
+ 90,
+ "Child doc should be IBM866 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 134,
+ "Parent doc should decode as Shift_JIS subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 90,
+ "Child doc should decode as Shift_JIS subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "Shift_JIS",
+ "Parent doc should report Shift_JIS subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "Shift_JIS",
+ "Child doc should report Shift_JIS subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1543077-3.js b/docshell/test/browser/browser_bug1543077-3.js
new file mode 100644
index 0000000000..fea8c6a365
--- /dev/null
+++ b/docshell/test/browser/browser_bug1543077-3.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1543077-3.html",
+ afterOpen,
+ "Japanese",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 136,
+ "Parent doc should be ISO-2022-JP initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 92,
+ "Child doc should be ISO-2022-JP initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 136,
+ "Parent doc should decode as ISO-2022-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 92,
+ "Child doc should decode as ISO-2022-JP subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "ISO-2022-JP",
+ "Parent doc should report ISO-2022-JP subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "ISO-2022-JP",
+ "Child doc should report ISO-2022-JP subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1543077-4.js b/docshell/test/browser/browser_bug1543077-4.js
new file mode 100644
index 0000000000..ba20352b7f
--- /dev/null
+++ b/docshell/test/browser/browser_bug1543077-4.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1543077-4.html",
+ afterOpen,
+ "Japanese",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u0434"),
+ 131,
+ "Parent doc should be IBM866 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u0412"),
+ 90,
+ "Child doc should be IBM866 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 131,
+ "Parent doc should decode as EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 90,
+ "Child doc should decode as Shift_JIS subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "EUC-JP",
+ "Parent doc should report EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "Shift_JIS",
+ "Child doc should report Shift_JIS subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1594938.js b/docshell/test/browser/browser_bug1594938.js
new file mode 100644
index 0000000000..5a9d4814ba
--- /dev/null
+++ b/docshell/test/browser/browser_bug1594938.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test for Bug 1594938
+ *
+ * If a session history listener blocks reloads we shouldn't crash.
+ */
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "https://example.com/" },
+ async function(browser) {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ let history = this.content.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ let listenerCalled = false;
+ let listener = {
+ OnHistoryNewEntry: aNewURI => {},
+ OnHistoryReload: () => {
+ listenerCalled = true;
+ this.content.setTimeout(() => {
+ testDone.resolve();
+ });
+ return false;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.legacySHistory.addSHistoryListener(listener);
+
+ history.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ await testDone.promise;
+
+ Assert.ok(listenerCalled, "reloads were blocked");
+
+ history.legacySHistory.removeSHistoryListener(listener);
+ });
+
+ return;
+ }
+
+ let history = browser.browsingContext.sessionHistory;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ let listenerCalled = false;
+ let listener = {
+ OnHistoryNewEntry: aNewURI => {},
+ OnHistoryReload: () => {
+ listenerCalled = true;
+ setTimeout(() => {
+ testDone.resolve();
+ });
+ return false;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.addSHistoryListener(listener);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ let history = this.content.docShell.QueryInterface(Ci.nsIWebNavigation)
+ .sessionHistory;
+ history.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ });
+ await testDone.promise;
+
+ Assert.ok(listenerCalled, "reloads were blocked");
+
+ history.removeSHistoryListener(listener);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1622420.js b/docshell/test/browser/browser_bug1622420.js
new file mode 100644
index 0000000000..df15eba583
--- /dev/null
+++ b/docshell/test/browser/browser_bug1622420.js
@@ -0,0 +1,30 @@
+const ACTOR = "Bug1622420";
+
+add_task(async function test() {
+ let base = getRootDirectory(gTestPath).slice(0, -1);
+ ChromeUtils.registerWindowActor(ACTOR, {
+ allFrames: true,
+ child: {
+ moduleURI: `${base}/Bug1622420Child.jsm`,
+ },
+ });
+
+ registerCleanupFunction(async () => {
+ gBrowser.removeTab(tab);
+
+ ChromeUtils.unregisterWindowActor(ACTOR);
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.org/browser/docshell/test/browser/file_bug1622420.html"
+ );
+ let childBC = tab.linkedBrowser.browsingContext.children[0];
+ let success = await childBC.currentWindowGlobal
+ .getActor(ACTOR)
+ .sendQuery("hasWindowContextForTopBC");
+ ok(
+ success,
+ "Should have a WindowContext for the top BrowsingContext in the process of a child BrowsingContext"
+ );
+});
diff --git a/docshell/test/browser/browser_bug1648464-1.js b/docshell/test/browser/browser_bug1648464-1.js
new file mode 100644
index 0000000000..d643a253e9
--- /dev/null
+++ b/docshell/test/browser/browser_bug1648464-1.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1648464-1.html",
+ afterOpen,
+ "_autodetect_all",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u00A4"),
+ 146,
+ "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"),
+ 146,
+ "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_bug1673702.js b/docshell/test/browser/browser_bug1673702.js
new file mode 100644
index 0000000000..483103ffa2
--- /dev/null
+++ b/docshell/test/browser/browser_bug1673702.js
@@ -0,0 +1,24 @@
+const DUMMY =
+ "http://example.org/browser/docshell/test/browser/dummy_page.html";
+const JSON =
+ "http://example.com/browser/docshell/test/browser/file_bug1673702.json";
+
+add_task(async function test_backAndReload() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: DUMMY }, async function(
+ browser
+ ) {
+ info("Start JSON load.");
+ BrowserTestUtils.loadURI(browser, JSON);
+ await BrowserTestUtils.waitForLocationChange(gBrowser, JSON);
+
+ info("JSON load has started, go back.");
+ browser.goBack();
+ await BrowserTestUtils.browserStopped(browser);
+
+ info("Reload.");
+ BrowserReload();
+ await BrowserTestUtils.waitForLocationChange(gBrowser);
+
+ is(browser.documentURI.spec, DUMMY);
+ });
+});
diff --git a/docshell/test/browser/browser_bug1688368-1.js b/docshell/test/browser/browser_bug1688368-1.js
new file mode 100644
index 0000000000..ac3b8f2a38
--- /dev/null
+++ b/docshell/test/browser/browser_bug1688368-1.js
@@ -0,0 +1,25 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1688368-1.sjs",
+ afterOpen,
+ "UTF-8",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.body.textContent.indexOf("â"),
+ 0,
+ "Doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.body.textContent.indexOf("â"),
+ 0,
+ "Doc should be UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-1.js b/docshell/test/browser/browser_bug234628-1.js
new file mode 100644
index 0000000000..e26b582ff7
--- /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,
+ "windows-1251",
+ 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("\u0402"),
+ 129,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u0402"),
+ 85,
+ "Child doc should decode as windows-1251 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "windows-1251",
+ "Child doc should report windows-1251 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..d507378ed6
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-10.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-10.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 151,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 71,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 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..be71746aad
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-11.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-11.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 193,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 107,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 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..bad7a3c442
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-2.js
@@ -0,0 +1,51 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-2.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 129,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf(
+ "\u0432\u201A\u00AC"
+ ),
+ 78,
+ "Child doc should decode as windows-1251 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "windows-1251",
+ "Child doc should report windows-1251 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..218a81e144
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-3.js
@@ -0,0 +1,49 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-3.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 118,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf(
+ "\u0432\u201A\u00AC"
+ ),
+ 73,
+ "Child doc should decode as windows-1251 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "windows-1251",
+ "Child doc should report windows-1251 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..c7c629bf3e
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-4.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-4.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 132,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 79,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 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..6c3462b9fc
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-5.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-5.html",
+ afterOpen,
+ "windows-1251",
+ 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("\u0402"),
+ 146,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 87,
+ "Child doc should decode as utf-16 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 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..8b5994d52d
--- /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,
+ "windows-1251",
+ 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("\u0402"),
+ 190,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 109,
+ "Child doc should decode as utf-16 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-16BE",
+ "Child doc should report UTF-16 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-7.js b/docshell/test/browser/browser_bug234628-7.js
new file mode 100644
index 0000000000..10c4b432f9
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-7.js
@@ -0,0 +1,49 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-7.html",
+ afterOpen,
+ "windows-1251",
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 188,
+ "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("\u0402"),
+ 188,
+ "Parent doc should decode as windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf(
+ "\u0432\u201A\u00AC"
+ ),
+ 107,
+ "Child doc should decode as windows-1251 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1251",
+ "Parent doc should report windows-1251 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "windows-1251",
+ "Child doc should report windows-1251 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..1c27619f2e
--- /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/";
+ runCharsetTest(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..2ba714c09c
--- /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/";
+ runCharsetTest(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..6fde42e876
--- /dev/null
+++ b/docshell/test/browser/browser_bug349769.js
@@ -0,0 +1,72 @@
+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.loadURI(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..6206e158af
--- /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..d6e88887dc
--- /dev/null
+++ b/docshell/test/browser/browser_bug388121-2.js
@@ -0,0 +1,73 @@
+function test() {
+ waitForExplicitFinish();
+
+ var w;
+ var iteration = 1;
+ const uris = ["", "about:blank"];
+ var uri;
+ var origDoc;
+
+ function testLoad() {
+ if (w.document == origDoc) {
+ // Go back to polling
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(testLoad, 10);
+ return;
+ }
+ var prin = w.document.nodePrincipal;
+ 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").content;
+ var prin = w.document.nodePrincipal;
+ 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 {
+ origDoc = w.document;
+ // 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..9287de0e46
--- /dev/null
+++ b/docshell/test/browser/browser_bug420605.js
@@ -0,0 +1,133 @@
+/* Test for Bug 420605
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=420605
+ */
+
+const { PlacesTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+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;
+ }),
+ "places"
+ );
+ 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;
+ }),
+ "places"
+ );
+
+ // Now open the test page in a new tab.
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "DOMContentLoaded",
+ true
+ ).then(onPageLoad);
+ BrowserTestUtils.loadURI(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..7d29a0017b
--- /dev/null
+++ b/docshell/test/browser/browser_bug422543.js
@@ -0,0 +1,251 @@
+/* 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, () =>
+ BrowserTestUtils.loadURI(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: {
+ moduleURI: `${base}/Bug422543Child.jsm`,
+ },
+ });
+
+ 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, () =>
+ BrowserTestUtils.loadURI(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..c912b83a31
--- /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..56915ac58f
--- /dev/null
+++ b/docshell/test/browser/browser_bug554155.js
@@ -0,0 +1,32 @@
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { 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..88d662ba0c
--- /dev/null
+++ b/docshell/test/browser/browser_bug655270.js
@@ -0,0 +1,64 @@
+/* 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.import(
+ "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+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;
+ }),
+ "places"
+ );
+ let icon2;
+ let promiseIcon2 = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == newURL) {
+ icon2 = e.faviconUrl;
+ return true;
+ }
+ return false;
+ }),
+ "places"
+ );
+
+ // The page at origURL has a <link rel='icon'>, 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..b788224f38
--- /dev/null
+++ b/docshell/test/browser/browser_bug655273.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * 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(
+ { 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..cb92ee4159
--- /dev/null
+++ b/docshell/test/browser/browser_bug670318.js
@@ -0,0 +1,144 @@
+/* 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.loadURI(browser, URL);
+
+ await testDone.promise;
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug673467.js b/docshell/test/browser/browser_bug673467.js
new file mode 100644
index 0000000000..182cc0ee80
--- /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,<html><body onload='load()'>" +
+ "<script>" +
+ " var iframe = document.createElement('iframe');" +
+ " iframe.id = 'iframe';" +
+ " document.documentElement.appendChild(iframe);" +
+ " function load() {" +
+ " iframe.src = 'data:text/html,Hello!';" +
+ " }" +
+ "</script>" +
+ "</body></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..3d8e8cc6b7
--- /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,
+ "<p> contains expected text"
+ );
+ Assert.equal(
+ content.document.getElementById("testtextarea").innerHTML,
+ text,
+ "<textarea> contains expected text"
+ );
+ Assert.equal(
+ content.document.getElementById("testinput").value,
+ text,
+ "<input> contains expected text"
+ );
+ });
+}
+
+function afterOpen() {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ /* The test text decoded incorrectly as Windows-1251. This is the "right" wrong
+ text; anything else is unexpected. */
+ const wrongText =
+ "\u0453\u2020\u0453\u006A\u0453\u0052\u0403\u005B\u0453\u0068\u201A\u041D\u0403\u0041\u201A\u00B7\u201A\u0427\u201A\u0414\u201A\u041C\u2022\u00B6\u040B\u0459\u201A\u0419\u040A\u0415\u2014\u004C\u201A\u041C\u201D\u0424\u040C\u2020\u201A\u0440\u2022\u0074\u2014\u005E\u201A\u00B5\u201A\u042C\u201A\u00B7";
+
+ /* Test that the content on load is the expected wrong decoding */
+ testContent(wrongText).then(() => {
+ BrowserSetForcedCharacterSet("Shift_JIS");
+ });
+}
+
+function afterChangeCharset() {
+ /* The test text decoded correctly as Shift_JIS */
+ const rightText =
+ "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059";
+
+ /* test that the content is decoded correctly */
+ testContent(rightText).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ // Get the local directory. This needs to be a file: URI because chrome: URIs
+ // are always UTF-8 (bug 617339) and we are testing decoding from other
+ // charsets.
+ var jar = getJar(getRootDirectory(gTestPath));
+ var dir = jar
+ ? extractJarToTmp(jar)
+ : getChromeDir(getResolvedURI(gTestPath));
+ var rootDir = Services.io.newFileURI(dir).spec;
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "test-form_sjis.html"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
+}
diff --git a/docshell/test/browser/browser_click_link_within_view_source.js b/docshell/test/browser/browser_click_link_within_view_source.js
new file mode 100644
index 0000000000..5c298a56c6
--- /dev/null
+++ b/docshell/test/browser/browser_click_link_within_view_source.js
@@ -0,0 +1,78 @@
+"use strict";
+
+/**
+ * Test for Bug 1359204
+ *
+ * Loading a local file, then view-source on that file. Make sure that
+ * clicking a link within that view-source page is not blocked by security checks.
+ */
+
+add_task(async function test_click_link_within_view_source() {
+ let TEST_FILE = "file_click_link_within_view_source.html";
+ let TEST_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+ TEST_FILE_URI.append(TEST_FILE);
+ TEST_FILE_URI = Services.io.newFileURI(TEST_FILE_URI).spec;
+
+ let DUMMY_FILE = "dummy_page.html";
+ let DUMMY_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+ DUMMY_FILE_URI.append(DUMMY_FILE);
+ DUMMY_FILE_URI = Services.io.newFileURI(DUMMY_FILE_URI).spec;
+
+ await BrowserTestUtils.withNewTab(TEST_FILE_URI, async function(aBrowser) {
+ let tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("file://") && tabSpec.endsWith(TEST_FILE),
+ "sanity check to make sure html loaded"
+ );
+
+ info("click view-source of html");
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ document.getElementById("View:PageSource").doCommand();
+
+ let tab = await tabPromise;
+ tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(TEST_FILE),
+ "loading view-source of html succeeded"
+ );
+
+ info("click testlink within view-source page");
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ url => url.endsWith("dummy_page.html")
+ );
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() {
+ if (content.document.readyState != "complete") {
+ await ContentTaskUtils.waitForEvent(
+ content.document,
+ "readystatechange",
+ false,
+ () => content.document.readyState == "complete"
+ );
+ }
+ // document.getElementById() does not work on a view-source page, hence we use document.links
+ let linksOnPage = content.document.links;
+ is(
+ linksOnPage.length,
+ 1,
+ "sanity check: make sure only one link is present on page"
+ );
+ let myLink = content.document.links[0];
+ myLink.click();
+ });
+
+ await loadPromise;
+
+ tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(DUMMY_FILE),
+ "loading view-source of html succeeded"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/docshell/test/browser/browser_cross_process_csp_inheritance.js b/docshell/test/browser/browser_cross_process_csp_inheritance.js
new file mode 100644
index 0000000000..bb29cafa18
--- /dev/null
+++ b/docshell/test/browser/browser_cross_process_csp_inheritance.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_URI = TEST_PATH + "file_cross_process_csp_inheritance.html";
+const DATA_URI =
+ "data:text/html,<html>test-same-diff-process-csp-inhertiance</html>";
+
+const FISSION_ENABLED = SpecialPowers.useRemoteSubframes;
+
+function getCurrentPID(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+}
+
+function getCurrentURI(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ let channel = content.docShell.currentDocumentChannel;
+ return channel.URI.asciiSpec;
+ });
+}
+
+function verifyResult(
+ aTestName,
+ aBrowser,
+ aDataURI,
+ aPID,
+ aSamePID,
+ aFissionEnabled
+) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aDataURI, aPID, aSamePID, aFissionEnabled }],
+ async function({ aTestName, aDataURI, aPID, aSamePID, aFissionEnabled }) {
+ // sanity, to make sure the correct URI was loaded
+ let channel = content.docShell.currentDocumentChannel;
+ is(
+ channel.URI.asciiSpec,
+ aDataURI,
+ aTestName + ": correct data uri loaded"
+ );
+
+ // check that the process ID is the same/different when opening the new tab
+ let pid = Services.appinfo.processID;
+ if (aSamePID) {
+ is(pid, aPID, aTestName + ": process ID needs to be identical");
+ } else if (aFissionEnabled) {
+ // TODO: Fission discards dom.noopener.newprocess.enabled and puts
+ // data: URIs in the same process. Unfortunately todo_isnot is not
+ // defined in that scope, hence we have to use a workaround.
+ todo(
+ false,
+ pid == aPID,
+ ": process ID needs to be different in fission"
+ );
+ } else {
+ isnot(pid, aPID, aTestName + ": process ID needs to be different");
+ }
+
+ // finally, evaluate that the CSP was set.
+ let cspOBJ = JSON.parse(content.document.cspJSON);
+ let policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "should be one policy");
+ let policy = policies[0];
+ is(
+ policy["script-src"],
+ "'none'",
+ aTestName + ": script-src directive matches"
+ );
+ }
+ );
+}
+
+async function simulateCspInheritanceForNewTab(aTestName, aSamePID) {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function(browser) {
+ // do some sanity checks
+ let currentURI = await getCurrentURI(gBrowser.selectedBrowser);
+ is(currentURI, TEST_URI, aTestName + ": correct test uri loaded");
+
+ let pid = await getCurrentPID(gBrowser.selectedBrowser);
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI, true);
+ // simulate click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testLink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyResult(
+ aTestName,
+ gBrowser.selectedBrowser,
+ DATA_URI,
+ pid,
+ aSamePID,
+ FISSION_ENABLED
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+}
+
+add_task(async function test_csp_inheritance_diff_process() {
+ // forcing the new data: URI load to happen in a *new* process by flipping the pref
+ // to force <a rel="noopener" ...> to be loaded in a new process.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.noopener.newprocess.enabled", true]],
+ });
+ await simulateCspInheritanceForNewTab("diff-process-inheritance", false);
+});
+
+add_task(async function test_csp_inheritance_same_process() {
+ // forcing the new data: URI load to happen in a *same* process by resetting the pref
+ // and loaded <a rel="noopener" ...> in the *same* process.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.noopener.newprocess.enabled", false]],
+ });
+ await simulateCspInheritanceForNewTab("same-process-inheritance", true);
+});
diff --git a/docshell/test/browser/browser_csp_uir.js b/docshell/test/browser/browser_csp_uir.js
new file mode 100644
index 0000000000..b1565162bc
--- /dev/null
+++ b/docshell/test/browser/browser_csp_uir.js
@@ -0,0 +1,87 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_URI = TEST_PATH + "file_csp_uir.html"; // important to be http: to test upgrade-insecure-requests
+const RESULT_URI =
+ TEST_PATH.replace("http://", "https://") + "file_csp_uir_dummy.html";
+
+function verifyCSP(aTestName, aBrowser, aResultURI) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aResultURI }],
+ async function({ aTestName, aResultURI }) {
+ let channel = content.docShell.currentDocumentChannel;
+ is(channel.URI.asciiSpec, aResultURI, "testing CSP for " + aTestName);
+ }
+ );
+}
+
+add_task(async function test_csp_inheritance_regular_click() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ RESULT_URI
+ );
+ // set the data href + simulate click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await loadPromise;
+ await verifyCSP("click()", gBrowser.selectedBrowser, RESULT_URI);
+ });
+});
+
+add_task(async function test_csp_inheritance_ctrl_click() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ RESULT_URI,
+ true
+ );
+ // set the data href + simulate ctrl+click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { ctrlKey: true, metaKey: true },
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP("ctrl-click()", gBrowser.selectedBrowser, RESULT_URI);
+ await BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(
+ async function test_csp_inheritance_right_click_open_link_in_new_tab() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, RESULT_URI);
+ // set the data href + simulate right-click open link in tab
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP(
+ "right-click-open-in-new-tab()",
+ gBrowser.selectedBrowser,
+ RESULT_URI
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+ }
+);
diff --git a/docshell/test/browser/browser_dataURI_unique_opaque_origin.js b/docshell/test/browser/browser_dataURI_unique_opaque_origin.js
new file mode 100644
index 0000000000..352c9a616b
--- /dev/null
+++ b/docshell/test/browser/browser_dataURI_unique_opaque_origin.js
@@ -0,0 +1,29 @@
+add_task(async function test_dataURI_unique_opaque_origin() {
+ let tab = BrowserTestUtils.addTab(gBrowser, "http://example.com");
+ let browser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let pagePrincipal = browser.contentPrincipal;
+ info("pagePrincial " + pagePrincipal.origin);
+
+ BrowserTestUtils.loadURI(browser, "data:text/html,hi");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ principal: pagePrincipal }],
+ async function(args) {
+ info("data URI principal: " + content.document.nodePrincipal.origin);
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "data: URI should have NullPrincipal."
+ );
+ Assert.ok(
+ !content.document.nodePrincipal.equals(args.principal),
+ "data: URI should have unique opaque origin."
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_data_load_inherit_csp.js b/docshell/test/browser/browser_data_load_inherit_csp.js
new file mode 100644
index 0000000000..806803d2b5
--- /dev/null
+++ b/docshell/test/browser/browser_data_load_inherit_csp.js
@@ -0,0 +1,109 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const HTML_URI = TEST_PATH + "file_data_load_inherit_csp.html";
+const DATA_URI = "data:text/html;html,<html><body>foo</body></html>";
+
+function setDataHrefOnLink(aBrowser, aDataURI) {
+ return SpecialPowers.spawn(aBrowser, [aDataURI], function(uri) {
+ let link = content.document.getElementById("testlink");
+ link.href = uri;
+ });
+}
+
+function verifyCSP(aTestName, aBrowser, aDataURI) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aDataURI }],
+ async function({ aTestName, aDataURI }) {
+ let channel = content.docShell.currentDocumentChannel;
+ is(channel.URI.spec, aDataURI, "testing CSP for " + aTestName);
+ let cspJSON = content.document.cspJSON;
+ let cspOBJ = JSON.parse(cspJSON);
+ let policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "should be one policy");
+ let policy = policies[0];
+ is(
+ policy["script-src"],
+ "'unsafe-inline'",
+ "script-src directive matches"
+ );
+ }
+ );
+}
+
+add_task(async function setup() {
+ // allow top level data: URI navigations, otherwise clicking data: link fails
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.data_uri.block_toplevel_data_uri_navigations", false]],
+ });
+});
+
+add_task(async function test_data_csp_inheritance_regular_click() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.browserLoaded(browser, false, DATA_URI);
+ // set the data href + simulate click
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await loadPromise;
+ await verifyCSP("click()", gBrowser.selectedBrowser, DATA_URI);
+ });
+});
+
+add_task(async function test_data_csp_inheritance_ctrl_click() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI, true);
+ // set the data href + simulate ctrl+click
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { ctrlKey: true, metaKey: true },
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP("ctrl-click()", gBrowser.selectedBrowser, DATA_URI);
+ await BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(
+ async function test_data_csp_inheritance_right_click_open_link_in_new_tab() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function(browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ DATA_URI,
+ true
+ );
+ // set the data href + simulate right-click open link in tab
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP(
+ "right-click-open-in-new-tab()",
+ gBrowser.selectedBrowser,
+ DATA_URI
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+ }
+);
diff --git a/docshell/test/browser/browser_fall_back_to_https.js b/docshell/test/browser/browser_fall_back_to_https.js
new file mode 100644
index 0000000000..0d08a18e19
--- /dev/null
+++ b/docshell/test/browser/browser_fall_back_to_https.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * This test is for bug 1002724.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1002724
+ *
+ * When a user enters a host name or IP address in the URL bar, "http" is
+ * assumed. If the host rejects connections on port 80, we try HTTPS as a
+ * fall-back and only fail if HTTPS connection fails.
+ *
+ * This tests that when a user enters "example.com", it attempts to load
+ * http://example.com:80 (not rejected), and when trying secureonly.example.com
+ * (which rejects connections on port 80), it fails then loads
+ * https://secureonly.example.com:443 instead.
+ */
+
+const { UrlbarTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlbarTestUtils.jsm"
+);
+
+const bug1002724_tests = [
+ {
+ original: "example.com",
+ expected: "http://example.com",
+ explanation: "Should load HTTP version of example.com",
+ },
+ {
+ original: "secureonly.example.com",
+ expected: "https://secureonly.example.com",
+ explanation:
+ "Should reject secureonly.example.com on HTTP but load the HTTPS version",
+ },
+];
+
+async function test_one(test_obj) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ gURLBar.focus();
+ gURLBar.value = test_obj.original;
+
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ ok(
+ tab.linkedBrowser.currentURI.spec.startsWith(test_obj.expected),
+ test_obj.explanation
+ );
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_bug1002724() {
+ await SpecialPowers.pushPrefEnv(
+ // Disable HSTS preload just in case.
+ {
+ set: [
+ ["network.stricttransportsecurity.preloadlist", false],
+ ["network.dns.native-is-localhost", true],
+ ],
+ }
+ );
+
+ for (let test of bug1002724_tests) {
+ await test_one(test);
+ }
+});
diff --git a/docshell/test/browser/browser_fission_maxOrigins.js b/docshell/test/browser/browser_fission_maxOrigins.js
new file mode 100644
index 0000000000..c8a0b35193
--- /dev/null
+++ b/docshell/test/browser/browser_fission_maxOrigins.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+SimpleTest.requestFlakyTimeout("Need to test expiration timeout");
+
+function delay(msec) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+function promiseIdle() {
+ return new Promise(resolve => {
+ Services.tm.idleDispatchToMainThread(resolve);
+ });
+}
+
+const ORIGIN_CAP = 5;
+const SLIDING_WINDOW_MS = 5000;
+
+const PREF_ORIGIN_CAP = "fission.experiment.max-origins.origin-cap";
+const PREF_SLIDING_WINDOW_MS =
+ "fission.experiment.max-origins.sliding-window-ms";
+const PREF_QUALIFIED = "fission.experiment.max-origins.qualified";
+const PREF_LAST_QUALIFIED = "fission.experiment.max-origins.last-qualified";
+const PREF_LAST_DISQUALIFIED =
+ "fission.experiment.max-origins.last-disqualified";
+
+const SITE_ORIGINS = [
+ "http://example.com/",
+ "http://example.org/",
+ "http://example.net/",
+ "http://example.tw/",
+ "http://example.cn/",
+ "http://example.fi/",
+ "http://example.in/",
+ "http://example.lk/",
+ "http://w3c-test.org/",
+ "https://www.mozilla.org/",
+];
+
+function openTab(url) {
+ return BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url,
+ waitForStateStop: true,
+ });
+}
+
+async function assertQualified() {
+ // The unique origin calculation runs from an idle task, so make sure
+ // the queued idle task has had a chance to run.
+ await promiseIdle();
+
+ // Make sure the clock has advanced since the qualification timestamp
+ // was recorded.
+ await delay(1);
+
+ let qualified = Services.prefs.getBoolPref(PREF_QUALIFIED);
+ let lastQualified = Services.prefs.getIntPref(PREF_LAST_QUALIFIED);
+ let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
+ let currentTime = Date.now() / 1000;
+
+ ok(qualified, "Should be qualified");
+ ok(
+ lastQualified > 0,
+ `Last qualified timestamp (${lastQualified}) should be greater than 0`
+ );
+ ok(
+ lastQualified < currentTime,
+ `Last qualified timestamp (${lastQualified}) should be less than the current time (${currentTime})`
+ );
+ ok(
+ lastQualified > lastDisqualified,
+ `Last qualified timestamp (${lastQualified}) should be after the last disqualified time (${lastDisqualified})`
+ );
+
+ ok(
+ lastDisqualified < currentTime,
+ `Last disqualified timestamp (${lastDisqualified}) should be less than the current time (${currentTime})`
+ );
+}
+
+async function assertDisqualified(opts) {
+ // The unique origin calculation runs from an idle task, so make sure
+ // the queued idle task has had a chance to run.
+ await promiseIdle();
+
+ let qualified = Services.prefs.getBoolPref(PREF_QUALIFIED);
+ let lastQualified = Services.prefs.getIntPref(PREF_LAST_QUALIFIED, 0);
+ let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
+ let currentTime = Date.now() / 1000;
+
+ ok(!qualified, "Should not be qualified");
+ if (!opts.initialValues) {
+ ok(
+ lastQualified > 0,
+ `Last qualified timestamp (${lastQualified}) should be greater than 0`
+ );
+ }
+ ok(
+ lastQualified < currentTime,
+ `Last qualified timestamp (${lastQualified}) should be less than the current time (${currentTime})`
+ );
+
+ ok(
+ lastDisqualified < currentTime,
+ `Last disqualified timestamp (${lastDisqualified}) should be less than the current time (${currentTime})`
+ );
+
+ ok(
+ lastDisqualified > 0,
+ `Last disqualified timestamp (${lastDisqualified}) should be greater than 0`
+ );
+
+ if (opts.qualificationPending) {
+ ok(
+ lastQualified > lastDisqualified,
+ `Last qualified timestamp (${lastQualified}) should be after the last disqualified time (${lastDisqualified})`
+ );
+ } else {
+ ok(
+ lastDisqualified > lastQualified,
+ `Last disqualified timestamp (${lastDisqualified}) should be after the last qualified time (${lastQualified})`
+ );
+ }
+}
+
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_ORIGIN_CAP, ORIGIN_CAP],
+ [PREF_SLIDING_WINDOW_MS, SLIDING_WINDOW_MS],
+ ],
+ });
+
+ // Make sure we actually record telemetry for our disqualifying origin
+ // count.
+ BrowserUtils.min_interval = 1;
+
+ let tabs = [];
+
+ // Open one initial tab to make sure the origin counting code has had
+ // a chance to run before checking the initial state.
+ tabs.push(await openTab("http://mochi.test:8888/"));
+
+ await assertQualified();
+
+ let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
+ is(lastDisqualified, 0, "Last disqualification timestamp should be 0");
+
+ info(
+ `Opening ${SITE_ORIGINS.length} tabs with distinct origins to exceed the cap (${ORIGIN_CAP})`
+ );
+ ok(
+ SITE_ORIGINS.length > ORIGIN_CAP,
+ "Should have enough site origins to exceed the origin cap"
+ );
+ tabs.push(...(await Promise.all(SITE_ORIGINS.map(openTab))));
+
+ await assertDisqualified({ qualificationPending: false });
+
+ info("Close unique-origin tabs");
+ await Promise.all(tabs.map(tab => BrowserTestUtils.removeTab(tab)));
+
+ info("Open a new tab to trigger the origin count code once more");
+ tabs = [await openTab(SITE_ORIGINS[0])];
+
+ await assertDisqualified({ qualificationPending: true });
+
+ info(
+ "Wait long enough to clear the sliding window since last disqualified state"
+ );
+ await delay(SLIDING_WINDOW_MS + 1000);
+
+ info("Open a new tab to trigger the origin count code again");
+ tabs.push(await openTab(SITE_ORIGINS[0]));
+
+ await assertQualified();
+
+ info(
+ "Clear preference values and re-populate the initial value from telemetry"
+ );
+ Services.prefs.clearUserPref(PREF_QUALIFIED);
+ Services.prefs.clearUserPref(PREF_LAST_QUALIFIED);
+ Services.prefs.clearUserPref(PREF_LAST_DISQUALIFIED);
+ BrowserUtils._checkedInitialExperimentQualification = false;
+
+ info("Open a new tab to trigger the origin count code again");
+ tabs.push(await openTab(SITE_ORIGINS[0]));
+
+ await assertDisqualified({ initialValues: true });
+
+ info(
+ "Wait long enough to clear the sliding window since last disqualified state"
+ );
+ await delay(SLIDING_WINDOW_MS + 1000);
+
+ info("Open a new tab to trigger the origin count code again");
+ tabs.push(await openTab(SITE_ORIGINS[0]));
+
+ await assertQualified();
+
+ await Promise.all(tabs.map(tab => BrowserTestUtils.removeTab(tab)));
+
+ // Clear the cached recording interval so it resets to the default
+ // value on the next call.
+ BrowserUtils.min_interval = null;
+});
diff --git a/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js
new file mode 100644
index 0000000000..1bed399b3b
--- /dev/null
+++ b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const HTML_URI = TEST_PATH + "dummy_page.html";
+const VIEW_SRC_URI = "view-source:" + HTML_URI;
+
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+
+ info("load baseline html in new tab");
+ await BrowserTestUtils.withNewTab(HTML_URI, async function(aBrowser) {
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ HTML_URI,
+ "sanity check to make sure html loaded"
+ );
+
+ info("right-click -> view-source of html");
+ let vSrcCtxtMenu = document.getElementById("contentAreaContextMenu");
+ let popupPromise = BrowserTestUtils.waitForEvent(
+ vSrcCtxtMenu,
+ "popupshown"
+ );
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ { type: "contextmenu", button: 2 },
+ aBrowser
+ );
+ await popupPromise;
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, VIEW_SRC_URI);
+ let vSrcItem = vSrcCtxtMenu.getElementsByAttribute(
+ "id",
+ "context-viewsource"
+ )[0];
+ vSrcItem.click();
+ vSrcCtxtMenu.hidePopup();
+ let tab = await tabPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ VIEW_SRC_URI,
+ "loading view-source of html succeeded"
+ );
+
+ info("load html file again before going .back()");
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ HTML_URI
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [HTML_URI], HTML_URI => {
+ content.document.location = HTML_URI;
+ });
+ await loadPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ HTML_URI,
+ "loading html another time succeeded"
+ );
+
+ info(
+ "click .back() to view-source of html again and make sure the history entry has a triggeringPrincipal"
+ );
+ let backCtxtMenu = document.getElementById("contentAreaContextMenu");
+ popupPromise = BrowserTestUtils.waitForEvent(backCtxtMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ { type: "contextmenu", button: 2 },
+ aBrowser
+ );
+ await popupPromise;
+ loadPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ let backItem = backCtxtMenu.getElementsByAttribute("id", "context-back")[0];
+ backItem.click();
+ backCtxtMenu.hidePopup();
+ await loadPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ VIEW_SRC_URI,
+ "clicking .back() to view-source of html succeeded"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/docshell/test/browser/browser_loadURI_postdata.js b/docshell/test/browser/browser_loadURI_postdata.js
new file mode 100644
index 0000000000..616fbd9d8e
--- /dev/null
+++ b/docshell/test/browser/browser_loadURI_postdata.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const gPostData = "postdata=true";
+const gUrl =
+ "http://mochi.test:8888/browser/docshell/test/browser/print_postdata.sjs";
+
+add_task(async function test_loadURI_persists_postData() {
+ waitForExplicitFinish();
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ registerCleanupFunction(function() {
+ gBrowser.removeTab(tab);
+ });
+
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ dataStream.data = gPostData;
+
+ var postStream = Cc[
+ "@mozilla.org/network/mime-input-stream;1"
+ ].createInstance(Ci.nsIMIMEInputStream);
+ postStream.addHeader("Content-Type", "application/x-www-form-urlencoded");
+ postStream.setData(dataStream);
+ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(
+ Ci.nsIPrincipal
+ );
+
+ tab.linkedBrowser.loadURI(gUrl, {
+ triggeringPrincipal: systemPrincipal,
+ postData: postStream,
+ });
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, gUrl);
+ let body = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => content.document.body.textContent
+ );
+ is(body, gPostData, "post data was submitted correctly");
+ finish();
+});
diff --git a/docshell/test/browser/browser_multiple_pushState.js b/docshell/test/browser/browser_multiple_pushState.js
new file mode 100644
index 0000000000..92587b6567
--- /dev/null
+++ b/docshell/test/browser/browser_multiple_pushState.js
@@ -0,0 +1,23 @@
+add_task(async function test_multiple_pushState() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url:
+ "http://example.org/browser/docshell/test/browser/file_multiple_pushState.html",
+ },
+ async function(browser) {
+ const kExpected = "http://example.org/bar/ABC/DEF?key=baz";
+
+ let contentLocation = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function() {
+ return content.document.location.href;
+ }
+ );
+
+ is(contentLocation, kExpected);
+ is(browser.documentURI.spec, kExpected);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_onbeforeunload.js b/docshell/test/browser/browser_onbeforeunload.js
new file mode 100644
index 0000000000..014f0537c6
--- /dev/null
+++ b/docshell/test/browser/browser_onbeforeunload.js
@@ -0,0 +1,326 @@
+"use strict";
+
+// We need to test a lot of permutations here, and there isn't any sensible way
+// to split them up or run them faster.
+requestLongerTimeout(4);
+
+const BASE_URL = "http://mochi.test:8888/browser/docshell/test/browser/";
+
+const TEST_PAGE = BASE_URL + "file_onbeforeunload_0.html";
+
+const DIALOG_TOPIC = "tabmodal-dialog-loaded";
+
+async function withTabModalPromptCount(expected, task) {
+ let count = 0;
+ function observer() {
+ count++;
+ }
+
+ Services.obs.addObserver(observer, DIALOG_TOPIC);
+ try {
+ return await task();
+ } finally {
+ Services.obs.removeObserver(observer, DIALOG_TOPIC);
+ is(count, expected, "Should see expected number of tab modal prompts");
+ }
+}
+
+function promiseAllowUnloadPrompt(allowNavigation) {
+ return BrowserUtils.promiseObserved(DIALOG_TOPIC).then(
+ ({ subject: node }) => {
+ let button = node.querySelector(
+ `.tabmodalprompt-button${allowNavigation ? 0 : 1}`
+ );
+ button.click();
+ }
+ );
+}
+
+// Maintain a pool of background tabs with our test document loaded so
+// we don't have to wait for a load prior to each test step (potentially
+// tearing down and recreating content processes in the process).
+const TabPool = {
+ poolSize: 5,
+
+ pendingCount: 0,
+
+ readyTabs: [],
+
+ readyPromise: null,
+ resolveReadyPromise: null,
+
+ spawnTabs() {
+ while (this.pendingCount + this.readyTabs.length < this.poolSize) {
+ this.pendingCount++;
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ this.readyTabs.push(tab);
+ this.pendingCount--;
+
+ if (this.resolveReadyPromise) {
+ this.readyPromise = null;
+ this.resolveReadyPromise();
+ this.resolveReadyPromise = null;
+ }
+
+ this.spawnTabs();
+ });
+ }
+ },
+
+ getReadyPromise() {
+ if (!this.readyPromise) {
+ this.readyPromise = new Promise(resolve => {
+ this.resolveReadyPromise = resolve;
+ });
+ }
+ return this.readyPromise;
+ },
+
+ async getTab() {
+ while (!this.readyTabs.length) {
+ this.spawnTabs();
+ await this.getReadyPromise();
+ }
+
+ let tab = this.readyTabs.shift();
+ this.spawnTabs();
+
+ gBrowser.selectedTab = tab;
+ return tab;
+ },
+
+ async cleanup() {
+ this.poolSize = 0;
+
+ while (this.pendingCount) {
+ await this.getReadyPromise();
+ }
+
+ while (this.readyTabs.length) {
+ await BrowserTestUtils.removeTab(this.readyTabs.shift());
+ }
+ },
+};
+
+const ACTIONS = {
+ NONE: 0,
+ LISTEN_AND_ALLOW: 1,
+ LISTEN_AND_BLOCK: 2,
+};
+
+const ACTION_NAMES = new Map(Object.entries(ACTIONS).map(([k, v]) => [v, k]));
+
+function* generatePermutations(depth) {
+ if (depth == 0) {
+ yield [];
+ return;
+ }
+ for (let subActions of generatePermutations(depth - 1)) {
+ for (let action of Object.values(ACTIONS)) {
+ yield [action, ...subActions];
+ }
+ }
+}
+
+const PERMUTATIONS = Array.from(generatePermutations(4));
+
+const FRAMES = [
+ { process: 0 },
+ { process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
+ { process: 0 },
+ { process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
+];
+
+function addListener(bc, block) {
+ return SpecialPowers.spawn(bc, [block], block => {
+ return new Promise(resolve => {
+ function onbeforeunload(event) {
+ if (block) {
+ event.preventDefault();
+ }
+ resolve({ event: "beforeunload" });
+ }
+ content.addEventListener("beforeunload", onbeforeunload, { once: true });
+ content.unlisten = () => {
+ content.removeEventListener("beforeunload", onbeforeunload);
+ };
+
+ content.addEventListener(
+ "unload",
+ () => {
+ resolve({ event: "unload" });
+ },
+ { once: true }
+ );
+ });
+ });
+}
+
+function descendants(bc) {
+ if (bc) {
+ return [bc, ...descendants(bc.children[0])];
+ }
+ return [];
+}
+
+async function addListeners(frames, actions, startIdx) {
+ let process = startIdx >= 0 ? FRAMES[startIdx].process : -1;
+
+ let roundTripPromises = [];
+
+ let expectNestedEventLoop = false;
+ let numBlockers = 0;
+ let unloadPromises = [];
+ let beforeUnloadPromises = [];
+
+ for (let [i, frame] of frames.entries()) {
+ let action = actions[i];
+ if (action === ACTIONS.NONE) {
+ continue;
+ }
+
+ let block = action === ACTIONS.LISTEN_AND_BLOCK;
+ let promise = addListener(frame, block);
+ if (startIdx <= i) {
+ if (block || FRAMES[i].process !== process) {
+ expectNestedEventLoop = true;
+ }
+ beforeUnloadPromises.push(promise);
+ numBlockers += block;
+ } else {
+ unloadPromises.push(promise);
+ }
+
+ roundTripPromises.push(SpecialPowers.spawn(frame, [], () => {}));
+ }
+
+ // Wait for round trip messages to any processes with event listeners to
+ // return so we're sure that all listeners are registered and their state
+ // flags are propagated before we continue.
+ await Promise.all(roundTripPromises);
+
+ return {
+ expectNestedEventLoop,
+ expectPrompt: !!numBlockers,
+ unloadPromises,
+ beforeUnloadPromises,
+ };
+}
+
+async function doTest(actions, startIdx, navigate) {
+ let tab = await TabPool.getTab();
+ let browser = tab.linkedBrowser;
+
+ let frames = descendants(browser.browsingContext);
+ let expected = await addListeners(frames, actions, startIdx);
+
+ let awaitingPrompt = false;
+ let promptPromise;
+ if (expected.expectPrompt) {
+ awaitingPrompt = true;
+ promptPromise = promiseAllowUnloadPrompt(false).then(() => {
+ awaitingPrompt = false;
+ });
+ }
+
+ let promptCount = expected.expectPrompt ? 1 : 0;
+ await withTabModalPromptCount(promptCount, async () => {
+ await navigate(tab, frames).then(result => {
+ ok(
+ !awaitingPrompt,
+ "Navigation should not complete while we're still expecting a prompt"
+ );
+
+ is(
+ result.eventLoopSpun,
+ expected.expectNestedEventLoop,
+ "Should have nested event loop?"
+ );
+ });
+
+ for (let result of await Promise.all(expected.beforeUnloadPromises)) {
+ is(
+ result.event,
+ "beforeunload",
+ "Should have seen beforeunload event before unload"
+ );
+ }
+ await promptPromise;
+
+ await Promise.all(
+ frames.map(frame =>
+ SpecialPowers.spawn(frame, [], () => {
+ if (content.unlisten) {
+ content.unlisten();
+ }
+ }).catch(() => {})
+ )
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+ });
+
+ for (let result of await Promise.all(expected.unloadPromises)) {
+ is(result.event, "unload", "Should have seen unload event");
+ }
+}
+
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+
+ for (let actions of PERMUTATIONS) {
+ info(
+ `Testing frame actions: [${actions.map(action =>
+ ACTION_NAMES.get(action)
+ )}]`
+ );
+
+ for (let startIdx = 0; startIdx < FRAMES.length; startIdx++) {
+ info(`Testing content reload from frame ${startIdx}`);
+
+ await doTest(actions, startIdx, (tab, frames) => {
+ return SpecialPowers.spawn(frames[startIdx], [], () => {
+ let eventLoopSpun = false;
+ SpecialPowers.Services.tm.dispatchToMainThread(() => {
+ eventLoopSpun = true;
+ });
+
+ content.location.reload();
+
+ return { eventLoopSpun };
+ });
+ });
+ }
+
+ info(`Testing tab close from parent process`);
+ await doTest(actions, -1, (tab, frames) => {
+ let eventLoopSpun = false;
+ Services.tm.dispatchToMainThread(() => {
+ eventLoopSpun = true;
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ let result = { eventLoopSpun };
+
+ // Make an extra couple of trips through the event loop to give us time
+ // to process SpecialPowers.spawn responses before resolving.
+ return new Promise(resolve => {
+ executeSoon(() => {
+ executeSoon(() => resolve(result));
+ });
+ });
+ });
+ }
+});
+
+add_task(async function cleanup() {
+ await TabPool.cleanup();
+});
diff --git a/docshell/test/browser/browser_onbeforeunload_navigation.js b/docshell/test/browser/browser_onbeforeunload_navigation.js
new file mode 100644
index 0000000000..d09a751e90
--- /dev/null
+++ b/docshell/test/browser/browser_onbeforeunload_navigation.js
@@ -0,0 +1,174 @@
+"use strict";
+
+const TEST_PAGE =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1046022.html";
+const TARGETED_PAGE =
+ "data:text/html," +
+ encodeURIComponent("<body>Shouldn't be seeing this</body>");
+
+var loadStarted = false;
+var tabStateListener = {
+ resolveLoad: null,
+ expectLoad: null,
+
+ onStateChange(webprogress, request, flags, status) {
+ const WPL = Ci.nsIWebProgressListener;
+ if (flags & WPL.STATE_IS_WINDOW) {
+ if (flags & WPL.STATE_START) {
+ loadStarted = true;
+ } else if (flags & WPL.STATE_STOP) {
+ let url = request.QueryInterface(Ci.nsIChannel).URI.spec;
+ is(url, this.expectLoad, "Should only see expected document loads");
+ if (url == this.expectLoad) {
+ this.resolveLoad();
+ }
+ }
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function promiseLoaded(url, callback) {
+ if (tabStateListener.expectLoad) {
+ throw new Error("Can't wait for multiple loads at once");
+ }
+ tabStateListener.expectLoad = url;
+ return new Promise(resolve => {
+ tabStateListener.resolveLoad = resolve;
+ if (callback) {
+ callback();
+ }
+ }).then(() => {
+ tabStateListener.expectLoad = null;
+ tabStateListener.resolveLoad = null;
+ });
+}
+
+async function promiseStayOnPagePrompt(acceptNavigation) {
+ loadStarted = false;
+ let [dialog] = await TestUtils.topicObserved("tabmodal-dialog-loaded");
+
+ ok(!loadStarted, "No load should be started");
+
+ let button = dialog.querySelector(
+ acceptNavigation ? ".tabmodalprompt-button0" : ".tabmodalprompt-button1"
+ );
+ button.click();
+
+ // Make a trip through the event loop so that, if anything is going to
+ // happen after we deny the navigation, it has a chance to happen
+ // before we return to our caller.
+ await new Promise(executeSoon);
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE,
+ false,
+ true
+ );
+ let browser = testTab.linkedBrowser;
+ browser.addProgressListener(
+ tabStateListener,
+ Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
+ );
+
+ const NUM_TESTS = 7;
+ await SpecialPowers.spawn(browser, [NUM_TESTS], testCount => {
+ let { testFns } = this.content.wrappedJSObject;
+ Assert.equal(
+ testFns.length,
+ testCount,
+ "Should have the correct number of test functions"
+ );
+ });
+
+ for (let allowNavigation of [false, true]) {
+ for (let i = 0; i < NUM_TESTS; i++) {
+ info(
+ `Running test ${i} with navigation ${
+ allowNavigation ? "allowed" : "forbidden"
+ }`
+ );
+
+ if (allowNavigation) {
+ // If we're allowing navigations, we need to re-load the test
+ // page after each test, since the tests will each navigate away
+ // from it.
+ await promiseLoaded(TEST_PAGE, () => {
+ browser.loadURI(TEST_PAGE, {
+ triggeringPrincipal: document.nodePrincipal,
+ });
+ });
+ }
+
+ let promptPromise = promiseStayOnPagePrompt(allowNavigation);
+ let loadPromise;
+ if (allowNavigation) {
+ loadPromise = promiseLoaded(TARGETED_PAGE);
+ }
+
+ let winID = await SpecialPowers.spawn(
+ browser,
+ [i, TARGETED_PAGE],
+ (testIdx, url) => {
+ let { testFns } = this.content.wrappedJSObject;
+ this.content.onbeforeunload = testFns[testIdx];
+ this.content.location = url;
+ return this.content.windowGlobalChild.innerWindowId;
+ }
+ );
+
+ await promptPromise;
+ await loadPromise;
+
+ if (allowNavigation) {
+ await SpecialPowers.spawn(
+ browser,
+ [TARGETED_PAGE, winID],
+ (url, winID) => {
+ this.content.onbeforeunload = null;
+ Assert.equal(
+ this.content.location.href,
+ url,
+ "Page should have navigated to the correct URL"
+ );
+ Assert.notEqual(
+ this.content.windowGlobalChild.innerWindowId,
+ winID,
+ "Page should have a new inner window"
+ );
+ }
+ );
+ } else {
+ await SpecialPowers.spawn(browser, [TEST_PAGE, winID], (url, winID) => {
+ this.content.onbeforeunload = null;
+ Assert.equal(
+ this.content.location.href,
+ url,
+ "Page should have the same URL"
+ );
+ Assert.equal(
+ this.content.windowGlobalChild.innerWindowId,
+ winID,
+ "Page should have the same inner window"
+ );
+ });
+ }
+ }
+ }
+
+ gBrowser.removeTab(testTab);
+});
diff --git a/docshell/test/browser/browser_onunload_stop.js b/docshell/test/browser/browser_onunload_stop.js
new file mode 100644
index 0000000000..d21966d269
--- /dev/null
+++ b/docshell/test/browser/browser_onunload_stop.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_1 =
+ "http://mochi.test:8888/browser/docshell/test/browser/dummy_page.html";
+
+const TEST_PAGE_2 =
+ "http://example.com/browser/docshell/test/browser/dummy_page.html";
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(TEST_PAGE_1, async function(browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_PAGE_2);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.addEventListener("unload", e => e.currentTarget.stop(), true);
+ });
+ BrowserTestUtils.loadURI(browser, TEST_PAGE_2);
+ await loaded;
+ ok(true, "Page loaded successfully");
+ });
+});
diff --git a/docshell/test/browser/browser_overlink.js b/docshell/test/browser/browser_overlink.js
new file mode 100644
index 0000000000..64973985ad
--- /dev/null
+++ b/docshell/test/browser/browser_overlink.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+add_task(async function test_stripAuthCredentials() {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "overlink_test.html",
+ async function(browser) {
+ await SpecialPowers.spawn(browser, [], function() {
+ content.document.getElementById("link").focus();
+ });
+
+ await TestUtils.waitForCondition(
+ () => XULBrowserWindow.overLink == "https://example.com",
+ "Overlink should be missing auth credentials"
+ );
+
+ ok(true, "Test successful");
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_platform_emulation.js b/docshell/test/browser/browser_platform_emulation.js
new file mode 100644
index 0000000000..3a9d3abe94
--- /dev/null
+++ b/docshell/test/browser/browser_platform_emulation.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+async function contentTaskNoOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customPlatform,
+ "",
+ "There should initially be no customPlatform"
+ );
+}
+
+async function contentTaskOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customPlatform,
+ "foo",
+ "The platform should be changed to foo"
+ );
+
+ is(
+ content.navigator.platform,
+ "foo",
+ "The platform should be changed to foo"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ is(
+ frameWin.navigator.platform,
+ "foo",
+ "The platform should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ is(
+ newFrameWin.navigator.platform,
+ "foo",
+ "Newly created frames should use the new platform"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+
+ is(
+ newFrameWin.navigator.platform,
+ "foo",
+ "New platform should persist across reloads"
+ );
+}
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function(
+ browser
+ ) {
+ await SpecialPowers.spawn(browser, [], contentTaskNoOverride);
+
+ let browsingContext = BrowserTestUtils.getBrowsingContextFrom(browser);
+ browsingContext.customPlatform = "foo";
+
+ await SpecialPowers.spawn(browser, [], contentTaskOverride);
+ });
+});
diff --git a/docshell/test/browser/browser_search_notification.js b/docshell/test/browser/browser_search_notification.js
new file mode 100644
index 0000000000..abb9330467
--- /dev/null
+++ b/docshell/test/browser/browser_search_notification.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function() {
+ // Our search would be handled by the urlbar normally and not by the docshell,
+ // thus we must force going through dns first, so that the urlbar thinks
+ // the value may be a url, and asks the docshell to visit it.
+ // On NS_ERROR_UNKNOWN_HOST the docshell will fix it up.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.fixup.dns_first_for_single_words", true]],
+ });
+ const kSearchEngineID = "test_urifixup_search_engine";
+ const kSearchEngineURL = "http://localhost/?search={searchTerms}";
+ await Services.search.addEngineWithDetails(kSearchEngineID, {
+ method: "get",
+ template: kSearchEngineURL,
+ });
+
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID)
+ );
+
+ let selectedName = (await Services.search.getDefault()).name;
+ Assert.equal(
+ selectedName,
+ kSearchEngineID,
+ "Check fake search engine is selected"
+ );
+
+ registerCleanupFunction(async function() {
+ if (oldDefaultEngine) {
+ await Services.search.setDefault(oldDefaultEngine);
+ }
+ let engine = Services.search.getEngineByName(kSearchEngineID);
+ if (engine) {
+ await Services.search.removeEngine(engine);
+ }
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gBrowser.selectedTab = tab;
+
+ gURLBar.value = "firefox";
+ gURLBar.handleCommand();
+
+ let [subject, data] = await TestUtils.topicObserved("keyword-search");
+
+ let engine = Services.search.defaultEngine;
+ Assert.ok(engine, "Have default search engine.");
+ Assert.equal(engine, subject, "Notification subject is engine.");
+ Assert.equal(data, "firefox", "Notification data is search term.");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_tab_replace_while_loading.js b/docshell/test/browser/browser_tab_replace_while_loading.js
new file mode 100644
index 0000000000..e1b88334ff
--- /dev/null
+++ b/docshell/test/browser/browser_tab_replace_while_loading.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* Test for bug 1578379. */
+
+add_task(async function test_window_open_about_blank() {
+ const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_open_about_blank.html";
+ let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Opening about:blank using a click");
+ await SpecialPowers.spawn(firstTab.linkedBrowser, [""], async function() {
+ content.document.querySelector("#open").click();
+ });
+
+ info("Waiting for the second tab to be opened");
+ let secondTab = await promiseTabOpened;
+
+ info("Detaching tab");
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(secondTab);
+ let win = await windowOpenedPromise;
+
+ info("Asserting document is visible");
+ let tab = win.gBrowser.selectedTab;
+ await SpecialPowers.spawn(tab.linkedBrowser, [""], async function() {
+ is(
+ content.document.visibilityState,
+ "visible",
+ "Document should be visible"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.removeTab(firstTab);
+});
+
+add_task(async function test_detach_loading_page() {
+ const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_slow_load.sjs";
+ // Open a dummy tab so that detaching the second tab works.
+ let dummyTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let slowLoadingTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ URL,
+ /* waitForLoad = */ false
+ );
+
+ info("Wait for content document to be created");
+ await BrowserTestUtils.waitForCondition(async function() {
+ return SpecialPowers.spawn(
+ slowLoadingTab.linkedBrowser,
+ [URL],
+ async function(url) {
+ return content.document.documentURI == url;
+ }
+ );
+ });
+
+ info("Detaching tab");
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(slowLoadingTab);
+ let win = await windowOpenedPromise;
+
+ info("Asserting document is visible");
+ let tab = win.gBrowser.selectedTab;
+ await SpecialPowers.spawn(tab.linkedBrowser, [""], async function() {
+ is(content.document.readyState, "loading");
+ is(content.document.visibilityState, "visible");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.removeTab(dummyTab);
+});
diff --git a/docshell/test/browser/browser_tab_touch_events.js b/docshell/test/browser/browser_tab_touch_events.js
new file mode 100644
index 0000000000..3b3d1ee51d
--- /dev/null
+++ b/docshell/test/browser/browser_tab_touch_events.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function() {
+ const URI = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+ await BrowserTestUtils.withNewTab({ gBrowser, url: URI }, async function(
+ browser
+ ) {
+ await SpecialPowers.spawn(browser, [], test_body);
+ });
+});
+
+async function test_body() {
+ let bc = content.browsingContext;
+
+ is(
+ bc.touchEventsOverride,
+ "none",
+ "touchEventsOverride flag should be initially set to NONE"
+ );
+
+ bc.touchEventsOverride = "disabled";
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "touchEventsOverride flag should be changed to DISABLED"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ bc = frameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "touchEventsOverride flag should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "Newly created frames should use the new touchEventsOverride flag"
+ );
+
+ // Wait for the non-transient about:blank to load.
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+ newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "Newly created frames should use the new touchEventsOverride flag"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+ newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "New touchEventsOverride flag should persist across reloads"
+ );
+}
diff --git a/docshell/test/browser/browser_timelineMarkers-01.js b/docshell/test/browser/browser_timelineMarkers-01.js
new file mode 100644
index 0000000000..3109b6d427
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-01.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the docShell has the right profile timeline API
+
+const URL = "data:text/html;charset=utf-8,Test page";
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function(
+ browser
+ ) {
+ await SpecialPowers.spawn(browser, [], function() {
+ ok(
+ "recordProfileTimelineMarkers" in docShell,
+ "The recordProfileTimelineMarkers attribute exists"
+ );
+ ok(
+ "popProfileTimelineMarkers" in docShell,
+ "The popProfileTimelineMarkers function exists"
+ );
+ ok(
+ docShell.recordProfileTimelineMarkers === false,
+ "recordProfileTimelineMarkers is false by default"
+ );
+ ok(
+ docShell.popProfileTimelineMarkers().length === 0,
+ "There are no markers by default"
+ );
+
+ docShell.recordProfileTimelineMarkers = true;
+ ok(
+ docShell.recordProfileTimelineMarkers === true,
+ "recordProfileTimelineMarkers can be set to true"
+ );
+
+ docShell.recordProfileTimelineMarkers = false;
+ ok(
+ docShell.recordProfileTimelineMarkers === false,
+ "recordProfileTimelineMarkers can be set to false"
+ );
+ });
+ });
+});
diff --git a/docshell/test/browser/browser_timelineMarkers-02.js b/docshell/test/browser/browser_timelineMarkers-02.js
new file mode 100644
index 0000000000..a2b569d9d6
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-02.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var TEST_URL =
+ "<!DOCTYPE html><style>" +
+ "body {margin:0; padding: 0;} " +
+ "div {width:100px;height:100px;background:red;} " +
+ ".resize-change-color {width:50px;height:50px;background:blue;} " +
+ ".change-color {width:50px;height:50px;background:yellow;} " +
+ ".add-class {}" +
+ "</style><div></div>";
+TEST_URL = "data:text/html;charset=utf8," + encodeURIComponent(TEST_URL);
+
+var test = makeTimelineTest("browser_timelineMarkers-frame-02.js", TEST_URL);
diff --git a/docshell/test/browser/browser_timelineMarkers-03.js b/docshell/test/browser/browser_timelineMarkers-03.js
new file mode 100644
index 0000000000..b104367c10
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-03.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var URL = "data:text/html;charset=utf-8,<p>Test page</p>";
+
+var test = makeTimelineTest("browser_timelineMarkers-frame-03.js", URL);
diff --git a/docshell/test/browser/browser_timelineMarkers-04.js b/docshell/test/browser/browser_timelineMarkers-04.js
new file mode 100644
index 0000000000..3630b0683f
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-04.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/timelineMarkers-04.html";
+
+var test = makeTimelineTest("browser_timelineMarkers-frame-04.js", URL);
diff --git a/docshell/test/browser/browser_timelineMarkers-05.js b/docshell/test/browser/browser_timelineMarkers-05.js
new file mode 100644
index 0000000000..391ce54a92
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-05.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var TEST_URL =
+ "<!DOCTYPE html><style>" +
+ "body {margin:0; padding: 0;} " +
+ "div {width:100px;height:100px;background:red;} " +
+ ".resize-change-color {width:50px;height:50px;background:blue;} " +
+ ".change-color {width:50px;height:50px;background:yellow;} " +
+ ".add-class {}" +
+ "</style><div></div>";
+TEST_URL = "data:text/html;charset=utf8," + encodeURIComponent(TEST_URL);
+
+var test = makeTimelineTest("browser_timelineMarkers-frame-05.js", TEST_URL);
diff --git a/docshell/test/browser/browser_timelineMarkers-frame-02.js b/docshell/test/browser/browser_timelineMarkers-frame-02.js
new file mode 100644
index 0000000000..b4b3efe49f
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-02.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file expects frame-head.js to be loaded in the environment.
+/* import-globals-from frame-head.js */
+
+"use strict";
+
+// Test that the docShell profile timeline API returns the right markers when
+// restyles, reflows and paints occur
+
+function rectangleContains(rect, x, y, width, height) {
+ return (
+ rect.x <= x && rect.y <= y && rect.width >= width && rect.height >= height
+ );
+}
+
+function sanitizeMarkers(list) {
+ // These markers are currently gathered from all docshells, which may
+ // interfere with this test.
+ return list.filter(e => e.name != "Worker" && e.name != "MinorGC");
+}
+
+var TESTS = [
+ {
+ desc: "Changing the width of the test element",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "resize-change-color");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(markers.length > 0, "markers were returned");
+ console.log(markers);
+ info(JSON.stringify(markers.filter(m => m.name == "Paint")));
+ ok(
+ markers.some(m => m.name == "Reflow"),
+ "markers includes Reflow"
+ );
+ ok(
+ markers.some(m => m.name == "Paint"),
+ "markers includes Paint"
+ );
+ for (let marker of markers.filter(m => m.name == "Paint")) {
+ // This change should generate at least one rectangle.
+ ok(marker.rectangles.length >= 1, "marker has one rectangle");
+ // One of the rectangles should contain the div.
+ ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 100, 100)));
+ }
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "Changing the test element's background color",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "change-color");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(markers.length > 0, "markers were returned");
+ ok(
+ !markers.some(m => m.name == "Reflow"),
+ "markers doesn't include Reflow"
+ );
+ ok(
+ markers.some(m => m.name == "Paint"),
+ "markers includes Paint"
+ );
+ for (let marker of markers.filter(m => m.name == "Paint")) {
+ // This change should generate at least one rectangle.
+ ok(marker.rectangles.length >= 1, "marker has one rectangle");
+ // One of the rectangles should contain the div.
+ ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 50, 50)));
+ }
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "Changing the test element's classname",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "change-color add-class");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(markers.length > 0, "markers were returned");
+ ok(
+ !markers.some(m => m.name == "Reflow"),
+ "markers doesn't include Reflow"
+ );
+ ok(
+ !markers.some(m => m.name == "Paint"),
+ "markers doesn't include Paint"
+ );
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "sync console.time/timeEnd",
+ searchFor: "ConsoleTime",
+ setup(docShell) {
+ content.console.time("FOOBAR");
+ content.console.timeEnd("FOOBAR");
+ let markers = docShell.popProfileTimelineMarkers();
+ is(markers.length, 1, "Got one marker");
+ is(markers[0].name, "ConsoleTime", "Got ConsoleTime marker");
+ is(markers[0].causeName, "FOOBAR", "Got ConsoleTime FOOBAR detail");
+ content.console.time("FOO");
+ content.setTimeout(() => {
+ content.console.time("BAR");
+ content.setTimeout(() => {
+ content.console.timeEnd("FOO");
+ content.console.timeEnd("BAR");
+ }, 100);
+ }, 100);
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ is(markers.length, 2, "Got 2 markers");
+ is(markers[0].name, "ConsoleTime", "Got first ConsoleTime marker");
+ is(markers[0].causeName, "FOO", "Got ConsoleTime FOO detail");
+ is(markers[1].name, "ConsoleTime", "Got second ConsoleTime marker");
+ is(markers[1].causeName, "BAR", "Got ConsoleTime BAR detail");
+ },
+ },
+ {
+ desc: "Timestamps created by console.timeStamp()",
+ searchFor: "Timestamp",
+ setup(docShell) {
+ content.console.timeStamp("rock");
+ let markers = docShell.popProfileTimelineMarkers();
+ is(markers.length, 1, "Got one marker");
+ is(markers[0].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[0].causeName, "rock", "Got Timestamp label value");
+ content.console.timeStamp("paper");
+ content.console.timeStamp("scissors");
+ content.console.timeStamp();
+ content.console.timeStamp(undefined);
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ is(markers.length, 4, "Got 4 markers");
+ is(markers[0].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[0].causeName, "paper", "Got Timestamp label value");
+ is(markers[1].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[1].causeName, "scissors", "Got Timestamp label value");
+ is(
+ markers[2].name,
+ "TimeStamp",
+ "Got empty Timestamp marker when no argument given"
+ );
+ is(markers[2].causeName, void 0, "Got empty Timestamp label value");
+ is(
+ markers[3].name,
+ "TimeStamp",
+ "Got empty Timestamp marker when argument is undefined"
+ );
+ is(markers[3].causeName, void 0, "Got empty Timestamp label value");
+ markers.forEach(m =>
+ is(
+ m.end,
+ m.start,
+ "All Timestamp markers should have identical start/end times"
+ )
+ );
+ },
+ },
+];
+
+timelineContentTest(TESTS);
diff --git a/docshell/test/browser/browser_timelineMarkers-frame-03.js b/docshell/test/browser/browser_timelineMarkers-frame-03.js
new file mode 100644
index 0000000000..2368b36063
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-03.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file expects frame-head.js to be loaded in the environment.
+/* import-globals-from frame-head.js */
+
+"use strict";
+
+// Test that the docShell profile timeline API returns the right
+// markers for DOM events.
+
+var TESTS = [
+ {
+ desc: "Event dispatch with single handler",
+ searchFor: "DOMEvent",
+ setup(docShell) {
+ content.document.body.addEventListener(
+ "dog",
+ function(e) {
+ console.log("hi");
+ },
+ true
+ );
+ content.document.body.dispatchEvent(new content.Event("dog"));
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ is(markers.length, 1, "Got 1 marker");
+ is(markers[0].type, "dog", "Got dog event name");
+ is(markers[0].eventPhase, 2, "Got phase 2");
+ },
+ },
+ {
+ desc: "Event dispatch with a second handler",
+ searchFor(markers) {
+ return markers.filter(m => m.name == "DOMEvent").length >= 2;
+ },
+ setup(docShell) {
+ content.document.body.addEventListener("dog", function(e) {
+ console.log("hi");
+ });
+ content.document.body.dispatchEvent(new content.Event("dog"));
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ is(markers.length, 2, "Got 2 markers");
+ },
+ },
+ {
+ desc: "Event targeted at child",
+ searchFor(markers) {
+ return markers.filter(m => m.name == "DOMEvent").length >= 2;
+ },
+ setup(docShell) {
+ let child = content.document.body.firstElementChild;
+ child.addEventListener("dog", function(e) {});
+ child.dispatchEvent(new content.Event("dog"));
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ is(markers.length, 2, "Got 2 markers");
+ is(markers[0].eventPhase, 1, "Got phase 1 marker");
+ is(markers[1].eventPhase, 2, "Got phase 2 marker");
+ },
+ },
+ {
+ desc: "Event dispatch on a new document",
+ searchFor(markers) {
+ return markers.filter(m => m.name == "DOMEvent").length >= 2;
+ },
+ setup(docShell) {
+ let doc = content.document.implementation.createHTMLDocument("doc");
+ let p = doc.createElement("p");
+ p.innerHTML = "inside";
+ doc.body.appendChild(p);
+
+ p.addEventListener("zebra", function(e) {
+ console.log("hi");
+ });
+ p.dispatchEvent(new content.Event("zebra"));
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ is(markers.length, 1, "Got 1 marker");
+ },
+ },
+ {
+ desc: "Event dispatch on window",
+ searchFor(markers) {
+ return markers.filter(m => m.name == "DOMEvent").length >= 2;
+ },
+ setup(docShell) {
+ content.window.addEventListener("aardvark", function(e) {
+ console.log("I like ants!");
+ });
+
+ content.window.dispatchEvent(new content.Event("aardvark"));
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ is(markers.length, 1, "Got 1 marker");
+ },
+ },
+];
+
+timelineContentTest(TESTS);
diff --git a/docshell/test/browser/browser_timelineMarkers-frame-04.js b/docshell/test/browser/browser_timelineMarkers-frame-04.js
new file mode 100644
index 0000000000..a05804c5b3
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file expects frame-head.js to be loaded in the environment.
+/* import-globals-from frame-head.js */
+
+"use strict";
+
+// Test that the docShell profile timeline API returns the right
+// markers for XMLHttpRequest events.
+
+var TESTS = [
+ {
+ desc: "Event dispatch from XMLHttpRequest",
+ searchFor(markers) {
+ return markers.filter(m => m.name == "DOMEvent").length >= 5;
+ },
+ setup(docShell) {
+ content.dispatchEvent(new content.Event("dog"));
+ },
+ check(markers) {
+ let domMarkers = markers.filter(m => m.name == "DOMEvent");
+ // One subtlety here is that we have five events: the event we
+ // inject in "setup", plus the four state transition events. The
+ // first state transition is reported synchronously and so should
+ // show up as a nested marker.
+ is(domMarkers.length, 5, "Got 5 markers");
+
+ // We should see some Javascript markers, and they should have a
+ // cause.
+ let jsMarkers = markers.filter(
+ m => m.name == "Javascript" && m.causeName
+ );
+ ok(jsMarkers.length > 0, "Got some Javascript markers");
+ is(
+ jsMarkers[0].stack.functionDisplayName,
+ "do_xhr",
+ "Javascript marker has entry point name"
+ );
+ },
+ },
+];
+
+if (
+ !Services.prefs.getBoolPref(
+ "javascript.options.asyncstack_capture_debuggee_only"
+ )
+) {
+ TESTS.push(
+ {
+ desc: "Async stack trace on Javascript marker",
+ searchFor: markers => {
+ return markers.some(
+ m => m.name == "Javascript" && m.causeName == "promise callback"
+ );
+ },
+ setup(docShell) {
+ content.dispatchEvent(new content.Event("promisetest"));
+ },
+ check(markers) {
+ markers = markers.filter(
+ m => m.name == "Javascript" && m.causeName == "promise callback"
+ );
+ ok(markers.length > 0, "Found a Javascript marker");
+
+ let frame = markers[0].stack;
+ ok(frame.asyncParent !== null, "Parent frame has async parent");
+ is(
+ frame.asyncParent.asyncCause,
+ "promise callback",
+ "Async parent has correct cause"
+ );
+ let asyncFrame = frame.asyncParent;
+ // Skip over self-hosted parts of our Promise implementation.
+ while (asyncFrame.source === "self-hosted") {
+ asyncFrame = asyncFrame.parent;
+ }
+ is(
+ asyncFrame.functionDisplayName,
+ "do_promise",
+ "Async parent has correct function name"
+ );
+ },
+ },
+ {
+ desc: "Async stack trace on Javascript marker with script",
+ searchFor: markers => {
+ return markers.some(
+ m => m.name == "Javascript" && m.causeName == "promise callback"
+ );
+ },
+ setup(docShell) {
+ content.dispatchEvent(new content.Event("promisescript"));
+ },
+ check(markers) {
+ markers = markers.filter(
+ m => m.name == "Javascript" && m.causeName == "promise callback"
+ );
+ ok(markers.length > 0, "Found a Javascript marker");
+
+ let frame = markers[0].stack;
+ ok(frame.asyncParent !== null, "Parent frame has async parent");
+ is(
+ frame.asyncParent.asyncCause,
+ "promise callback",
+ "Async parent has correct cause"
+ );
+ let asyncFrame = frame.asyncParent;
+ // Skip over self-hosted parts of our Promise implementation.
+ while (asyncFrame.source === "self-hosted") {
+ asyncFrame = asyncFrame.parent;
+ }
+ is(
+ asyncFrame.functionDisplayName,
+ "do_promise_script",
+ "Async parent has correct function name"
+ );
+ },
+ }
+ );
+}
+
+timelineContentTest(TESTS);
diff --git a/docshell/test/browser/browser_timelineMarkers-frame-05.js b/docshell/test/browser/browser_timelineMarkers-frame-05.js
new file mode 100644
index 0000000000..b5c245e451
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file expects frame-head.js to be loaded in the environment.
+/* import-globals-from frame-head.js */
+
+"use strict";
+
+function forceSyncReflow(div) {
+ div.setAttribute("class", "resize-change-color");
+ // Force a reflow.
+ return div.offsetWidth;
+}
+
+function testSendingEvent() {
+ content.document.body.dispatchEvent(new content.Event("dog"));
+}
+
+function testConsoleTime() {
+ content.console.time("cats");
+}
+
+function testConsoleTimeEnd() {
+ content.console.timeEnd("cats");
+}
+
+function makePromise() {
+ let resolver;
+ new Promise(function(resolve, reject) {
+ testConsoleTime();
+ resolver = resolve;
+ }).then(function(val) {
+ testConsoleTimeEnd();
+ });
+ return resolver;
+}
+
+function resolvePromise(resolver) {
+ resolver(23);
+}
+
+var TESTS = [
+ {
+ desc: "Stack trace on sync reflow",
+ searchFor: "Reflow",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ forceSyncReflow(div);
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "Reflow");
+ ok(markers.length > 0, "Reflow marker includes stack");
+ ok(markers[0].stack.functionDisplayName == "forceSyncReflow");
+ },
+ },
+ {
+ desc: "Stack trace on DOM event",
+ searchFor: "DOMEvent",
+ setup(docShell) {
+ content.document.body.addEventListener(
+ "dog",
+ function(e) {
+ console.log("hi");
+ },
+ true
+ );
+ testSendingEvent();
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "DOMEvent");
+ ok(markers.length > 0, "DOMEvent marker includes stack");
+ ok(
+ markers[0].stack.functionDisplayName == "testSendingEvent",
+ "testSendingEvent is on the stack"
+ );
+ },
+ },
+ {
+ desc: "Stack trace on console event",
+ searchFor: "ConsoleTime",
+ setup(docShell) {
+ testConsoleTime();
+ testConsoleTimeEnd();
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "ConsoleTime");
+ ok(markers.length > 0, "ConsoleTime marker includes stack");
+ ok(
+ markers[0].stack.functionDisplayName == "testConsoleTime",
+ "testConsoleTime is on the stack"
+ );
+ ok(
+ markers[0].endStack.functionDisplayName == "testConsoleTimeEnd",
+ "testConsoleTimeEnd is on the stack"
+ );
+ },
+ },
+];
+
+if (
+ !Services.prefs.getBoolPref(
+ "javascript.options.asyncstack_capture_debuggee_only"
+ )
+) {
+ TESTS.push({
+ desc: "Async stack trace on Promise",
+ searchFor: "ConsoleTime",
+ setup(docShell) {
+ let resolver = makePromise();
+ resolvePromise(resolver);
+ },
+ check(markers) {
+ markers = markers.filter(m => m.name == "ConsoleTime");
+ ok(markers.length > 0, "Promise marker includes stack");
+ ok(
+ markers[0].stack.functionDisplayName == "testConsoleTime",
+ "testConsoleTime is on the stack"
+ );
+ let frame = markers[0].endStack;
+ ok(
+ frame.functionDisplayName == "testConsoleTimeEnd",
+ "testConsoleTimeEnd is on the stack"
+ );
+
+ frame = frame.parent;
+ ok(
+ frame.functionDisplayName == "makePromise/<",
+ "makePromise/< is on the stack"
+ );
+ let asyncFrame = frame.asyncParent;
+ ok(asyncFrame !== null, "Frame has async parent");
+ is(
+ asyncFrame.asyncCause,
+ "promise callback",
+ "Async parent has correct cause"
+ );
+ // Skip over self-hosted parts of our Promise implementation.
+ while (asyncFrame.source === "self-hosted") {
+ asyncFrame = asyncFrame.parent;
+ }
+ is(
+ asyncFrame.functionDisplayName,
+ "makePromise",
+ "Async parent has correct function name"
+ );
+ },
+ });
+}
+
+timelineContentTest(TESTS);
diff --git a/docshell/test/browser/browser_ua_emulation.js b/docshell/test/browser/browser_ua_emulation.js
new file mode 100644
index 0000000000..604f302179
--- /dev/null
+++ b/docshell/test/browser/browser_ua_emulation.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+// Test that the docShell UA emulation works
+async function contentTaskNoOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customUserAgent,
+ "",
+ "There should initially be no customUserAgent"
+ );
+}
+
+async function contentTaskOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customUserAgent,
+ "foo",
+ "The user agent should be changed to foo"
+ );
+
+ is(
+ content.navigator.userAgent,
+ "foo",
+ "The user agent should be changed to foo"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ is(
+ frameWin.navigator.userAgent,
+ "foo",
+ "The UA should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ is(
+ newFrameWin.navigator.userAgent,
+ "foo",
+ "Newly created frames should use the new UA"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+
+ is(
+ newFrameWin.navigator.userAgent,
+ "foo",
+ "New UA should persist across reloads"
+ );
+}
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function(
+ browser
+ ) {
+ await SpecialPowers.spawn(browser, [], contentTaskNoOverride);
+
+ let browsingContext = BrowserTestUtils.getBrowsingContextFrom(browser);
+ browsingContext.customUserAgent = "foo";
+
+ await SpecialPowers.spawn(browser, [], contentTaskOverride);
+ });
+});
diff --git a/docshell/test/browser/browser_uriFixupAlternateRedirects.js b/docshell/test/browser/browser_uriFixupAlternateRedirects.js
new file mode 100644
index 0000000000..ffe58bc754
--- /dev/null
+++ b/docshell/test/browser/browser_uriFixupAlternateRedirects.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { UrlbarTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlbarTestUtils.jsm"
+);
+
+const REDIRECTURL =
+ "http://www.example.com/browser/docshell/test/browser/redirect_to_example.sjs";
+
+add_task(async function() {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ value => {
+ gURLBar.value = value;
+ },
+ value => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value,
+ });
+ },
+ ];
+ for (let setValueFn of setValueFns) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ // Enter search terms and start a search.
+ gURLBar.focus();
+ await setValueFn(REDIRECTURL);
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await errorPageLoaded;
+ let [contentURL, originalURL] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [
+ content.document.documentURI,
+ content.document.mozDocumentURIIfNotForErrorPages.spec,
+ ];
+ }
+ );
+ info("Page that loaded: " + contentURL);
+ const errorURI = "about:neterror?";
+ ok(contentURL.startsWith(errorURI), "Should be on an error page");
+
+ const contentPrincipal = tab.linkedBrowser.contentPrincipal;
+ ok(
+ contentPrincipal.spec.startsWith(errorURI),
+ "Principal should be for the error page"
+ );
+
+ originalURL = new URL(originalURL);
+ is(
+ originalURL.host,
+ "example",
+ "Should be an error for http://example, not http://www.example.com/"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/docshell/test/browser/browser_uriFixupIntegration.js b/docshell/test/browser/browser_uriFixupIntegration.js
new file mode 100644
index 0000000000..e00c5e0f63
--- /dev/null
+++ b/docshell/test/browser/browser_uriFixupIntegration.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { UrlbarTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlbarTestUtils.jsm"
+);
+
+const kSearchEngineID = "browser_urifixup_search_engine";
+const kSearchEngineURL = "http://example.com/?search={searchTerms}";
+const kPrivateSearchEngineID = "browser_urifixup_search_engine_private";
+const kPrivateSearchEngineURL = "http://example.com/?private={searchTerms}";
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ],
+ });
+
+ let oldCurrentEngine = await Services.search.getDefault();
+ let oldPrivateEngine = await Services.search.getDefaultPrivate();
+
+ // Add new fake search engines.
+ let newCurrentEngine = await Services.search.addEngineWithDetails(
+ kSearchEngineID,
+ {
+ method: "get",
+ template: kSearchEngineURL,
+ }
+ );
+ await Services.search.setDefault(newCurrentEngine);
+
+ let newPrivateEngine = await Services.search.addEngineWithDetails(
+ kPrivateSearchEngineID,
+ {
+ method: "get",
+ template: kPrivateSearchEngineURL,
+ }
+ );
+ await Services.search.setDefaultPrivate(newPrivateEngine);
+
+ // Remove the fake engines when done.
+ registerCleanupFunction(async () => {
+ if (oldCurrentEngine) {
+ await Services.search.setDefault(oldCurrentEngine);
+ }
+ if (oldPrivateEngine) {
+ await Services.search.setDefault(oldPrivateEngine);
+ }
+ await Services.search.removeEngine(newCurrentEngine);
+ await Services.search.removeEngine(newPrivateEngine);
+ });
+});
+
+add_task(async function test() {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ (value, win) => {
+ win.gURLBar.value = value;
+ },
+ (value, win) => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ waitForFocus: SimpleTest.waitForFocus,
+ value,
+ });
+ },
+ ];
+
+ for (let value of ["foo bar", "brokenprotocol:somethingelse"]) {
+ for (let setValueFn of setValueFns) {
+ for (let inPrivateWindow of [false, true]) {
+ await do_test(value, setValueFn, inPrivateWindow);
+ }
+ }
+ }
+});
+
+async function do_test(value, setValueFn, inPrivateWindow) {
+ info(`Search ${value} in a ${inPrivateWindow ? "private" : "normal"} window`);
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: inPrivateWindow,
+ });
+ // Enter search terms and start a search.
+ win.gURLBar.focus();
+ await setValueFn(value, win);
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+
+ // Check that we load the correct URL.
+ let escapedValue = encodeURIComponent(value).replace("%20", "+");
+ let searchEngineUrl = inPrivateWindow
+ ? kPrivateSearchEngineURL
+ : kSearchEngineURL;
+ let expectedURL = searchEngineUrl.replace("{searchTerms}", escapedValue);
+ await BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ expectedURL
+ );
+ // There should be at least one test.
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ expectedURL,
+ "New tab should have loaded with expected url."
+ );
+
+ // Cleanup.
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/docshell/test/browser/browser_viewsource_chrome_to_content.js b/docshell/test/browser/browser_viewsource_chrome_to_content.js
new file mode 100644
index 0000000000..e7e404bb06
--- /dev/null
+++ b/docshell/test/browser/browser_viewsource_chrome_to_content.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_URI = `view-source:${TEST_PATH}dummy_page.html`;
+
+add_task(async function chrome_to_content_view_source() {
+ await BrowserTestUtils.withNewTab("about:mozilla", async browser => {
+ is(browser.documentURI.spec, "about:mozilla");
+
+ // This process switch would previously crash in debug builds due to assertion failures.
+ BrowserTestUtils.loadURI(browser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(browser.documentURI.spec, TEST_URI);
+ });
+});
diff --git a/docshell/test/browser/browser_viewsource_multipart.js b/docshell/test/browser/browser_viewsource_multipart.js
new file mode 100644
index 0000000000..3a3a5836af
--- /dev/null
+++ b/docshell/test/browser/browser_viewsource_multipart.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const MULTIPART_URI = `${TEST_PATH}file_basic_multipart.sjs`;
+
+add_task(async function viewsource_multipart_uri() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ BrowserTestUtils.loadURI(browser, MULTIPART_URI);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(browser.currentURI.spec, MULTIPART_URI);
+
+ // Continue probing the URL until we find the h1 we're expecting. This
+ // should handle cases where we somehow beat the second document having
+ // loaded.
+ await TestUtils.waitForCondition(async () => {
+ let value = await SpecialPowers.spawn(browser, [], async () => {
+ let headers = content.document.querySelectorAll("h1");
+ is(headers.length, 1, "only one h1 should be present");
+ return headers[0].textContent;
+ });
+
+ ok(value == "First" || value == "Second", "some other value was found?");
+ return value == "Second";
+ });
+
+ // Load a view-source version of the page, which should show the full
+ // content, not handling multipart.
+ BrowserTestUtils.loadURI(browser, `view-source:${MULTIPART_URI}`);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let viewSourceContent = await SpecialPowers.spawn(browser, [], async () => {
+ return content.document.body.textContent;
+ });
+
+ ok(viewSourceContent.includes("<h1>First</h1>"), "first header");
+ ok(viewSourceContent.includes("<h1>Second</h1>"), "second header");
+ ok(viewSourceContent.includes("BOUNDARY"), "boundary");
+ });
+});
diff --git a/docshell/test/browser/dummy_iframe_page.html b/docshell/test/browser/dummy_iframe_page.html
new file mode 100644
index 0000000000..12ce921856
--- /dev/null
+++ b/docshell/test/browser/dummy_iframe_page.html
@@ -0,0 +1,8 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file with an iframe
+ <iframe id="frame1" src="dummy_page.html?sub_entry=0"></iframe>
+ <iframe id="frame2" src="dummy_page.html?sub_entry=0"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/browser/dummy_page.html b/docshell/test/browser/dummy_page.html
new file mode 100644
index 0000000000..59bf2a5f8f
--- /dev/null
+++ b/docshell/test/browser/dummy_page.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file
+ </body>
+</html>
diff --git a/docshell/test/browser/favicon_bug655270.ico b/docshell/test/browser/favicon_bug655270.ico
new file mode 100644
index 0000000000..d44438903b
--- /dev/null
+++ b/docshell/test/browser/favicon_bug655270.ico
Binary files differ
diff --git a/docshell/test/browser/file_basic_multipart.sjs b/docshell/test/browser/file_basic_multipart.sjs
new file mode 100644
index 0000000000..5e89b93948
--- /dev/null
+++ b/docshell/test/browser/file_basic_multipart.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader(
+ "Content-Type",
+ "multipart/x-mixed-replace;boundary=BOUNDARY",
+ false
+ );
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ response.write(`--BOUNDARY
+Content-Type: text/html
+
+<h1>First</h1>
+Will be replaced
+--BOUNDARY
+Content-Type: text/html
+
+<h1>Second</h1>
+This will stick around
+--BOUNDARY
+--BOUNDARY--
+`);
+}
diff --git a/docshell/test/browser/file_bug1046022.html b/docshell/test/browser/file_bug1046022.html
new file mode 100644
index 0000000000..27a1e1f079
--- /dev/null
+++ b/docshell/test/browser/file_bug1046022.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1046022 - test navigating inside onbeforeunload</title>
+ </head>
+ <body>
+ Waiting for onbeforeunload to hit...
+ </body>
+
+ <script>
+var testFns = [
+ function(e) {
+ e.target.location.href = "otherpage-href-set.html";
+ return "stop";
+ },
+ function(e) {
+ e.target.location.reload();
+ return "stop";
+ },
+ function(e) {
+ e.currentTarget.stop();
+ return "stop";
+ },
+ function(e) {
+ e.target.location.replace("otherpage-location-replaced.html");
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = "otherpage.html";
+ e.target.body.appendChild(link);
+ link.click();
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = "otherpage.html";
+ link.setAttribute("target", "_blank");
+ e.target.body.appendChild(link);
+ link.click();
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = e.target.location.href;
+ e.target.body.appendChild(link);
+ link.setAttribute("target", "somearbitrarywindow");
+ link.click();
+ return "stop";
+ },
+];
+ </script>
+</html>
diff --git a/docshell/test/browser/file_bug1206879.html b/docshell/test/browser/file_bug1206879.html
new file mode 100644
index 0000000000..5313902a9b
--- /dev/null
+++ b/docshell/test/browser/file_bug1206879.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test page for bug 1206879</title>
+ </head>
+ <body>
+ <iframe src="http://example.com/"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501.html b/docshell/test/browser/file_bug1328501.html
new file mode 100644
index 0000000000..517ef53e02
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Page with iframes</title>
+ <script type="application/javascript">
+ let promiseResolvers = {
+ "testFrame1": {},
+ "testFrame2": {},
+ };
+ let promises = [
+ new Promise(r => promiseResolvers.testFrame1.resolve = r),
+ new Promise(r => promiseResolvers.testFrame2.resolve = r),
+ ];
+ function frameLoaded(frame) {
+ promiseResolvers[frame].resolve();
+ }
+ Promise.all(promises).then(() => window.dispatchEvent(new Event("frames-loaded")));
+ </script>
+ </head>
+ <body onunload="">
+ <div>
+ <iframe id="testFrame1" src="dummy_page.html" onload="frameLoaded(this.id);" ></iframe>
+ <iframe id="testFrame2" src="dummy_page.html" onload="frameLoaded(this.id);" ></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501_frame.html b/docshell/test/browser/file_bug1328501_frame.html
new file mode 100644
index 0000000000..156dd41eaa
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501_frame.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html lang="en">
+ <body>Subframe page for testing</body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501_framescript.js b/docshell/test/browser/file_bug1328501_framescript.js
new file mode 100644
index 0000000000..19c86c75e7
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501_framescript.js
@@ -0,0 +1,38 @@
+// Forward iframe loaded event.
+
+/* eslint-env mozilla/frame-script */
+
+addEventListener(
+ "frames-loaded",
+ e => sendAsyncMessage("test:frames-loaded"),
+ true,
+ true
+);
+
+let requestObserver = {
+ observe(subject, topic, data) {
+ if (topic == "http-on-opening-request") {
+ // Get DOMWindow on all child docshells to force about:blank
+ // content viewers being created.
+ getChildDocShells().map(ds => {
+ ds
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsILoadContext).associatedWindow;
+ });
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+};
+Services.obs.addObserver(requestObserver, "http-on-opening-request");
+addEventListener("unload", e => {
+ if (e.target == this) {
+ Services.obs.removeObserver(requestObserver, "http-on-opening-request");
+ }
+});
+
+function getChildDocShells() {
+ return docShell.getAllDocShellsInSubtree(
+ Ci.nsIDocShellTreeItem.typeAll,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS
+ );
+}
diff --git a/docshell/test/browser/file_bug1543077-1-child.html b/docshell/test/browser/file_bug1543077-1-child.html
new file mode 100644
index 0000000000..d244b27717
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-1-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as EUC-JP: ¤˘</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-1.html b/docshell/test/browser/file_bug1543077-1.html
new file mode 100644
index 0000000000..4d37ec18fc
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Hiragana letter a if decoded as EUC-JP: ¤˘</p>
+
+<iframe src="file_bug1543077-1-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-2-child.html b/docshell/test/browser/file_bug1543077-2-child.html
new file mode 100644
index 0000000000..c3415e97a4
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-2-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as Shift_JIS: ‚ </p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-2.html b/docshell/test/browser/file_bug1543077-2.html
new file mode 100644
index 0000000000..c0ad81570f
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Hiragana letter a if decoded as Shift_JIS: ‚ </p>
+
+<iframe src="file_bug1543077-2-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-3-child.html b/docshell/test/browser/file_bug1543077-3-child.html
new file mode 100644
index 0000000000..858a4623ed
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-3-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as ISO-2022-JP: $B$"(B</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-3.html b/docshell/test/browser/file_bug1543077-3.html
new file mode 100644
index 0000000000..c4f467dd3f
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-3.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Hiragana letter a if decoded as ISO-2022-JP: $B$"(B</p>
+
+<iframe src="file_bug1543077-3-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-4-child.html b/docshell/test/browser/file_bug1543077-4-child.html
new file mode 100644
index 0000000000..c3415e97a4
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-4-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as Shift_JIS: ‚ </p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-4.html b/docshell/test/browser/file_bug1543077-4.html
new file mode 100644
index 0000000000..b8feb4cba6
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-4.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Hiragana letter a if decoded as EUC-JP: ¤˘</p>
+
+<iframe src="file_bug1543077-4-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1622420.html b/docshell/test/browser/file_bug1622420.html
new file mode 100644
index 0000000000..63beb38302
--- /dev/null
+++ b/docshell/test/browser/file_bug1622420.html
@@ -0,0 +1 @@
+<iframe src="http://example.com/"></iframe>
diff --git a/docshell/test/browser/file_bug1648464-1-child.html b/docshell/test/browser/file_bug1648464-1-child.html
new file mode 100644
index 0000000000..7bb1ad965b
--- /dev/null
+++ b/docshell/test/browser/file_bug1648464-1-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset=windows-1252>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>windows-1252 in parent and child, actually EUC-JP</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as EUC-JP: ¤˘</p>
+<p>¤ł¤ě¤Ďʸ»úĽÂ¸ł¤Ç¤ąˇŁ</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1648464-1.html b/docshell/test/browser/file_bug1648464-1.html
new file mode 100644
index 0000000000..2051cf61ed
--- /dev/null
+++ b/docshell/test/browser/file_bug1648464-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset=windows-1252>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>windows-1252 in parent and child, actually EUC-JP</title>
+</head>
+<body>
+<h1>windows-1252 in parent and child, actually EUC-JP</h1>
+
+<p>Hiragana letter a if decoded as EUC-JP: ¤˘</p>
+<p>¤ł¤ě¤Ďʸ»úĽÂ¸ł¤Ç¤ąˇŁ</p>
+
+<iframe src="file_bug1648464-1-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1673702.json b/docshell/test/browser/file_bug1673702.json
new file mode 100644
index 0000000000..6d7227eb1f
--- /dev/null
+++ b/docshell/test/browser/file_bug1673702.json
@@ -0,0 +1 @@
+{ "version": 1, }
diff --git a/docshell/test/browser/file_bug1673702.json^headers^ b/docshell/test/browser/file_bug1673702.json^headers^
new file mode 100644
index 0000000000..6010bfd188
--- /dev/null
+++ b/docshell/test/browser/file_bug1673702.json^headers^
@@ -0,0 +1 @@
+Content-Type: application/json; charset=utf-8
diff --git a/docshell/test/browser/file_bug1688368-1.sjs b/docshell/test/browser/file_bug1688368-1.sjs
new file mode 100644
index 0000000000..a14ff296f3
--- /dev/null
+++ b/docshell/test/browser/file_bug1688368-1.sjs
@@ -0,0 +1,32 @@
+"use strict";
+
+const DELAY = 1 * 1000; // Delay one second before completing the request.
+
+let nsTimer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
+
+let timer;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(`<!DOCTYPE html>
+<html>
+<head>
+ <title>UTF-8 file, 1024 bytes long!</title>
+</head>
+<body>`);
+
+ // Note: We need to store a reference to the timer to prevent it from being
+ // canceled when it's GCed.
+ timer = new nsTimer(() => {
+ var snowmen = "\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083";
+ response.write(snowmen + `
+</body>
+</html>
+
+`);
+ response.finish();
+ }, DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/docshell/test/browser/file_bug234628-1-child.html b/docshell/test/browser/file_bug234628-1-child.html
new file mode 100644
index 0000000000..c36197ac4f
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-1-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-1.html b/docshell/test/browser/file_bug234628-1.html
new file mode 100644
index 0000000000..11c523ccd9
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-1-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-10-child.xhtml b/docshell/test/browser/file_bug234628-10-child.xhtml
new file mode 100644
index 0000000000..cccf6f2bc0
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-10-child.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head><title>XML child with no encoding declaration</title></head>
+<body><p>Euro sign if decoded as UTF-8: €</p></body>
+</html>
diff --git a/docshell/test/browser/file_bug234628-10.html b/docshell/test/browser/file_bug234628-10.html
new file mode 100644
index 0000000000..78b8f0035d
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-10.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in HTML parent or XHTML child</title>
+</head>
+<body>
+<h1>No encoding declaration in HTML parent or XHTML child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-10-child.xhtml"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml b/docshell/test/browser/file_bug234628-11-child.xhtml
new file mode 100644
index 0000000000..11ef668b0c
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11-child.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head><title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title></head>
+<body><p>Euro sign if decoded as UTF-8: €</p></body>
+</html>
diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml^headers^ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^
new file mode 100644
index 0000000000..30fb304056
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^
@@ -0,0 +1 @@
+Content-Type: application/xhtml+xml; charset=utf-8
diff --git a/docshell/test/browser/file_bug234628-11.html b/docshell/test/browser/file_bug234628-11.html
new file mode 100644
index 0000000000..21c5b733e0
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title>
+</head>
+<body>
+<h1>No encoding declaration in HTML parent and HTTP declaration in XHTML child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-11-child.xhtml"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-2-child.html b/docshell/test/browser/file_bug234628-2-child.html
new file mode 100644
index 0000000000..0acd2e0b27
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-2-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-2.html b/docshell/test/browser/file_bug234628-2.html
new file mode 100644
index 0000000000..a87d29e126
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-2-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-3-child.html b/docshell/test/browser/file_bug234628-3-child.html
new file mode 100644
index 0000000000..a6ad832310
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-3-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-3.html b/docshell/test/browser/file_bug234628-3.html
new file mode 100644
index 0000000000..8caab60402
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-3.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and child</title>
+</head>
+<body>
+<h1>meta declaration in parent and child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-3-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-4-child.html b/docshell/test/browser/file_bug234628-4-child.html
new file mode 100644
index 0000000000..f0e7c2c058
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-4-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOM in child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-4.html b/docshell/test/browser/file_bug234628-4.html
new file mode 100644
index 0000000000..0137579010
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOM in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and BOM in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-4-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-5-child.html b/docshell/test/browser/file_bug234628-5-child.html
new file mode 100644
index 0000000000..a650552f63
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-5-child.html
Binary files differ
diff --git a/docshell/test/browser/file_bug234628-5.html b/docshell/test/browser/file_bug234628-5.html
new file mode 100644
index 0000000000..987e6420be
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-5.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and UTF-16 BOM in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and UTF-16 BOM in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-5-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-6-child.html b/docshell/test/browser/file_bug234628-6-child.html
new file mode 100644
index 0000000000..52c37f2596
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6-child.html
Binary files differ
diff --git a/docshell/test/browser/file_bug234628-6-child.html^headers^ b/docshell/test/browser/file_bug234628-6-child.html^headers^
new file mode 100644
index 0000000000..bfdcf487fb
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6-child.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=utf-16be
diff --git a/docshell/test/browser/file_bug234628-6.html b/docshell/test/browser/file_bug234628-6.html
new file mode 100644
index 0000000000..9d7fc580c3
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-6-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-7-child.html b/docshell/test/browser/file_bug234628-7-child.html
new file mode 100644
index 0000000000..c761ace101
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-7-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-7-child.html^headers^ b/docshell/test/browser/file_bug234628-7-child.html^headers^
new file mode 100644
index 0000000000..2d1c08b9e8
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-7-child.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=utf-8
diff --git a/docshell/test/browser/file_bug234628-7.html b/docshell/test/browser/file_bug234628-7.html
new file mode 100644
index 0000000000..7cb506096d
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-7.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: €</p>
+<p>a with diaeresis if decoded as Windows-1252: ä</p>
+
+<iframe src="file_bug234628-7-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-8-child.html b/docshell/test/browser/file_bug234628-8-child.html
new file mode 100644
index 0000000000..254e0fb2b3
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-8-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and no declaration in child</title>
+</head>
+<body>
+<p>Capital dje if decoded as Windows-1251: €</p>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-8.html b/docshell/test/browser/file_bug234628-8.html
new file mode 100644
index 0000000000..b44e91801c
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-8.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1251">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and no declaration in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and no declaration in child</h1>
+
+<p>Capital dje if decoded as Windows-1251: €</p>
+
+<iframe src="file_bug234628-8-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-9-child.html b/docshell/test/browser/file_bug234628-9-child.html
new file mode 100644
index 0000000000..a86b14d7ee
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-9-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>UTF-16 with BOM in parent and no declaration in child</title>
+</head>
+<body>
+<p>Euro sign if decoded as Windows-1251: €</p>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-9.html b/docshell/test/browser/file_bug234628-9.html
new file mode 100644
index 0000000000..8a469da3aa
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-9.html
Binary files differ
diff --git a/docshell/test/browser/file_bug420605.html b/docshell/test/browser/file_bug420605.html
new file mode 100644
index 0000000000..8424b92f8f
--- /dev/null
+++ b/docshell/test/browser/file_bug420605.html
@@ -0,0 +1,31 @@
+<head>
+<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/>
+ <title>Page Title for Bug 420605</title>
+</head>
+<body>
+ <h1>Fragment links</h1>
+
+ <p>This page has a bunch of fragment links to sections below:</p>
+
+ <ul>
+ <li><a id="firefox-link" href="#firefox">Firefox</a></li>
+ <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li>
+ <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li>
+ </ul>
+
+ <p>And here are the sections:</p>
+
+ <h2 id="firefox">Firefox</h2>
+
+ <p>Firefox is a browser.</p>
+
+ <h2 id="thunderbird">Thunderbird</h2>
+
+ <p>Thunderbird is an email client</p>
+
+ <h2 id="seamonkey">Seamonkey</h2>
+
+ <p>Seamonkey is the all-in-one application.</p>
+
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug503832.html b/docshell/test/browser/file_bug503832.html
new file mode 100644
index 0000000000..338631c8a0
--- /dev/null
+++ b/docshell/test/browser/file_bug503832.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=503832
+-->
+<head>
+ <title>Page Title for Bug 503832</title>
+</head>
+<body>
+ <h1>Fragment links</h1>
+
+ <p>This page has a bunch of fragment links to sections below:</p>
+
+ <ul>
+ <li><a id="firefox-link" href="#firefox">Firefox</a></li>
+ <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li>
+ <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li>
+ </ul>
+
+ <p>And here are the sections:</p>
+
+ <h2 id="firefox">Firefox</h2>
+
+ <p>Firefox is a browser.</p>
+
+ <h2 id="thunderbird">Thunderbird</h2>
+
+ <p>Thunderbird is an email client</p>
+
+ <h2 id="seamonkey">Seamonkey</h2>
+
+ <p>Seamonkey is the all-in-one application.</p>
+
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug655270.html b/docshell/test/browser/file_bug655270.html
new file mode 100644
index 0000000000..0c08d982b1
--- /dev/null
+++ b/docshell/test/browser/file_bug655270.html
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+ <link rel='icon' href='favicon_bug655270.ico'>
+</head>
+
+<body>
+Nothing to see here...
+</body>
+
+</html>
diff --git a/docshell/test/browser/file_bug670318.html b/docshell/test/browser/file_bug670318.html
new file mode 100644
index 0000000000..a78e8fcb19
--- /dev/null
+++ b/docshell/test/browser/file_bug670318.html
@@ -0,0 +1,23 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<script>
+function load() {
+ function next() {
+ if (count < 5)
+ iframe.src = "data:text/html;charset=utf-8,iframe " + (++count);
+ }
+
+ var count = 0;
+ var iframe = document.createElement("iframe");
+ iframe.onload = function() { setTimeout(next, 0); };
+ document.body.appendChild(iframe);
+
+ setTimeout(next, 0);
+}
+</script>
+</head>
+
+<body onload="load()">
+Testcase
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug852909.pdf b/docshell/test/browser/file_bug852909.pdf
new file mode 100644
index 0000000000..89066463f1
--- /dev/null
+++ b/docshell/test/browser/file_bug852909.pdf
Binary files differ
diff --git a/docshell/test/browser/file_bug852909.png b/docshell/test/browser/file_bug852909.png
new file mode 100644
index 0000000000..c7510d388f
--- /dev/null
+++ b/docshell/test/browser/file_bug852909.png
Binary files differ
diff --git a/docshell/test/browser/file_click_link_within_view_source.html b/docshell/test/browser/file_click_link_within_view_source.html
new file mode 100644
index 0000000000..d78e4ba0ff
--- /dev/null
+++ b/docshell/test/browser/file_click_link_within_view_source.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <a id="testlink" href="dummy_page.html">clickme</a>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_cross_process_csp_inheritance.html b/docshell/test/browser/file_cross_process_csp_inheritance.html
new file mode 100644
index 0000000000..d87761a609
--- /dev/null
+++ b/docshell/test/browser/file_cross_process_csp_inheritance.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test CSP inheritance if load happens in same and different process</title>
+ <meta http-equiv="Content-Security-Policy" content="script-src 'none'">
+</head>
+<body>
+ <a href="data:text/html,<html>test-same-diff-process-csp-inhertiance</html>" id="testLink" target="_blank" rel="noopener">click to test same/diff process CSP inheritance</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_csp_uir.html b/docshell/test/browser/file_csp_uir.html
new file mode 100644
index 0000000000..be60f41a80
--- /dev/null
+++ b/docshell/test/browser/file_csp_uir.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1542858 - Test CSP upgrade-insecure-requests</title>
+ <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+</head>
+<body>
+ <a id="testlink" href="file_csp_uir_dummy.html">testlink</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_csp_uir_dummy.html b/docshell/test/browser/file_csp_uir_dummy.html
new file mode 100644
index 0000000000..f0ab6775c0
--- /dev/null
+++ b/docshell/test/browser/file_csp_uir_dummy.html
@@ -0,0 +1 @@
+<html><body>foo</body></html>
diff --git a/docshell/test/browser/file_data_load_inherit_csp.html b/docshell/test/browser/file_data_load_inherit_csp.html
new file mode 100644
index 0000000000..1efe738e4c
--- /dev/null
+++ b/docshell/test/browser/file_data_load_inherit_csp.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1358009 - Inherit CSP into data URI</title>
+ <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'">
+</head>
+<body>
+ <a id="testlink">testlink</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_multiple_pushState.html b/docshell/test/browser/file_multiple_pushState.html
new file mode 100644
index 0000000000..580a30645e
--- /dev/null
+++ b/docshell/test/browser/file_multiple_pushState.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test multiple calls to history.pushState</title>
+ </head>
+ <body>
+ <h1>Ohai</h1>
+ </body>
+ <script type="text/javascript">
+ window.history.pushState({}, "", "/bar/ABC?key=baz");
+ let data = new Array(100000).join("a");
+ window.history.pushState({ data }, "", "/bar/ABC/DEF?key=baz");
+ // Test also Gecko specific state object size limit.
+ try {
+ let largeData = new Array(10000000).join("a");
+ window.history.pushState({ largeData }, "", "/bar/ABC/DEF/GHI?key=baz");
+ } catch (ex) {}
+ </script>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_0.html b/docshell/test/browser/file_onbeforeunload_0.html
new file mode 100644
index 0000000000..7d9acf057d
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_0.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_1.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_1.html b/docshell/test/browser/file_onbeforeunload_1.html
new file mode 100644
index 0000000000..edd27783e4
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://mochi.test:8888/browser/docshell/test/browser/file_onbeforeunload_2.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_2.html b/docshell/test/browser/file_onbeforeunload_2.html
new file mode 100644
index 0000000000..a52a4ace5c
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_3.html"></iframe>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_onbeforeunload_3.html b/docshell/test/browser/file_onbeforeunload_3.html
new file mode 100644
index 0000000000..9914f0cd85
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_open_about_blank.html b/docshell/test/browser/file_open_about_blank.html
new file mode 100644
index 0000000000..134384e2f7
--- /dev/null
+++ b/docshell/test/browser/file_open_about_blank.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<button id="open" onclick="window.open('')">Open child window</button>
diff --git a/docshell/test/browser/file_slow_load.sjs b/docshell/test/browser/file_slow_load.sjs
new file mode 100644
index 0000000000..4c6dd6d5b9
--- /dev/null
+++ b/docshell/test/browser/file_slow_load.sjs
@@ -0,0 +1,8 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/html");
+ response.write("<!doctype html>Loading... ");
+ // We don't block on this, so it's fine to never finish the response.
+}
diff --git a/docshell/test/browser/frame-head.js b/docshell/test/browser/frame-head.js
new file mode 100644
index 0000000000..c785675682
--- /dev/null
+++ b/docshell/test/browser/frame-head.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env mozilla/frame-script */
+
+// Functions that are automatically loaded as frame scripts for
+// timeline tests.
+
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+// Functions that look like mochitest functions but forward to the
+// browser process.
+
+this.ok = function(value, message) {
+ sendAsyncMessage("browser:test:ok", {
+ value: !!value,
+ message,
+ });
+};
+
+this.is = function(v1, v2, message) {
+ ok(v1 == v2, message);
+};
+
+this.info = function(message) {
+ sendAsyncMessage("browser:test:info", { message });
+};
+
+this.finish = function() {
+ sendAsyncMessage("browser:test:finish");
+};
+
+/* Start a task that runs some timeline tests in the ordinary way.
+ *
+ * @param array tests
+ * The tests to run. This is an array where each element
+ * is of the form { desc, searchFor, setup, check }.
+ *
+ * desc is the test description, a string.
+ * searchFor is a string or a function
+ * If a string, then when a marker with this name is
+ * found, marker-reading is stopped.
+ * If a function, then the accumulated marker array is
+ * passed to it, and marker reading stops when it returns
+ * true.
+ * setup is a function that takes the docshell as an argument.
+ * It should start the test.
+ * check is a function that takes an array of markers
+ * as an argument and checks the results of the test.
+ */
+this.timelineContentTest = function(tests) {
+ (async function() {
+ let docShell = content.docShell;
+
+ info("Start recording");
+ docShell.recordProfileTimelineMarkers = true;
+
+ for (let { desc, searchFor, setup, check } of tests) {
+ info("Running test: " + desc);
+
+ info("Flushing the previous markers if any");
+ docShell.popProfileTimelineMarkers();
+
+ info("Running the test setup function");
+ let onMarkers = timelineWaitForMarkers(docShell, searchFor);
+ setup(docShell);
+ info("Waiting for new markers on the docShell");
+ let markers = await onMarkers;
+
+ // Cycle collection markers are non-deterministic, and none of these tests
+ // expect them to show up.
+ markers = markers.filter(m => !m.name.includes("nsCycleCollector"));
+
+ info("Running the test check function");
+ check(markers);
+ }
+
+ info("Stop recording");
+ docShell.recordProfileTimelineMarkers = false;
+ finish();
+ })();
+};
+
+function timelineWaitForMarkers(docshell, searchFor) {
+ if (typeof searchFor == "string") {
+ let searchForString = searchFor;
+ let f = function(markers) {
+ return markers.some(m => m.name == searchForString);
+ };
+ searchFor = f;
+ }
+
+ return new Promise(function(resolve, reject) {
+ let waitIterationCount = 0;
+ let maxWaitIterationCount = 10; // Wait for 2sec maximum
+ let markers = [];
+
+ setTimeout(function timeoutHandler() {
+ let newMarkers = docshell.popProfileTimelineMarkers();
+ markers = [...markers, ...newMarkers];
+ if (searchFor(markers) || waitIterationCount > maxWaitIterationCount) {
+ resolve(markers);
+ } else {
+ setTimeout(timeoutHandler, 200);
+ waitIterationCount++;
+ }
+ }, 200);
+ });
+}
diff --git a/docshell/test/browser/head.js b/docshell/test/browser/head.js
new file mode 100644
index 0000000000..47a649606f
--- /dev/null
+++ b/docshell/test/browser/head.js
@@ -0,0 +1,253 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Helper function for timeline tests. Returns an async task that is
+ * suitable for use as a particular timeline test.
+ * @param string frameScriptName
+ * Base name of the frame script file.
+ * @param string url
+ * URL to load.
+ */
+function makeTimelineTest(frameScriptName, url) {
+ info("in timelineTest");
+ return async function() {
+ info("in in timelineTest");
+ waitForExplicitFinish();
+
+ await timelineTestOpenUrl(url);
+
+ const here = "chrome://mochitests/content/browser/docshell/test/browser/";
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(here + "frame-head.js", false);
+ mm.loadFrameScript(here + frameScriptName, false);
+
+ // Set up some listeners so that timeline tests running in the
+ // content process can forward their results to the main process.
+ mm.addMessageListener("browser:test:ok", function(message) {
+ ok(message.data.value, message.data.message);
+ });
+ mm.addMessageListener("browser:test:info", function(message) {
+ info(message.data.message);
+ });
+ mm.addMessageListener("browser:test:finish", function(ignore) {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ };
+}
+
+/* Open a URL for a timeline test. */
+function timelineTestOpenUrl(url) {
+ window.focus();
+
+ let tabSwitchPromise = new Promise((resolve, reject) => {
+ window.gBrowser.addEventListener(
+ "TabSwitchDone",
+ function() {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+
+ let loadPromise = new Promise(function(resolve, reject) {
+ let browser = window.gBrowser;
+ let tab = (browser.selectedTab = BrowserTestUtils.addTab(browser, url));
+ let linkedBrowser = tab.linkedBrowser;
+
+ BrowserTestUtils.browserLoaded(linkedBrowser).then(() => resolve(tab));
+ });
+
+ return Promise.all([tabSwitchPromise, loadPromise]).then(([_, tab]) => tab);
+}
+
+/**
+ * Helper function for charset tests. It loads |url| in a new tab,
+ * runs |check1| in a ContentTask when the page is ready, switches the
+ * charset to |charset|, and then runs |check2| in a ContentTask when
+ * the page has finished reloading.
+ *
+ * |charset| and |check2| can be omitted, in which case the test
+ * finishes when |check1| completes.
+ */
+function runCharsetTest(url, check1, charset, check2) {
+ waitForExplicitFinish();
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url, true).then(afterOpen);
+
+ function afterOpen() {
+ if (charset) {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check1).then(() => {
+ BrowserSetForcedCharacterSet(charset);
+ });
+ } else {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check1).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ }
+ }
+
+ function afterChangeCharset() {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check2).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ }
+}
+
+async function pushState(url, frameId) {
+ info(
+ `Doing a pushState, expecting to load ${url} ${
+ frameId ? "in an iframe" : ""
+ }`
+ );
+ let browser = gBrowser.selectedBrowser;
+ let bc = browser.browsingContext;
+ if (frameId) {
+ bc = await SpecialPowers.spawn(bc, [frameId], function(id) {
+ return content.document.getElementById(id).browsingContext;
+ });
+ }
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ await SpecialPowers.spawn(bc, [url], function(url) {
+ content.history.pushState({}, "", url);
+ });
+ await loaded;
+ info(`Loaded ${url} ${frameId ? "in an iframe" : ""}`);
+}
+
+async function loadURI(url) {
+ info(`Doing a top-level loadURI, expecting to load ${url}`);
+ let browser = gBrowser.selectedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.loadURI(browser, url);
+ await loaded;
+ info(`Loaded ${url}`);
+}
+
+async function followLink(url, frameId) {
+ info(
+ `Creating and following a link to ${url} ${frameId ? "in an iframe" : ""}`
+ );
+ let browser = gBrowser.selectedBrowser;
+ let bc = browser.browsingContext;
+ if (frameId) {
+ bc = await SpecialPowers.spawn(bc, [frameId], function(id) {
+ return content.document.getElementById(id).browsingContext;
+ });
+ }
+ let loaded = BrowserTestUtils.browserLoaded(browser, !!frameId, url);
+ await SpecialPowers.spawn(bc, [url], function(url) {
+ let a = content.document.createElement("a");
+ a.href = url;
+ content.document.body.appendChild(a);
+ a.click();
+ });
+ await loaded;
+ info(`Loaded ${url} ${frameId ? "in an iframe" : ""}`);
+}
+
+async function goForward(url, useFrame = false) {
+ info(
+ `Clicking the forward button, expecting to load ${url} ${
+ useFrame ? "in an iframe" : ""
+ }`
+ );
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ let forwardButton = document.getElementById("forward-button");
+ forwardButton.click();
+ await loaded;
+ info(`Loaded ${url} ${useFrame ? "in an iframe" : ""}`);
+}
+
+async function goBack(url, useFrame = false) {
+ info(
+ `Clicking the back button, expecting to load ${url} ${
+ useFrame ? "in an iframe" : ""
+ }`
+ );
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ let backButton = document.getElementById("back-button");
+ backButton.click();
+ await loaded;
+ info(`Loaded ${url} ${useFrame ? "in an iframe" : ""}`);
+}
+
+function assertBackForwardState(canGoBack, canGoForward) {
+ let backButton = document.getElementById("back-button");
+ let forwardButton = document.getElementById("forward-button");
+
+ is(
+ backButton.disabled,
+ !canGoBack,
+ `${gBrowser.currentURI.spec}: back button is ${
+ canGoBack ? "not" : ""
+ } disabled`
+ );
+ is(
+ forwardButton.disabled,
+ !canGoForward,
+ `${gBrowser.currentURI.spec}: forward button is ${
+ canGoForward ? "not" : ""
+ } disabled`
+ );
+}
+
+class SHListener {
+ static NewEntry = 0;
+ static Reload = 1;
+ static GotoIndex = 2;
+ static Purge = 3;
+ static ReplaceEntry = 4;
+ static async waitForHistory(history, event) {
+ return new Promise(resolve => {
+ let listener = {
+ OnHistoryNewEntry: () => {},
+ OnHistoryReload: () => {
+ return true;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ function finish() {
+ history.removeSHistoryListener(listener);
+ resolve();
+ }
+ switch (event) {
+ case this.NewEntry:
+ listener.OnHistoryNewEntry = finish;
+ break;
+ case this.Reload:
+ listener.OnHistoryReload = () => {
+ finish();
+ return true;
+ };
+ break;
+ case this.GotoIndex:
+ listener.OnHistoryGotoIndex = finish;
+ break;
+ case this.Purge:
+ listener.OnHistoryPurge = finish;
+ break;
+ case this.ReplaceEntry:
+ listener.OnHistoryReplaceEntry = finish;
+ break;
+ }
+
+ history.addSHistoryListener(listener);
+ });
+ }
+}
diff --git a/docshell/test/browser/onload_message.html b/docshell/test/browser/onload_message.html
new file mode 100644
index 0000000000..6c69b710c7
--- /dev/null
+++ b/docshell/test/browser/onload_message.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ if (opener) {
+ addEventListener("load", function() {
+ opener.postMessage("load", "*");
+ });
+ }
+ </script>
+ </head>
+ <body>
+ This file posts a message containing "load" to opener on load completion.
+ </body>
+</html>
diff --git a/docshell/test/browser/onpageshow_message.html b/docshell/test/browser/onpageshow_message.html
new file mode 100644
index 0000000000..1a44a154ec
--- /dev/null
+++ b/docshell/test/browser/onpageshow_message.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ addEventListener("pageshow", function() {
+ opener.postMessage("pageshow", "*");
+ });
+ </script>
+ </head>
+ <body>
+ This file posts a message containing "pageshow" to opener on pageshow.
+ </body>
+</html>
diff --git a/docshell/test/browser/overlink_test.html b/docshell/test/browser/overlink_test.html
new file mode 100644
index 0000000000..5efd689311
--- /dev/null
+++ b/docshell/test/browser/overlink_test.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <head> <meta charset="utf-8"> </head>
+ <body>
+ <a id="link" href="https://user:password@example.com">Link</a>
+ </body>
+</html>
diff --git a/docshell/test/browser/print_postdata.sjs b/docshell/test/browser/print_postdata.sjs
new file mode 100644
index 0000000000..4175a24805
--- /dev/null
+++ b/docshell/test/browser/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/docshell/test/browser/redirect_to_example.sjs b/docshell/test/browser/redirect_to_example.sjs
new file mode 100644
index 0000000000..eef5e49f43
--- /dev/null
+++ b/docshell/test/browser/redirect_to_example.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 302, "Moved Permanently");
+ response.setHeader("Location", "http://example");
+}
diff --git a/docshell/test/browser/test-form_sjis.html b/docshell/test/browser/test-form_sjis.html
new file mode 100644
index 0000000000..91c375deef
--- /dev/null
+++ b/docshell/test/browser/test-form_sjis.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=windows-1251">
+ <title>Shift_JIS in body and text area</title>
+ </head>
+ <body>
+ <h1>Incorrect meta charset</h1>
+ <h2>This page is encoded in Shift_JIS, but has an incorrect meta charset
+ claiming that it is windows-1251</h2>
+ <p id="testpar">†jR[h‚ÍA‚·‚ׂĂ̕¶Žš‚ÉŚĹ—L‚̔ԍ†‚đ•t—^‚µ‚Ü‚·</p>
+ <form>
+ <p>
+ <textarea id="testtextarea" rows=6 cols=60>†jR[h‚ÍA‚·‚ׂĂ̕¶Žš‚ÉŚĹ—L‚̔ԍ†‚đ•t—^‚µ‚Ü‚·</textarea>
+ <input id="testinput" type="text" size=60 value="†jR[h‚ÍA‚·‚ׂĂ̕¶Žš‚ÉŚĹ—L‚̔ԍ†‚đ•t—^‚µ‚Ü‚·">
+ </p>
+ </form>
+ <h2>Expected text on load:</h2>
+ <p>&#x453;&#x2020;&#x453;&#x6A;&#x453;&#x52;&#x403;&#x5B;&#x453;&#x68;&#x201A;&#x41D;&#x403;&#x41;&#x201A;&#xB7;&#x201A;&#x427;&#x201A;&#x414;&#x201A;&#x41C;&#x2022;&#xB6;&#x40B;&#x459;&#x201A;&#x419;&#x40A;&#x415;&#x2014;&#x4C;&#x201A;&#x41C;&#x201D;&#x424;&#x40C;&#x2020;&#x201A;&#x440;&#x2022;&#x74;&#x2014;&#x5E;&#x201A;&#xB5;&#x201A;&#x42C;&#x201A;&#xB7;</p>
+ <h2>Expected text on resetting the encoding to Shift_JIS:</h2>
+ <p>&#x30E6;&#x30CB;&#x30B3;&#x30FC;&#x30C9;&#x306F;&#x3001;&#x3059;&#x3079;&#x3066;&#x306E;&#x6587;&#x5B57;&#x306B;&#x56FA;&#x6709;&#x306E;&#x756A;&#x53F7;&#x3092;&#x4ED8;&#x4E0E;&#x3057;&#x307E;&#x3059;</p>
+ </body>
+</html>
diff --git a/docshell/test/browser/timelineMarkers-04.html b/docshell/test/browser/timelineMarkers-04.html
new file mode 100644
index 0000000000..ff2f429d62
--- /dev/null
+++ b/docshell/test/browser/timelineMarkers-04.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8"></meta>
+ <title>markers test</title>
+</head>
+<body>
+
+ <p>Test page</p>
+
+ <script>
+ function do_xhr() {
+ const theURL = "timelineMarkers-04.html";
+
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ dump("ReadyState = " + xhr.readyState + "\n");
+ };
+ xhr.open("get", theURL, true);
+ xhr.send();
+ }
+
+ window.addEventListener("dog", do_xhr, true);
+
+ function do_promise() {
+ new Promise(function(resolve, reject) {
+ console.time("Bob");
+ window.setTimeout(function() {
+ resolve(23);
+ }, 10);
+ }).then(function(val) {
+ console.timeEnd("Bob");
+ });
+ }
+
+ window.addEventListener("promisetest", do_promise, true);
+
+ var globalResolver;
+ function do_promise_script() {
+ new Promise(function(resolve, reject) {
+ console.time("Bob");
+ globalResolver = resolve;
+ // eslint-disable-next-line no-implied-eval
+ window.setTimeout("globalResolver(23)", 10);
+ }).then(function(val) {
+ console.timeEnd("Bob");
+ });
+ }
+
+ window.addEventListener("promisescript", do_promise_script, true);
+
+ </script>
+
+</body>
+</html>
+
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 @@
+<html>
+<head>
+<title>test1</title>
+</head>
+<body>
+<p>
+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.
+</p>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>test1</title>
+</head>
+<body style="height: 100%">
+ <input type="text" id="inp" value="">
+ </input>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%; width: 300%">Some more text</div>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>test1</title>
+</head>
+<body style="height: 100%">
+ <input type="text" id="inp" value="">
+ </input>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%; width: 350%">Some more text</div>
+</body>
+</html>
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 @@
+<?xml-stylesheet type="text/xsl" href="582176_xslt.xsl"?>
+<out/>
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 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="out">
+ <html>
+ <head><title>XSLT result doc</title></head>
+ <body><p>xslt result</p></body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
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 @@
+<html>
+ <head>
+ <title>A</title>
+ </head>
+ <body>
+ <a id="link" href="662200b.html">Next</a>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>B</title>
+ </head>
+ <body>
+ <a id="link" href="662200c.html">Next</a>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>C</title>
+ </head>
+ <body>
+ </body>
+</html>
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 @@
+<html>
+<head>
+<title>Bug 89419</title>
+</head>
+<body>
+<img src="http://mochi.test:8888/tests/docshell/test/chrome/bug89419.sjs">
+</body>
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 @@
+<html>
+<head>
+<title>test1</title>
+</head>
+<body>
+<p>
+This document will be sent with a no-store cache-control header. It should not be stored in bfcache.
+</p>
+</body>
+</html>
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/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
--- /dev/null
+++ b/docshell/test/chrome/blue.png
Binary files differ
diff --git a/docshell/test/chrome/bug112564_window.xhtml b/docshell/test/chrome/bug112564_window.xhtml
new file mode 100644
index 0000000000..21bb4916bc
--- /dev/null
+++ b/docshell/test/chrome/bug112564_window.xhtml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="112564Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="112564 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"];
+
+ var gBrowser;
+ var gTestsIterator;
+ var gExpected = [];
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+
+ function finish() {
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.removeEventListener(eventType, eventListener, true);
+ }
+
+ // Work around bug 467960
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let history = gBrowser.browsingContext.sessionHistory;
+ history.purgeHistory(history.count);
+ } else {
+ let history = gBrowser.webNavigation.sessionHistory;
+ history.legacySHistory.purgeHistory(history.count);
+ }
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ function onLoad() {
+ gBrowser = document.getElementById("content");
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.addEventListener(eventType, eventListener, true);
+ }
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ ok(gExpected.length >= 1, "Unexpected event " + event.type);
+ if (gExpected.length == 0) {
+ // in case of unexpected event, try to continue anyway
+ setTimeout(nextTest, 0);
+ return;
+ }
+
+ var exp = gExpected.shift();
+ is(event.type, exp.type, "Invalid event received");
+ if (typeof(exp.persisted) != "undefined") {
+ is(event.persisted, exp.persisted, "Invalid persisted state");
+ }
+ if (exp.title) {
+ ok(event.originalTarget instanceof HTMLDocument,
+ "originalTarget not a HTMLDocument");
+ is(event.originalTarget.title, exp.title, "titles don't match");
+ }
+
+ if (gExpected.length == 0) {
+ setTimeout(nextTest, 0);
+ }
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Load a secure page with a no-cache header, followed by a simple page.
+ // no-cache should not interfere with the bfcache in the way no-store
+ // does.
+ var test1DocURI = "https://example.com:443/tests/docshell/test/chrome/112564_nocache.html";
+
+ gExpected = [{type: "pagehide", persisted: true},
+ {type: "load", title: "test1"},
+ {type: "pageshow", title: "test1", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test1DocURI);
+ yield undefined;
+
+ var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ gExpected = [{type: "pagehide", title: "test1", persisted: true},
+ {type: "load", title: "test2"},
+ {type: "pageshow", title: "test2", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ // Now go back in history. First page has been cached.
+ // Check persisted property to confirm
+ gExpected = [{type: "pagehide", title: "test2", persisted: true},
+ {type: "pageshow", title: "test1", persisted: true}];
+ gBrowser.goBack();
+ yield undefined;
+
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug113934_window.xhtml b/docshell/test/chrome/bug113934_window.xhtml
new file mode 100644
index 0000000000..bb50b76c25
--- /dev/null
+++ b/docshell/test/chrome/bug113934_window.xhtml
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 113934" onload="doTheTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <vbox id="box1">
+ </vbox>
+ <vbox id="box2">
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "snapshotWindow",
+ "compareSnapshots", "onerror" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function addBrowser(parent, id, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ var type = window.location.search.slice(1);
+ is(type == "chrome" || type == "content", true, "Unexpected type");
+ b.setAttribute("type", type);
+ b.setAttribute("id", id);
+ b.setAttribute("width", width);
+ b.setAttribute("height", height);
+ $(parent).appendChild(b);
+ }
+ addBrowser("box1", "f1", 300, 200);
+ addBrowser("box1", "f2", 300, 200);
+ addBrowser("box2", "f3", 30, 200);
+
+ /** Test for Bug 113934 **/
+ var doc1 =
+ "data:text/html,<html><body onbeforeunload='document.documentElement.textContent = \"\"' onunload='document.documentElement.textContent = \"\"' onpagehide='document.documentElement.textContent = \"\"'>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><head></head><body>This is a second test</body></html>";
+
+
+ $("f1").setAttribute("src", doc1);
+ $("f2").setAttribute("src", doc2);
+ $("f3").setAttribute("src", doc2);
+
+ async function doTheTest() {
+ var s1 = await snapshotWindow($("f1").contentWindow);
+ var s2 = await snapshotWindow($("f2").contentWindow);
+ var s3 = await snapshotWindow($("f3").contentWindow);
+
+ // This test is broken - see bug 1090274
+ //ok(!compareSnapshots(s2, s3, true)[0],
+ // "Should look different due to different sizing");
+
+ function getDOM(id) {
+ return $(id).contentDocument.documentElement.innerHTML;
+ }
+
+ var dom1 = getDOM("f1");
+
+ var dom2 = getDOM("f2");
+ $("f2").contentDocument.body.textContent = "Modified the text";
+ var dom2star = getDOM("f2");
+ isnot(dom2, dom2star, "We changed the DOM!");
+
+ $("f1").swapDocShells($("f2"));
+ // now we have doms 2*, 1, 2 in the frames
+
+ is(getDOM("f1"), dom2star, "Shouldn't have changed the DOM on swap");
+ is(getDOM("f2"), dom1, "Shouldn't have fired event handlers");
+
+ // Test for bug 480149
+ // The DOMLink* events are dispatched asynchronously, thus I cannot
+ // just include the <link> element in the initial DOM and swap the
+ // docshells. Instead, the link element is added now. Then, when the
+ // first DOMLinkAdded event (which is a result of the actual addition)
+ // is dispatched, the docshells are swapped and the pageshow and pagehide
+ // events are tested. Only then, we wait for the DOMLink* events,
+ // which are a result of swapping the docshells.
+ var DOMLinkListener = {
+ _afterFirst: false,
+ _removedDispatched: false,
+ _addedDispatched: false,
+ handleEvent: async function(aEvent) {
+ if (!this._afterFirst) {
+ is(aEvent.type, "DOMLinkAdded");
+
+ var strs = { "f1": "", "f3" : "" };
+ 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, false);
+
+ listener.detach = function() {
+ node.removeEventListener("page" + type, listener, false);
+ }
+ return listener;
+ }
+
+ var l1 = attachListener($("f1"), "show");
+ var l2 = attachListener($("f1"), "hide");
+ var l3 = attachListener($("f3"), "show");
+ var l4 = attachListener($("f3"), "hide");
+
+ $("f1").swapDocShells($("f3"));
+ // now we have DOMs 2, 1, 2* in the frames
+
+ l1.detach();
+ l2.detach();
+ l3.detach();
+ l4.detach();
+
+ // swapDocShells reflows asynchronously, ensure layout is
+ // clean so that the viewport of f1 is the right size.
+ $("f1").getBoundingClientRect();
+ var s1_new = await snapshotWindow($("f1").contentWindow);
+ var [same, first, second] = compareSnapshots(s1_new, s2, true);
+ ok(same, "Should reflow on swap. Expected " + second + " but got " + first);
+
+ is(strs["f1"], "f1.pagehide f1.pageshow");
+ is(strs["f3"], "f3.pagehide f3.pageshow");
+ this._afterFirst = true;
+ return;
+ }
+ if (aEvent.type == "DOMLinkAdded") {
+ is(this._addedDispatched, false);
+ this._addedDispatched = true;
+ }
+ else {
+ is(this._removedDispatched, false);
+ this._removedDispatched = true;
+ }
+
+ if (this._addedDispatched && this._removedDispatched) {
+ $("f1").removeEventListener("DOMLinkAdded", this, false);
+ $("f1").removeEventListener("DOMLinkRemoved", this, false);
+ $("f3").removeEventListener("DOMLinkAdded", this, false);
+ $("f3").removeEventListener("DOMLinkRemoved", this, false);
+ window.close();
+ SimpleTest.finish();
+ }
+ }
+ };
+
+ $("f1").addEventListener("DOMLinkAdded", DOMLinkListener, false);
+ $("f1").addEventListener("DOMLinkRemoved", DOMLinkListener, false);
+ $("f3").addEventListener("DOMLinkAdded", DOMLinkListener, false);
+ $("f3").addEventListener("DOMLinkRemoved", DOMLinkListener, false);
+
+ var linkElement = $("f1").contentDocument.createElement("link");
+ linkElement.setAttribute("rel", "alternate");
+ linkElement.setAttribute("href", "about:blank");
+ $("f1").contentDocument.documentElement.firstChild.appendChild(linkElement);
+ }
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/bug215405_window.xhtml b/docshell/test/chrome/bug215405_window.xhtml
new file mode 100644
index 0000000000..94b6704b2c
--- /dev/null
+++ b/docshell/test/chrome/bug215405_window.xhtml
@@ -0,0 +1,170 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="215405Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="215405 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ var imports = [ "SimpleTest", "is", "isnot", "ok"];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ const text="MOZILLA";
+ const nostoreURI = "http://mochi.test:8888/tests/docshell/test/chrome/" +
+ "215405_nostore.html";
+ const nocacheURI = "https://example.com:443/tests/docshell/test/chrome/" +
+ "215405_nocache.html";
+
+ var gBrowser;
+ var gTestsIterator;
+ var scrollX = 0;
+ var scrollY = 0;
+
+ function finish() {
+ gBrowser.removeEventListener("pageshow", eventListener, true);
+ // Work around bug 467960
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ var history = gBrowser.browsingContext.sessionHistory;
+ history.purgeHistory(history.count);
+ } else {
+ var history = gBrowser.webNavigation.sessionHistory;
+ history.legacySHistory.purgeHistory(history.count);
+ }
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ function onLoad(e) {
+ gBrowser = document.getElementById("content");
+ gBrowser.addEventListener("pageshow", eventListener, true);
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ setTimeout(nextTest, 0);
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // No-store tests
+ var testName = "[nostore]";
+
+ // Load a page with a no-store header
+ BrowserTestUtils.loadURI(gBrowser, nostoreURI);
+ yield undefined;
+
+
+ // Now that the page has loaded, amend the form contents
+ var form = gBrowser.contentDocument.getElementById("inp");
+ form.value = text;
+
+ // Attempt to scroll the page
+ var originalXPosition = gBrowser.contentWindow.scrollX;
+ var originalYPosition = gBrowser.contentWindow.scrollY;
+ var scrollToX = gBrowser.contentWindow.scrollMaxX;
+ var scrollToY = gBrowser.contentWindow.scrollMaxY;
+ gBrowser.contentWindow.scrollBy(scrollToX, scrollToY);
+
+ // Save the scroll position for future comparison
+ scrollX = gBrowser.contentWindow.scrollX;
+ scrollY = gBrowser.contentWindow.scrollY;
+ isnot(scrollX, originalXPosition,
+ testName + " failed to scroll window horizontally");
+ isnot(scrollY, originalYPosition,
+ testName + " failed to scroll window vertically");
+
+ // Load a new document into the browser
+ var simple = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+ BrowserTestUtils.loadURI(gBrowser, simple);
+ yield undefined;
+
+
+ // Now go back in history. First page should not have been cached.
+ gBrowser.goBack();
+ yield undefined;
+
+
+ // First uncacheable page will now be reloaded. Check scroll position
+ // restored, and form contents not
+ is(gBrowser.contentWindow.scrollX, scrollX, testName +
+ " horizontal axis scroll position not correctly restored");
+ is(gBrowser.contentWindow.scrollY, scrollY, testName +
+ " vertical axis scroll position not correctly restored");
+ var formValue = gBrowser.contentDocument.getElementById("inp").value;
+ isnot(formValue, text, testName + " form value incorrectly restored");
+
+
+ // https no-cache
+ testName = "[nocache]";
+
+ // Load a page with a no-cache header. This should not be
+ // restricted like no-store (bug 567365)
+ BrowserTestUtils.loadURI(gBrowser, nocacheURI);
+ yield undefined;
+
+
+ // Now that the page has loaded, amend the form contents
+ form = gBrowser.contentDocument.getElementById("inp");
+ form.value = text;
+
+ // Attempt to scroll the page
+ originalXPosition = gBrowser.contentWindow.scrollX;
+ originalYPosition = gBrowser.contentWindow.scrollY;
+ scrollToX = gBrowser.contentWindow.scrollMaxX;
+ scrollToY = gBrowser.contentWindow.scrollMaxY;
+ gBrowser.contentWindow.scrollBy(scrollToX, scrollToY);
+
+ // Save the scroll position for future comparison
+ scrollX = gBrowser.contentWindow.scrollX;
+ scrollY = gBrowser.contentWindow.scrollY;
+ isnot(scrollX, originalXPosition,
+ testName + " failed to scroll window horizontally");
+ isnot(scrollY, originalYPosition,
+ testName + " failed to scroll window vertically");
+
+ BrowserTestUtils.loadURI(gBrowser, simple);
+ yield undefined;
+
+
+ // Now go back in history to the cached page.
+ gBrowser.goBack();
+ yield undefined;
+
+
+ // First page will now be reloaded. Check scroll position
+ // and form contents are restored
+ is(gBrowser.contentWindow.scrollX, scrollX, testName +
+ " horizontal axis scroll position not correctly restored");
+ is(gBrowser.contentWindow.scrollY, scrollY, testName +
+ " vertical axis scroll position not correctly restored");
+ var formValue = gBrowser.contentDocument.getElementById("inp").value;
+ is(formValue, text, testName + " form value not correctly restored");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
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 @@
+<html>
+ <head>
+ <title>Bug 293235 page1</title>
+ <style type="text/css">
+ a:visited, a.forcevisited.forcevisited { color: rgb(128, 0, 128); }
+ a:link, a.forcelink.forcelink { color: rgb(0, 0, 128); }
+ a:focus { color: rgb(128, 0, 0); }
+ </style>
+ </head>
+ <body>
+ <a id="link1" href="bug293235_p2.html">This is a test link.</a>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>Bug 293235 page2</title>
+ </head>
+ <body>
+ Nothing to see here, move along.
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug293235_window.xhtml b/docshell/test/chrome/bug293235_window.xhtml
new file mode 100644
index 0000000000..4c84c4070d
--- /dev/null
+++ b/docshell/test/chrome/bug293235_window.xhtml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="293235Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTests, 0);"
+ title="bug 293235 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+ // Return the Element object for the specified element id
+ function $(id) { return TestWindow.getDocument().getElementById(id); }
+
+ ////
+ // Generator function for test steps for bug 293235:
+ // A visited link should have the :visited style applied
+ // to it when displayed on a page which was fetched from
+ // the bfcache.
+ //
+ async function runTests() {
+ // Register our observer to know when the link lookup is complete.
+ let testURI = NetUtil.newURI(getHttpUrl("bug293235_p2.html"));
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ // Load a test page containing a link that should be initially
+ // blue, per the :link style.
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: getHttpUrl("bug293235.html"),
+ onNavComplete: resolve,
+ });
+ });
+
+ // Now that we've been notified, we can check our link color.
+ // Since we can't use getComputedStyle() for this because
+ // getComputedStyle lies about styles that result from :visited,
+ // we have to take snapshots.
+ // First, take two reference snapshots.
+ var link1 = $("link1");
+ link1.className = "forcelink";
+ var refLink = await snapshotWindow(TestWindow.getWindow());
+ link1.className = "forcevisited";
+ var refVisited = await snapshotWindow(TestWindow.getWindow());
+ link1.className = "";
+ function snapshotsEqual(snap1, snap2) {
+ return compareSnapshots(snap1, snap2, true)[0];
+ }
+ ok(!snapshotsEqual(refLink, refVisited), "references should not match");
+ ok(snapshotsEqual(refLink, await snapshotWindow(TestWindow.getWindow())),
+ "link should initially be blue");
+
+ let observedVisit = false, observedPageShow = false;
+ await new Promise(resolve => {
+ function maybeResolve() {
+ ok(true, "maybe run next test? visited: " + observedVisit + " pageShow: " + observedPageShow);
+ if (observedVisit && observedPageShow)
+ resolve();
+ }
+
+ // Because adding visits is async, we will not be notified immediately.
+ let visitObserver = {
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (!testURI.equals(aSubject.QueryInterface(Ci.nsIURI))) {
+ return;
+ }
+ os.removeObserver(this, aTopic);
+ observedVisit = true;
+ maybeResolve();
+ },
+ };
+ os.addObserver(visitObserver, "uri-visit-saved");
+ // Load the page that the link on the previous page points to.
+ doPageNavigation({
+ uri: getHttpUrl("bug293235_p2.html"),
+ onNavComplete: function() {
+ observedPageShow = true;
+ maybeResolve();
+ }
+ });
+ })
+
+ // And the nodes get notified after the "uri-visit-saved" topic, so
+ // we need to execute soon...
+ await new Promise(SimpleTest.executeSoon);
+
+ // Go back, verify the original page was loaded from the bfcache,
+ // and verify that the link is now purple, per the
+ // :visited style.
+ await new Promise(resolve => {
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ { type: "pageshow",
+ persisted: true,
+ title: "Bug 293235 page1" } ],
+ onNavComplete: resolve,
+ });
+ })
+
+ // Now we can test the link color.
+ ok(snapshotsEqual(refVisited, await snapshotWindow(TestWindow.getWindow())),
+ "visited link should be purple");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Bug 294258 Testcase</title>
+ <meta http-equiv="Content-Type" content="application/xhtml+xml"/>
+ <style type="text/css">
+ * {
+ font-family: monospace;
+ }
+ </style>
+ </head>
+ <body>
+ <div>
+ <p>
+ input type="text": <input id="text" type="text"/>
+ </p>
+ <p>
+ input type="checkbox": <input id="checkbox" type="checkbox"/>
+ </p>
+ <p>
+ input type="file": <input id="file" type="file"/>
+ </p>
+ <p>
+ input type="radio":
+ <input type="radio" id="radio1" name="radio" value="radio1"/>
+ <input id="radio2" type="radio" name="radio" value="radio2"/>
+ </p>
+ <p>
+ textarea: <textarea id="textarea" rows="4" cols="80"></textarea>
+ </p>
+ <p>
+ select -> option: <select id="select">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ </select>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug294258_window.xhtml b/docshell/test/chrome/bug294258_window.xhtml
new file mode 100644
index 0000000000..c282511c01
--- /dev/null
+++ b/docshell/test/chrome/bug294258_window.xhtml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="294258Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 294258 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ function $(id) { return TestWindow.getDocument().getElementById(id); }
+
+ ////
+ // Generator function for test steps for bug 294258:
+ // Form values should be preserved on reload.
+ //
+ function* testIterator()
+ {
+ // Load a page containing a form.
+ doPageNavigation( {
+ uri: getHttpUrl("bug294258_testcase.html"),
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Change the data for each of the form fields, and reload.
+ $("text").value = "text value";
+ $("checkbox").checked = true;
+ var file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ file.append("294258_test.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+ filePath = file.path;
+ $("file").value = filePath;
+ $("textarea").value = "textarea value";
+ $("radio1").checked = true;
+ $("select").selectedIndex = 2;
+ doPageNavigation( {
+ reload: true,
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Verify that none of the form data has changed.
+ is($("text").value, "text value", "Text value changed");
+ is($("checkbox").checked, true, "Checkbox value changed");
+ is($("file").value, filePath, "File value changed");
+ is($("textarea").value, "textarea value", "Textarea value changed");
+ is($("radio1").checked, true, "Radio value changed");
+ is($("select").selectedIndex, 2, "Select value changed");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug298622_window.xhtml b/docshell/test/chrome/bug298622_window.xhtml
new file mode 100644
index 0000000000..e02ecc1f2d
--- /dev/null
+++ b/docshell/test/chrome/bug298622_window.xhtml
@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="298622Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 298622 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src= "docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ // Global variable that holds a reference to the find bar.
+ var gFindBar;
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 298622:
+ // Find should work correctly on a page loaded from the
+ // bfcache.
+ //
+ function* testIterator()
+ {
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load a test page which contains some text to be found.
+ doPageNavigation({
+ uri: "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>find this!</body></html>",
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Load a second, dummy page, verifying that the original
+ // page gets stored in the bfcache.
+ doPageNavigation({
+ uri: getHttpUrl("generic.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test1",
+ persisted: true },
+ { type: "pageshow",
+ title: "generic page" } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Make sure we unsuppress painting before continuing
+ SimpleTest.executeSoon(nextTest);
+ yield undefined;
+
+ // Search for some text that's on the second page (but not on
+ // the first page), and verify that it can be found.
+ gFindBar = document.getElementById("FindToolbar");
+ document.getElementById("cmd_find").doCommand();
+ ok(!gFindBar.hidden, "failed to open findbar");
+ gFindBar._findField.value = "A generic page";
+ gFindBar._find();
+ SimpleTest.executeSoon(nextTest);
+ yield undefined;
+
+ // Make sure Find bar's internal status is not 'notfound'
+ isnot(gFindBar._findField.getAttribute("status"), "notfound",
+ "Findfield status attribute should not have been 'notfound'" +
+ " after Find");
+
+ // Make sure the key events above have time to be processed
+ // before continuing
+ waitForTrue(function() {
+ return (
+ TestWindow.getWindow().getSelection().toString().toLowerCase() ==
+ "a generic page");
+ }, nextTest, 20);
+ yield undefined;
+
+ is(gFindBar._findField.value, "A generic page",
+ "expected text not present in find input field");
+ is(TestWindow.getWindow().getSelection().toString().toLowerCase(),
+ "a generic page",
+ "find failed on second page loaded");
+
+ // Go back to the original page and verify it's loaded from the
+ // bfcache.
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ { type: "pageshow",
+ title: "test1",
+ persisted: true } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Search for some text that's on the original page (but not
+ // the dummy page loaded above), and verify that it can
+ // be found.
+ gFindBar = document.getElementById("FindToolbar");
+ document.getElementById("cmd_find").doCommand();
+ ok(!gFindBar.hidden, "failed to open findbar");
+ gFindBar._findField.value = "find this";
+ gFindBar._find();
+ SimpleTest.executeSoon(nextTest);
+ yield undefined;
+
+ // Make sure Find bar's internal status is not 'notfound'
+ isnot(gFindBar._findField.getAttribute("status"), "notfound",
+ "Findfield status attribute should not have been 'notfound'" +
+ " after Find");
+
+ // Make sure the key events above have time to be processed
+ // before continuing
+ waitForTrue(function() {
+ return (
+ TestWindow.getWindow().getSelection().toString().toLowerCase() ==
+ "find this");
+ }, nextTest, 20);
+ yield undefined;
+
+ is(TestWindow.getWindow().getSelection().toString().toLowerCase(),
+ "find this",
+ "find failed on page loaded from bfcache");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <commandset>
+ <command id="cmd_find"
+ oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
+ </commandset>
+ <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/>
+ <findbar id="FindToolbar" browserid="content"/>
+</window>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe parent</title>
+ </head>
+<body>
+ <iframe id="iframe" src="bug301397_2.html"/>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_2.html b/docshell/test/chrome/bug301397_2.html
new file mode 100644
index 0000000000..4237107060
--- /dev/null
+++ b/docshell/test/chrome/bug301397_2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe content #1</title>
+ </head>
+<body>
+ iframe page 1<br/>
+ <a id="link" href="bug301397_3.html">go to next page</a>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_3.html b/docshell/test/chrome/bug301397_3.html
new file mode 100644
index 0000000000..8d36e92461
--- /dev/null
+++ b/docshell/test/chrome/bug301397_3.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe content #2</title>
+ </head>
+<body>
+ iframe page 2<br/>
+ You made it!
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_4.html b/docshell/test/chrome/bug301397_4.html
new file mode 100644
index 0000000000..5584a4554a
--- /dev/null
+++ b/docshell/test/chrome/bug301397_4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>dummy page, no iframe</title>
+ </head>
+<body>
+ Just a boring test page, nothing special.
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_window.xhtml b/docshell/test/chrome/bug301397_window.xhtml
new file mode 100644
index 0000000000..59cbe783ae
--- /dev/null
+++ b/docshell/test/chrome/bug301397_window.xhtml
@@ -0,0 +1,250 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="301397Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 301397 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Return the document element with the specified id.
+ //
+ function $(id) { return TestWindow.getDocument().getElementById(id); }
+
+ ////
+ // Verifies that the given string exists in the innerHTML of the iframe
+ // content.
+ //
+ function verifyIframeInnerHtml(string) {
+ var iframeInnerHtml = $("iframe").contentDocument.body.innerHTML;
+ ok(iframeInnerHtml.includes(string),
+ "iframe contains wrong document: " + iframeInnerHtml);
+ }
+
+ ////
+ // Generator function for test steps for bug 301397:
+ // The correct page should be displayed in an iframe when
+ // navigating back and forwards, when the parent page
+ // occupies multiple spots in the session history.
+ //
+ function* testIterator()
+ {
+ // Make sure the bfcache is enabled.
+ enableBFCache(8);
+
+ // Load a dummy page.
+ doPageNavigation({
+ uri: getHttpUrl("generic.html"),
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Load a page containing an iframe.
+ doPageNavigation({
+ uri: getHttpUrl("bug301397_1.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "generic page",
+ persisted: true },
+ { type: "pageshow",
+ title: "iframe content #1",
+ persisted: false }, // false on initial load
+ { type: "pageshow",
+ title: "iframe parent",
+ persisted: false } ], // false on initial load
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Click a link in the iframe to cause the iframe to navigate
+ // to a new page, and wait until the related pagehide/pageshow
+ // events have occurred.
+ waitForPageEvents({
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #2",
+ persisted: false } ], // false on initial load
+ onNavComplete: nextTest
+ });
+ var link = $("iframe").contentDocument.getElementById("link");
+ var event = $("iframe").contentDocument.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, $("iframe").contentWindow,
+ 0, 0, 0, 0, 0,
+ false, false, false, false,
+ 0, null);
+ link.dispatchEvent(event);
+ yield undefined;
+
+ // Load another dummy page. Verify that both the outgoing parent and
+ // iframe pages are stored in the bfcache.
+ doPageNavigation({
+ uri: getHttpUrl("bug301397_4.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: true },
+ { type: "pageshow",
+ title: "dummy page, no iframe",
+ persisted: false } ], // false on initial load
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Go back. The iframe should show the second page loaded in it.
+ // Both the parent and the iframe pages should be loaded from
+ // the bfcache.
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "dummy page, no iframe",
+ persisted: true },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe content #2" },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe parent" } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ verifyIframeInnerHtml("You made it");
+
+ // Go gack again. The iframe should show the first page loaded in it.
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #1",
+ // false since this page was never stored
+ // in the bfcache in the first place
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ verifyIframeInnerHtml("go to next page");
+
+ // Go back to the generic page. Now go forward to the last page,
+ // again verifying that the iframe shows the first and second
+ // pages in order.
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: true },
+ { type: "pageshow",
+ title: "generic page",
+ persisted: true } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow",
+ title: "iframe content #1",
+ persisted: true},
+ {type: "pageshow",
+ title: "iframe parent",
+ persisted: true} ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ verifyIframeInnerHtml("go to next page");
+
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #2",
+ // false because the page wasn't stored in
+ // bfcache last time it was unloaded
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ verifyIframeInnerHtml("You made it");
+
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: true },
+ { type: "pageshow",
+ title: "dummy page, no iframe",
+ persisted: true } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Go back once more, and again verify that the iframe shows the
+ // second page loaded in it.
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "dummy page, no iframe",
+ persisted: true },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe content #2" },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe parent" } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ verifyIframeInnerHtml("You made it");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug303267.html b/docshell/test/chrome/bug303267.html
new file mode 100644
index 0000000000..21b9f30311
--- /dev/null
+++ b/docshell/test/chrome/bug303267.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <title>
+ bug303267.html
+ </title>
+ </head>
+<body onpageshow="showpageshowcount()">
+<script>
+var pageshowcount = 0;
+function showpageshowcount() {
+ pageshowcount++;
+ var div1 = document.getElementById("div1");
+ while (div1.firstChild) {
+ div1.firstChild.remove();
+ }
+ div1.appendChild(document.createTextNode(
+ "pageshowcount: " + pageshowcount));
+}
+</script>
+<div id="div1">
+ </div>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug303267_window.xhtml b/docshell/test/chrome/bug303267_window.xhtml
new file mode 100644
index 0000000000..b7da0a4397
--- /dev/null
+++ b/docshell/test/chrome/bug303267_window.xhtml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 303267 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 303267: When a page is
+ // displayed from the bfcache, the script globals should
+ // remain intact from the page's initial load.
+ //
+ function* testIterator()
+ {
+ // Load an initial test page which should be saved in the bfcache.
+ var navData = {
+ uri: getHttpUrl("bug303267.html"),
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: "bug303267.html"} ],
+ onNavComplete: nextTest
+ };
+ doPageNavigation(navData);
+ yield undefined;
+
+ // Save the HTML of the test page for later comparison.
+ var originalHTML = getInnerHTMLById("div1");
+
+ // Load a second test page. The first test page's pagehide event should
+ // have the .persisted property set to true, indicating that it was
+ // stored in the bfcache.
+ navData = {
+ uri: "data:text/html,<html><head><title>page2</title></head>" +
+ "<body>bug303267, page2</body></html>",
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "bug303267.html",
+ persisted: true},
+ {type: "pageshow",
+ title: "page2"} ],
+ onNavComplete: nextTest
+ };
+ doPageNavigation(navData);
+ yield undefined;
+
+ // Go back. Verify that the pageshow event for the original test page
+ // had a .persisted property of true, indicating that it came from the
+ // bfcache.
+ navData = {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "page2"},
+ {type: "pageshow",
+ title: "bug303267.html",
+ persisted: true} ],
+ onNavComplete: nextTest
+ };
+ doPageNavigation(navData);
+ yield undefined;
+
+ // After going back, if showpagecount() could access a global variable
+ // and change the test div's innerHTML, then we pass. Otherwise, it
+ // threw an exception and the following test will fail.
+ var newHTML = getInnerHTMLById("div1");
+ isnot(originalHTML,
+ newHTML, "HTML not updated on pageshow; javascript broken?");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ////
+ // Return the innerHTML of a particular element in the content document.
+ //
+ function getInnerHTMLById(id) {
+ return TestWindow.getDocument().getElementById(id).innerHTML;
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug311007_window.xhtml b/docshell/test/chrome/bug311007_window.xhtml
new file mode 100644
index 0000000000..30aa536bcb
--- /dev/null
+++ b/docshell/test/chrome/bug311007_window.xhtml
@@ -0,0 +1,204 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="311007Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="startup();"
+ title="bug 311007 test">
+
+ <script type="application/javascript" src="docshell_helpers.js"></script>
+ <script type="application/javascript"><![CDATA[
+/*
+ Regression test for bug 283733 and bug 307027.
+
+ Bug 283733
+ "accessing a relative anchor in a secure page removes the
+ locked icon and yellow background UI"
+
+ Bug 307027
+ "Going back from secure page to error page does not clear yellow bar"
+
+ And enhancements:
+
+ Bug 478927
+ onLocationChange should notify whether or not loading an error page.
+
+ */
+
+const kDNSErrorURI = "https://example/err.html";
+const kSecureURI =
+ "https://example.com/tests/docshell/test/navigation/blank.html";
+
+/*
+ Step 1: load a network error page. <err.html> Not Secure
+ Step 2: load a secure page. <blank.html> Secure
+ Step 3: a secure page + hashchange. <blank.html#foo> Secure (bug 283733)
+ Step 4: go back to the error page. <err.html> Not Secure (bug 307027)
+ */
+
+var gListener = null;
+
+function WebProgressListener() {
+ this._callback = null;
+}
+
+WebProgressListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Components.Exception("", Cr.NS_NOINTERFACE);
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ setTimeout(this._callback, 0, aWebProgress, aRequest, aLocation, aFlags);
+ },
+
+ set callback(aVal) {
+ this._callback = aVal;
+ }
+};
+
+function startup() {
+ gListener = new WebProgressListener();
+
+ document.getElementById("content")
+ .webProgress
+ .addProgressListener(gListener,
+ Ci.nsIWebProgress
+ .NOTIFY_LOCATION);
+
+ setTimeout(step1A, 0);
+}
+
+/******************************************************************************
+ * Step 1: Load an error page, and confirm UA knows it's insecure.
+ ******************************************************************************/
+
+function step1A() {
+ gListener.callback = step1B;
+ content.location = kDNSErrorURI;
+}
+
+function step1B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kDNSErrorURI, "Error page's URI (1)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (1)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is an error page.");
+
+ ok(!(document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is not a secure page (1)");
+
+ /* Go to step 2. */
+ setTimeout(step2A, 0);
+}
+
+/******************************************************************************
+ * Step 2: Load a HTTPS page, and confirm it's secure.
+ ******************************************************************************/
+
+function step2A() {
+ gListener.callback = step2B;
+ content.location = kSecureURI;
+}
+
+function step2B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kSecureURI, "A URI on HTTPS (2)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (2)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is not an error page.");
+
+ ok((document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is a secure page (2)");
+
+ /* Go to step 3. */
+ setTimeout(step3A, 0);
+}
+
+/*****************************************************************************
+ * Step 3: Trigger hashchange within a secure page, and confirm UA knows
+ * it's secure. (Bug 283733)
+ *****************************************************************************/
+
+function step3A() {
+ gListener.callback = step3B;
+ content.location += "#foo";
+}
+
+function step3B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kSecureURI + "#foo", "hashchange on HTTPS (3)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "We are in the same document as before (3)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is not an error page.");
+
+ ok((document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is a secure page (3)");
+
+ /* Go to step 4. */
+ setTimeout(step4A, 0);
+}
+
+/*****************************************************************************
+ * Step 4: Go back from a secure page to an error page, and confirm UA knows
+ * it's not secure. (Bug 307027)
+ *****************************************************************************/
+
+function step4A() {
+ gListener.callback = step4B;
+ content.history.go(-2);
+}
+
+function step4B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kDNSErrorURI, "Go back to the error URI (4)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (4)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is an error page.");
+
+ ok(!(document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is not a secure page (4)");
+
+ /* End. */
+ document.getElementById("content")
+ .webProgress.removeProgressListener(gListener);
+ delete(gListener);
+ finish();
+}
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug321671_window.xhtml b/docshell/test/chrome/bug321671_window.xhtml
new file mode 100644
index 0000000000..4ebb268a02
--- /dev/null
+++ b/docshell/test/chrome/bug321671_window.xhtml
@@ -0,0 +1,133 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="321671Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 321671 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ // Maximum number of entries in the bfcache for this session history.
+ // This number is hardcoded in docshell code. In the test, we'll
+ // navigate through enough pages so that we hit one that's been
+ // evicted from the bfcache because it's farther from the current
+ // page than this number.
+ const MAX_BFCACHE_PAGES = 3;
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 321671: Scroll position
+ // should be retained when moving backwards and forwards through pages
+ // when bfcache is enabled.
+ //
+ function* testIterator()
+ {
+ // Variable to hold the scroll positions of the test pages.
+ var scrollPositions = [];
+
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load enough test pages that so the first one is evicted from the
+ // bfcache, scroll down on each page, and save the
+ // current scroll position before continuing. Verify that each
+ // page we're navigating away from is initially put into the bfcache.
+ for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ doPageNavigation( {
+ uri: "data:text/html,<html><head><title>bug321671 page" + (i + 1) +
+ "</title></head>" +
+ "<body><table border='1' width='300' height='1000'>" +
+ "<tbody><tr><td>" +
+ " page " + (i + 1) + ": foobar foobar foobar foobar " +
+ "</td></tr></tbody></table> " +
+ "</body></html>",
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ persisted: true,
+ title: i == 0 ?
+ undefined : "bug321671 page" + i },
+ { type: "pageshow",
+ title: "bug321671 page" + (i + 1) } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ is(TestWindow.getWindow().scrollY, 0,
+ "Page initially has non-zero scrollY position");
+ TestWindow.getWindow().scrollByLines(10 + (2*i));
+ ok(TestWindow.getWindow().scrollY > 0,
+ "Page has zero scrollY position after scrolling");
+ scrollPositions[i] = TestWindow.getWindow().scrollY;
+ }
+
+ // Go back to the first page, one page at a time. For each 'back'
+ // action, verify that its vertical scroll position is restored
+ // correctly. Verify that the last page in the sequence
+ // does not come from the bfcache. Again verify that all pages
+ // that we navigate away from are initially
+ // stored in the bfcache.
+ for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) {
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug321671 page" + (i+1),
+ persisted: true },
+ { type: "pageshow",
+ title: "bug321671 page" + i,
+ persisted: i > 1 } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ is(TestWindow.getWindow().scrollY, scrollPositions[i-1],
+ "Scroll position not restored while going back!");
+ }
+
+ // Traverse history forward now, and verify scroll position is still
+ // restored. Similar to the backward traversal, verify that all
+ // but the last page in the sequence comes from the bfcache. Also
+ // verify that all of the pages get stored in the bfcache when we
+ // navigate away from them.
+ for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ doPageNavigation( {
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ persisted: true,
+ title: "bug321671 page" + i },
+ { type: "pageshow",
+ persisted: i < MAX_BFCACHE_PAGES + 1,
+ title: "bug321671 page" + (i + 1) } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ is(TestWindow.getWindow().scrollY, scrollPositions[i],
+ "Scroll position not restored while going forward!");
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug360511_case1.html b/docshell/test/chrome/bug360511_case1.html
new file mode 100644
index 0000000000..cca043bb66
--- /dev/null
+++ b/docshell/test/chrome/bug360511_case1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>
+ bug360511 case 1
+ </title>
+ </head>
+<body style="height: 100%">
+<a id="link1" href="#bottom">jump to bottom</a>
+<div id="div1" style="height: 200%; border: thin solid black;">
+ hello large div
+ </div>
+ <a name="bottom">here's the bottom of the page</a>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug360511_case2.html b/docshell/test/chrome/bug360511_case2.html
new file mode 100644
index 0000000000..217f47724e
--- /dev/null
+++ b/docshell/test/chrome/bug360511_case2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>
+ bug360511 case 2
+ </title>
+ </head>
+<body style="height: 100%">
+<a id="link1" href="#bottom">jump to bottom</a>
+<div id="div1" style="height: 200%; border: thin solid black;">
+ hello large div
+ </div>
+ <a name="bottom">here's the bottom of the page</a>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug360511_window.xhtml b/docshell/test/chrome/bug360511_window.xhtml
new file mode 100644
index 0000000000..b28162ae2f
--- /dev/null
+++ b/docshell/test/chrome/bug360511_window.xhtml
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="360511Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 360511 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 360511:
+ // Fragment uri's in session history should be restored correctly
+ // upon back navigation.
+ //
+ function* testIterator()
+ {
+ // Case 1: load a page containing a fragment link; the page should be
+ // stored in the bfcache.
+ // Case 2: load a page containing a fragment link; the page should NOT
+ // be stored in the bfcache.
+ for (var i = 1; i < 3; i++)
+ {
+ var url = "bug360511_case" + i + ".html";
+ doPageNavigation( {
+ uri: getHttpUrl(url),
+ onNavComplete: nextTest,
+ preventBFCache: i != 1
+ } );
+ yield undefined;
+
+ // Store the original url for later comparison.
+ var originalUrl = TestWindow.getBrowser().currentURI.spec;
+ var originalDocLocation = TestWindow.getDocument().location.href;
+
+ // Verify we're at the top of the page.
+ is(TestWindow.getWindow().scrollY, 0,
+ "Page initially has a non-zero scrollY property");
+
+ // Click the on the fragment link in the browser, and use setTimeout
+ // to give the event a chance to be processed.
+ var event = TestWindow.getDocument().createEvent('MouseEvent');
+ event.initMouseEvent("click", true, true, TestWindow.getWindow(), 0,
+ 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ TestWindow.getDocument().getElementById("link1").dispatchEvent(event);
+ waitForNextPaint(nextTest);
+ yield undefined;
+
+ // Store the fragment url for later comparison.
+ var fragmentUrl = TestWindow.getBrowser().currentURI.spec;
+ var fragDocLocation = TestWindow.getDocument().location.href;
+
+ // Verify we're no longer at the top of the page.
+ ok(TestWindow.getWindow().scrollY > 0,
+ "We're at the top of the page but we should be at the bottom");
+
+ // Now navigate to any other page
+ var expectedPageTitle = "bug360511 case " + i;
+ doPageNavigation( {
+ uri: getHttpUrl("generic.html"),
+ eventsToListenFor: ["pagehide", "pageshow"],
+ expectedEvents: [ {type: "pagehide", title: expectedPageTitle,
+ persisted: i == 1},
+ {type: "pageshow"} ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Go back
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: expectedPageTitle,
+ persisted: i == 1} ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Verify the current url is the fragment url
+ is(TestWindow.getBrowser().currentURI.spec, fragmentUrl,
+ "current url is not the previous fragment url");
+ is(TestWindow.getDocument().location.href, fragDocLocation,
+ "document.location is not the previous fragment url");
+
+ // Go back again. Since we're just going from a fragment url to
+ // parent url, no pageshow event is fired, so don't wait for any
+ // events. Rather, just wait for the page's scrollY property to
+ // change.
+ var originalScrollY = TestWindow.getWindow().scrollY;
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: []
+ } );
+ waitForTrue(
+ function() {
+ return (TestWindow.getWindow().scrollY != originalScrollY);
+ },
+ function() {
+ setTimeout(nextTest, 0);
+ }, 20);
+ yield undefined;
+
+ // Verify the current url is the original url without fragment
+ is(TestWindow.getBrowser().currentURI.spec, originalUrl,
+ "current url is not the original url");
+ is(TestWindow.getDocument().location.href, originalDocLocation,
+ "document.location is not the original url");
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug364461_window.xhtml b/docshell/test/chrome/bug364461_window.xhtml
new file mode 100644
index 0000000000..2a9f2a42d1
--- /dev/null
+++ b/docshell/test/chrome/bug364461_window.xhtml
@@ -0,0 +1,272 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="364461Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="364461 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+ const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"];
+
+ var gBrowser;
+ var gTestsIterator;
+ var gExpected = [];
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+ function finish() {
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.removeEventListener(eventType, eventListener, true);
+ }
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ function onLoad() {
+ gBrowser = document.getElementById("content");
+
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.addEventListener(eventType, eventListener, true);
+ }
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ ok(gExpected.length >= 1, "Unexpected event " + event.type);
+ if (gExpected.length == 0) {
+ // in case of unexpected event, try to continue anyway
+ setTimeout(nextTest, 0);
+ return;
+ }
+
+ var exp = gExpected.shift();
+ is(event.type, exp.type, "Invalid event received");
+ if (typeof(exp.persisted) != "undefined") {
+ is(event.persisted, exp.persisted, "Invalid persisted state");
+ }
+ if (exp.title) {
+ ok(event.originalTarget instanceof HTMLDocument,
+ "originalTarget not a HTMLDocument");
+ is(event.originalTarget.title, exp.title, "titles don't match");
+ }
+
+ if (gExpected.length == 0) {
+ setTimeout(nextTest, 0);
+ }
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Tests 1 + 2:
+ // Back/forward between two simple documents. Bfcache will be used.
+
+ var test1Doc = "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>test1</body></html>";
+
+ gExpected = [{type: "pagehide", persisted: true},
+
+ {type: "load", title: "test1"},
+ {type: "pageshow", title: "test1", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test1Doc);
+ yield undefined;
+
+ var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ gExpected = [{type: "pagehide", title: "test1", persisted: true},
+ {type: "load", title: "test2"},
+ {type: "pageshow", title: "test2", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ gExpected = [{type: "pagehide", title: "test2", persisted: true},
+ {type: "pageshow", title: "test1", persisted: true}];
+ gBrowser.goBack();
+ yield undefined;
+
+ gExpected = [{type: "pagehide", title: "test1", persisted: true},
+ {type: "pageshow", title: "test2", persisted: true}];
+ gBrowser.goForward();
+ yield undefined;
+
+ // Tests 3 + 4:
+ // Back/forward between a two-level deep iframed document and a simple
+ // document. Bfcache will be used and events should be dispatched to
+ // all frames.
+
+ var test3Doc = "data:text/html,<html><head><title>test3</title>" +
+ "</head><body>" +
+ "<iframe src='data:text/html," +
+ "<html><head><title>test3-nested1</title></head>" +
+ "<body>test3-nested1" +
+ "<iframe src=\"data:text/html," +
+ "<html><head><title>test3-nested2</title></head>" +
+ "<body>test3-nested2</body></html>\">" +
+ "</iframe>" +
+ "</body></html>'>" +
+ "</iframe>" +
+ "</body></html>";
+
+ gExpected = [{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}];
+ BrowserTestUtils.loadURI(gBrowser, test3Doc);
+ yield undefined;
+
+ var test4Doc = "data:text/html,<html><head><title>test4</title></head>" +
+ "<body>test4</body></html>";
+
+ gExpected = [{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}];
+ BrowserTestUtils.loadURI(gBrowser, test4Doc);
+ yield undefined;
+
+ gExpected = [{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}];
+ gBrowser.goBack();
+ yield undefined;
+
+ // This is where the two nested pagehide are not dispatched in bug 364461
+ gExpected = [{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}];
+ gBrowser.goForward();
+ yield undefined;
+
+ // 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,<html><head><title>test5</title></head>" +
+ "<body onunload='while(false) { /* nop */ }'>" +
+ "test5</body></html>";
+
+ gExpected = [{type: "pagehide", title: "test4", persisted: true},
+ {type: "load", title: "test5"},
+ {type: "pageshow", title: "test5", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test5Doc);
+ yield undefined;
+
+ var test6Doc = "data:text/html,<html><head><title>test6</title></head>" +
+ "<body>test6</body></html>";
+
+ gExpected = [{type: "pagehide", title: "test5", persisted: false},
+ {type: "unload", title: "test5"},
+ {type: "load", title: "test6"},
+ {type: "pageshow", title: "test6", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test6Doc);
+ yield undefined;
+
+ gExpected = [{type: "pagehide", title: "test6", persisted: true},
+ {type: "load", title: "test5"},
+ {type: "pageshow", title: "test5", persisted: false}];
+ gBrowser.goBack();
+ yield undefined;
+
+ gExpected = [{type: "pagehide", title: "test5", persisted: false},
+ {type: "unload", title: "test5"},
+ {type: "pageshow", title: "test6", persisted: true}];
+ gBrowser.goForward();
+ yield undefined;
+
+ // 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,<html><head><title>test7</title>" +
+ "</head><body>" +
+ "<iframe src='data:text/html," +
+ "<html><head><title>test7-nested1</title></head>" +
+ "<body>test7-nested1<br/>" +
+ "<a href=\"data:text/plain,aaa\" target=\"_top\">" +
+ "Click me, hit back, click me again</a>" +
+ "</body></html>'>" +
+ "</iframe>" +
+ "</body></html>";
+
+ gExpected = [{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}];
+ BrowserTestUtils.loadURI(gBrowser, test7Doc);
+ yield undefined;
+
+ // Simulates a click on the link inside the iframe
+ function clickIframeLink() {
+ var iframe = gBrowser.contentDocument.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);
+ }
+
+ gExpected = [{type: "pagehide", title: "test7", persisted: true},
+ {type: "pagehide", title: "test7-nested1", persisted: true},
+ {type: "load"},
+ {type: "pageshow", persisted: false}];
+ clickIframeLink();
+ yield undefined;
+
+ is(gBrowser.currentURI.spec, "data:text/plain,aaa",
+ "Navigation is blocked when clicking link");
+
+ gExpected = [{type: "pagehide", persisted: true},
+ {type: "pageshow", title: "test7-nested1", persisted: true},
+ {type: "pageshow", title: "test7", persisted: true}];
+ gBrowser.goBack();
+ yield undefined;
+
+ gExpected = [{type: "pagehide", title: "test7", persisted: true},
+ {type: "pagehide", title: "test7-nested1", persisted: true},
+ {type: "load"},
+ {type: "pageshow", persisted: false}];
+ clickIframeLink();
+ yield undefined;
+
+ is(gBrowser.currentURI.spec, "data:text/plain,aaa",
+ "Navigation is blocked when clicking link");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug396519_window.xhtml b/docshell/test/chrome/bug396519_window.xhtml
new file mode 100644
index 0000000000..557ecfc6df
--- /dev/null
+++ b/docshell/test/chrome/bug396519_window.xhtml
@@ -0,0 +1,172 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="396519Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="396519 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+ const LISTEN_EVENTS = ["pageshow"];
+
+ var gBrowser;
+ var gTestCount = 0;
+ var gTestsIterator;
+ var gExpected = [];
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+ function finish() {
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.removeEventListener(eventType, eventListener, true);
+ }
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ function onLoad() {
+ gBrowser = document.getElementById("content");
+
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.addEventListener(eventType, eventListener, true);
+ }
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ // we're in pageshow, but we need to let that finish
+ // content eviction and saving happen during pageshow, so when doTest
+ // runs, we should should be in a testable state
+ setTimeout(doTest, 0);
+ }
+
+ function doTest() {
+ let history;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ history = gBrowser.browsingContext.sessionHistory;
+ } else {
+ history = gBrowser.webNavigation.sessionHistory.legacySHistory;
+ }
+
+ if (history.count == gExpected.length) {
+ for (var i=0; i<history.count; i++) {
+ var shEntry = history.getEntryAtIndex(i).
+ QueryInterface(Ci.nsISHEntry);
+ is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount);
+ }
+
+ // Make sure none of the SHEntries share bfcache entries with one
+ // another.
+ for (var i = 0; i < history.count; i++) {
+ for (var j = 0; j < history.count; j++) {
+ if (j == i)
+ continue;
+
+ let shentry1 = history.getEntryAtIndex(i)
+ .QueryInterface(Ci.nsISHEntry);
+ let shentry2 = history.getEntryAtIndex(j)
+ .QueryInterface(Ci.nsISHEntry);
+ ok(!shentry1.sharesDocumentWith(shentry2),
+ 'Test ' + gTestCount + ': shentry[' + i + "] shouldn't " +
+ "share document with shentry[" + j + ']');
+ }
+ }
+ }
+ else {
+ is(history.count, gExpected.length, "Wrong history length in test "+gTestCount);
+ }
+
+ setTimeout(nextTest, 0);
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Tests 1 + 2:
+ // Back/forward between two simple documents. Bfcache will be used.
+
+ var test1Doc = "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>test1</body></html>";
+
+ gTestCount++;
+ gExpected = [false];
+ BrowserTestUtils.loadURI(gBrowser, test1Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [true, false];
+ var test2Doc = test1Doc.replace(/1/,"2");
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [true, true, false];
+ BrowserTestUtils.loadURI(gBrowser, test1Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [true, true, true, false];
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, true, true, true, false];
+ BrowserTestUtils.loadURI(gBrowser, test1Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, false, true, true, true, false];
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, false, true, true, false, true];
+ gBrowser.goBack();
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, false, true, true, true, false];
+ gBrowser.goForward();
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, false, true, true, true, false];
+ gBrowser.gotoIndex(1);
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, true, true, true, false, false];
+ gBrowser.goBack();
+ yield undefined;
+
+ gTestCount++;
+ gExpected = [false, false, true, true, false, false];
+ gBrowser.gotoIndex(5);
+ yield undefined;
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug396649_window.xhtml b/docshell/test/chrome/bug396649_window.xhtml
new file mode 100644
index 0000000000..4f90a7e2e0
--- /dev/null
+++ b/docshell/test/chrome/bug396649_window.xhtml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="396649Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 396649 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ // Maximum number of entries in the bfcache for this session history.
+ // This number is hardcoded in docshell code. In the test, we'll
+ // navigate through enough pages so that we hit one that's been
+ // evicted from the bfcache because it's farther from the current
+ // page than this number.
+ const MAX_BFCACHE_PAGES = 3;
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 396649: Content
+ // viewers should be evicted from bfcache when going back if more
+ // than MAX_BFCACHE_PAGES from the current index.
+ //
+ function* testIterator()
+ {
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load enough pages so that the first loaded is eviced from
+ // the bfcache, since it is greater the MAX_BFCACHE_PAGES from
+ // the current position in the session history. Verify all
+ // of the pages are initially stored in the bfcache when
+ // they're unloaded.
+ for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ doPageNavigation( {
+ uri: "data:text/html,<!DOCTYPE html><html>" +
+ "<head><title>bug396649 page" + i +
+ "</title></head>" +
+ "<body>" +
+ "test page " + i +
+ "</body></html>",
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: i == 0 ?
+ undefined : "bug396649 page" + (i-1),
+ persisted: true },
+ { type: "pageshow",
+ title: "bug396649 page" + i } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ // Go back to the first page, one page at a time. The first
+ // MAX_BFCACHE_PAGES pages loaded via back should come from the bfcache,
+ // the last should not, since it should have been evicted during the
+ // previous navigation sequence. Verify all pages are initially stored
+ // in the bfcache when they're unloaded.
+ for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) {
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug396649 page" + i,
+ persisted: true },
+ { type: "pageshow",
+ title: "bug396649 page" + (i - 1),
+ persisted: i > 1 } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ // Traverse history forward now. Again, the first MAX_BFCACHE_PAGES
+ // pages should come from the bfcache, the last should not,
+ // since it should have been evicted during the backwards
+ // traversal above. Verify all pages are initially stored
+ // in the bfcache when they're unloaded.
+ for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ doPageNavigation( {
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug396649 page" + (i-1),
+ persisted: true },
+ { type: "pageshow",
+ title: "bug396649 page" + i,
+ persisted: i < MAX_BFCACHE_PAGES + 1 } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug449778_window.xhtml b/docshell/test/chrome/bug449778_window.xhtml
new file mode 100644
index 0000000000..0404fd5f52
--- /dev/null
+++ b/docshell/test/chrome/bug449778_window.xhtml
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 449778" onload="doTheTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox id="parent">
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function addBrowser(parent, id, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ b.setAttribute("type", "content");
+ b.setAttribute("id", id);
+ b.setAttribute("width", width);
+ b.setAttribute("height", height);
+ $(parent).appendChild(b);
+ }
+ addBrowser("parent", "f1", 300, 200);
+ addBrowser("parent", "f2", 300, 200);
+
+ /** Test for Bug 449778 **/
+ var doc1 = "data:text/html,<html><body>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><body>This is a second test</body></html>";
+ var doc3 = "data:text/html,<html><body>This is a <script>var evt = document.createEvent('Events'); evt.initEvent('testEvt', true, true); document.dispatchEvent(evt);</script>third test</body></html>";
+
+
+ $("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, false);
+
+ listener.detach = function() {
+ node.removeEventListener("page" + type, listener, false);
+ }
+ 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, false);
+
+ 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, false);
+ }
+
+ function listener3() {
+ $("f1").removeEventListener("pageshow", listener3, false);
+
+ 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);
+ }
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/bug449780_window.xhtml b/docshell/test/chrome/bug449780_window.xhtml
new file mode 100644
index 0000000000..4d21d4d046
--- /dev/null
+++ b/docshell/test/chrome/bug449780_window.xhtml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 449780" onload="doTheTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox id="parent">
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function addBrowser(parent, id, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ b.setAttribute("type", "content");
+ b.setAttribute("id", id);
+ b.setAttribute("width", width);
+ b.setAttribute("height", height);
+ $(parent).appendChild(b);
+ }
+ addBrowser("parent", "f1", 300, 200);
+ addBrowser("parent", "f2", 300, 200);
+
+ /** Test for Bug 449780 **/
+ var doc1 = "data:text/html,<html><body>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><body>This is a second test</body></html>";
+
+ function getDOM(id) {
+ return $(id).contentDocument.documentElement.innerHTML;
+ }
+
+ var tester = (function*() {
+ var origDOM = getDOM("f1");
+ $("f1").contentDocument.body.textContent = "Modified";
+ var modifiedDOM = getDOM("f1");
+ isnot(origDOM, modifiedDOM, "DOM should be different");
+ $("f1").contentWindow.location.href = doc2;
+ yield undefined;
+
+ $("f1").goBack();
+ yield undefined;
+
+ is(getDOM("f1"), modifiedDOM, "Should have been bfcached");
+ $("f1").goForward();
+ yield undefined;
+
+ // Ignore the notifications during swap
+ $("f1").removeEventListener("pageshow", testDriver, false);
+ $("f1").swapDocShells($("f2"));
+ $("f2").addEventListener("pageshow", testDriver, false);
+ $("f2").goBack();
+ yield undefined;
+
+ is(getDOM("f2"), origDOM, "Should have not have been bfcached");
+ window.close();
+ SimpleTest.finish();
+ })();
+
+ function testDriver() {
+ setTimeout(function() { tester.next() }, 0);
+ }
+
+ function doTheTest() {
+ $("f1").addEventListener("pageshow", testDriver, false);
+ $("f1").setAttribute("src", doc1);
+ }
+ ]]></script>
+</window>
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 @@
+<window title="Mozilla Bug 454235 SubFrame"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <deck flex="1">
+ <browser id="topBrowser" src="about:mozilla"/>
+ <browser id="burriedBrowser" src="about:mozilla"/>
+ </deck>
+</window>
diff --git a/docshell/test/chrome/bug582176_window.xhtml b/docshell/test/chrome/bug582176_window.xhtml
new file mode 100644
index 0000000000..b31708104b
--- /dev/null
+++ b/docshell/test/chrome/bug582176_window.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="nextTestAsync();"
+ title="bug 582176 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTestAsync() {
+ SimpleTest.executeSoon(tests.next.bind(tests));
+ }
+
+ ////
+ // Generator function for test steps for bug 582176:
+ // Description goes here.
+ //
+ function* testIterator()
+ {
+ var browser = document.getElementById('content');
+ browser.addEventListener("pageshow", nextTestAsync, true);
+
+ enableBFCache(true);
+
+ var notificationCount = 0;
+ var observer = {
+ observe: function(aSubject, aTopic, aData) {
+ is(aSubject, browser.contentWindow,
+ "correct subject");
+ is(aTopic, "content-document-global-created",
+ "correct topic");
+ is(aData, "http://mochi.test:8888",
+ "correct data");
+ notificationCount++;
+ }
+ };
+
+ os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(observer, "content-document-global-created");
+
+ BrowserTestUtils.loadURI(browser, "http://mochi.test:8888/tests/docshell/test/chrome/582176_dummy.html");
+ yield undefined;
+ is(browser.contentWindow.testVar, undefined,
+ "variable unexpectedly there already");
+ browser.contentWindow.wrappedJSObject.testVar = 1;
+ is(notificationCount, 1, "Should notify on first navigation");
+
+ BrowserTestUtils.loadURI(browser, "http://mochi.test:8888/tests/docshell/test/chrome/582176_dummy.html?2");
+ yield undefined;
+ is(browser.contentWindow.wrappedJSObject.testVar, undefined,
+ "variable should no longer be there");
+ is(notificationCount, 2, "Should notify on second navigation");
+
+ browser.goBack();
+ yield undefined;
+ is(browser.contentWindow.wrappedJSObject.testVar, 1,
+ "variable should still be there");
+ is(notificationCount, 2, "Should not notify on back navigation");
+
+ BrowserTestUtils.loadURI(browser, "http://mochi.test:8888/tests/docshell/test/chrome/582176_xml.xml");
+ yield undefined;
+ is(browser.contentDocument.body.textContent, "xslt result",
+ "Transform performed successfully");
+ is(notificationCount, 3, "Should notify only once on XSLT navigation");
+
+ os.removeObserver(observer, "content-document-global-created")
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 608669 - Blank page"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="notifyOpener();">
+ <description flex="1" value="This window is intentionally left blank"/>
+ <script type="application/javascript">
+ function notifyOpener() {
+ if (opener) {
+ opener.postMessage("load", "*");
+ }
+ }
+ </script>
+</window>
diff --git a/docshell/test/chrome/bug662200_window.xhtml b/docshell/test/chrome/bug662200_window.xhtml
new file mode 100644
index 0000000000..61cf3c63eb
--- /dev/null
+++ b/docshell/test/chrome/bug662200_window.xhtml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 662200 test">
+
+ <script type="application/javascript"
+ src="docshell_helpers.js">
+ </script>
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 662200:
+ // Description goes here.
+ //
+ function* testIterator()
+ {
+ // Load the first test page
+ var navData = {
+ uri: getHttpUrl("662200a.html"),
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: "A"} ],
+ onNavComplete: nextTest
+ };
+ doPageNavigation(navData);
+ yield undefined;
+
+ // Load the second test page.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "A"},
+ {type: "pageshow",
+ title: "B"} ],
+ onNavComplete: nextTest
+ }
+ waitForPageEvents(navData);
+ var link = TestWindow.getDocument().getElementById("link");
+ var event = TestWindow.getDocument().createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, TestWindow.getWindow(),
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ yield undefined;
+
+ // Load the third test page.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "B"},
+ {type: "pageshow",
+ title: "C"} ],
+ onNavComplete: nextTest
+ };
+ waitForPageEvents(navData);
+ var link = TestWindow.getDocument().getElementById("link");
+ var event = TestWindow.getDocument().createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, TestWindow.getWindow(),
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ yield undefined;
+
+ // Go back.
+ navData = {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "C"},
+ {type: "pageshow",
+ title: "B"} ],
+ onNavComplete: nextTest
+ };
+ doPageNavigation(navData);
+ yield undefined;
+
+ var docshell = TestWindow.getWindow().docShell;
+ let shistory;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ shistory = docshell.browsingContext.sessionHistory;
+ } else {
+ shistory = docshell.sessionHistory.legacySHistory;
+ }
+
+ // Reload.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "B"},
+ {type: "pageshow",
+ title: "B"} ],
+ onNavComplete: nextTest
+ };
+ // Asking the docshell harness to reload for us will call reload on
+ // nsDocShell which has different behavior than the reload on nsSHistory
+ // so we call reloadCurrentEntry() (which is equivalent to reload(0) and
+ // visible from JS) explicitly here.
+ waitForPageEvents(navData);
+ shistory.reloadCurrentEntry();
+ yield undefined;
+
+ // After this sequence of events, we should be able to go back and forward
+ is(TestWindow.getBrowser().canGoBack, true, "Should be able to go back!");
+ is(TestWindow.getBrowser().canGoForward, true, "Should be able to go forward!");
+ is(shistory.requestedIndex, -1, "Requested index should be cleared!");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug690056_window.xhtml b/docshell/test/chrome/bug690056_window.xhtml
new file mode 100644
index 0000000000..75f23b134d
--- /dev/null
+++ b/docshell/test/chrome/bug690056_window.xhtml
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="690056Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 6500056 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ var tests = testIterator();
+
+ function nextTest() {
+ tests.next();
+ }
+
+ // Makes sure that we fire the visibilitychange events
+ function* testIterator() {
+ // Enable bfcache
+ enableBFCache(8);
+
+ // Load something for a start
+ doPageNavigation({
+ uri: 'data:text/html,<title>initial load</title>',
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now load a new page
+ doPageNavigation({
+ uri: 'data:text/html,<title>new load</title>',
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ // No visibilitychange events fired for initial pageload
+ { type: "pageshow",
+ title: "new load",
+ persisted: false }, // false on initial load
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now go back
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "new load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "initial load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // And forward
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "new load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ function generateDetector(state, hidden, title, name) {
+ var detector = function (event) {
+ is(event.target.hidden, hidden,
+ name + " hidden value does not match");
+ is(event.target.visibilityState, state,
+ name + " state value does not match");
+ is(event.target.title, title,
+ name + " title value does not match");
+ document.getElementById("content")
+ .removeEventListener("visibilitychange",
+ detector,
+ true);
+ nextTest();
+ }
+
+ document.getElementById("content")
+ .addEventListener("visibilitychange", detector, true);
+ }
+
+ generateDetector("hidden", true, "new load", "Going hidden");
+
+ // Now flip our docshell to not active
+ document.getElementById("content").docShellIsActive = false;
+ yield undefined;
+
+ // And navigate back; there should be no visibility state transitions
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "new load",
+ persisted: true },
+ { type: "pageshow",
+ title: "initial load",
+ persisted: true },
+ ],
+ unexpectedEvents: [ "visibilitychange" ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ generateDetector("visible", false, "initial load", "Going visible");
+
+ // Now set the docshell active again
+ document.getElementById("content").docShellIsActive = true;
+ yield undefined;
+
+ // And forward
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "new load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
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 @@
+<html>
+ <head>
+ <title>
+ </title>
+ </head>
+ <body>
+ <div id="div1" style="width:1024px; height:768px; border:none;">
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug89419.sjs b/docshell/test/chrome/bug89419.sjs
new file mode 100644
index 0000000000..c075b1e504
--- /dev/null
+++ b/docshell/test/chrome/bug89419.sjs
@@ -0,0 +1,13 @@
+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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="89419Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTests, 0);"
+ title="bug 89419 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ ////
+ // A visited link should have the :visited style applied
+ // to it when displayed on a page which was fetched from
+ // the bfcache.
+ //
+ async function runTests() {
+ // Disable rcwn to make cache behavior deterministic.
+ var {SpecialPowers} = window.arguments[0];
+ await SpecialPowers.pushPrefEnv({"set":[["network.http.rcwn.enabled", false]]});
+
+ // Load a test page containing an image referring to the sjs that returns
+ // a different redirect every time it's loaded.
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: getHttpUrl("89419.html"),
+ onNavComplete: resolve,
+ preventBFCache: true,
+ });
+ })
+
+ var first = await snapshotWindow(TestWindow.getWindow());
+
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: "about:blank",
+ onNavComplete: resolve,
+ });
+ });
+
+ var second = await snapshotWindow(TestWindow.getWindow());
+ function snapshotsEqual(snap1, snap2) {
+ return compareSnapshots(snap1, snap2, true)[0];
+ }
+ ok(!snapshotsEqual(first, second), "about:blank should not be the same as the image web page");
+
+ await new Promise(resolve => {
+ doPageNavigation({
+ back: true,
+ onNavComplete: resolve,
+ });
+ });
+
+ var third = await snapshotWindow(TestWindow.getWindow());
+ ok(!snapshotsEqual(third, second), "going back should not be the same as about:blank");
+ ok(snapshotsEqual(first, third), "going back should be the same as the initial load");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
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 @@
+<html>
+<head>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css">
+ <script src="bug909218.js"></script>
+</head>
+<body>
+ <img src="http://mochi.test:8888/tests/docshell/test/chrome/red.png">
+ <!-- an iframe so we can check these too get the correct flags -->
+ <iframe src="generic.html"/>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug909218.js b/docshell/test/chrome/bug909218.js
new file mode 100644
index 0000000000..2222480cd3
--- /dev/null
+++ b/docshell/test/chrome/bug909218.js
@@ -0,0 +1,2 @@
+// This file exists just to ensure that we load it with the correct flags.
+dump("bug909218.js loaded\n");
diff --git a/docshell/test/chrome/bug92598_window.xhtml b/docshell/test/chrome/bug92598_window.xhtml
new file mode 100644
index 0000000000..6220e67586
--- /dev/null
+++ b/docshell/test/chrome/bug92598_window.xhtml
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="92598Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="92598 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"];
+
+ var gBrowser;
+ var gTestsIterator;
+ var gExpected = [];
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+
+ function finish() {
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.removeEventListener(eventType, eventListener, true);
+ }
+
+ // Work around bug 467960
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let history = gBrowser.browsingContext.sessionHistory;
+ history.purgeHistory(history.count);
+ } else {
+ let history = gBrowser.webNavigation.sessionHistory;
+ history.legacySHistory.purgeHistory(history.count);
+ }
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ function onLoad() {
+ gBrowser = document.getElementById("content");
+ for (let eventType of LISTEN_EVENTS) {
+ gBrowser.addEventListener(eventType, eventListener, true);
+ }
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ ok(gExpected.length >= 1, "Unexpected event " + event.type);
+ if (gExpected.length == 0) {
+ // in case of unexpected event, try to continue anyway
+ setTimeout(nextTest, 0);
+ return;
+ }
+
+ var exp = gExpected.shift();
+ is(event.type, exp.type, "Invalid event received");
+ if (typeof(exp.persisted) != "undefined") {
+ is(event.persisted, exp.persisted, "Invalid persisted state");
+ }
+ if (exp.title) {
+ ok(event.originalTarget instanceof HTMLDocument,
+ "originalTarget not a HTMLDocument");
+ is(event.originalTarget.title, exp.title, "titles don't match");
+ }
+
+ if (gExpected.length == 0) {
+ setTimeout(nextTest, 0);
+ }
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Load a page with a no-cache header, followed by a simple page
+ // On pagehide, first page should report it is not being persisted
+ var test1DocURI = "http://mochi.test:8888/tests/docshell/test/chrome/92598_nostore.html";
+
+ gExpected = [{type: "pagehide", persisted: true},
+ {type: "load", title: "test1"},
+ {type: "pageshow", title: "test1", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test1DocURI);
+ yield undefined;
+
+ var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ gExpected = [{type: "pagehide", title: "test1", persisted: false},
+ {type: "unload", title: "test1"},
+ {type: "load", title: "test2"},
+ {type: "pageshow", title: "test2", persisted: false}];
+ BrowserTestUtils.loadURI(gBrowser, test2Doc);
+ yield undefined;
+
+ // Now go back in history. First page should not have been cached.
+ // Check persisted property to confirm
+ gExpected = [{type: "pagehide", title: "test2", persisted: true},
+ {type: "load", title: "test1"},
+ {type: "pageshow", title: "test1", persisted: false}];
+ gBrowser.goBack();
+ yield undefined;
+
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/chrome.ini b/docshell/test/chrome/chrome.ini
new file mode 100644
index 0000000000..a7d49effa1
--- /dev/null
+++ b/docshell/test/chrome/chrome.ini
@@ -0,0 +1,89 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ 662200a.html
+ 662200b.html
+ 662200c.html
+ 89419.html
+ 92598_nostore.html
+ bug112564_window.xhtml
+ bug113934_window.xhtml
+ bug215405_window.xhtml
+ bug293235.html
+ bug293235_p2.html
+ bug293235_window.xhtml
+ bug294258_testcase.html
+ bug294258_window.xhtml
+ bug298622_window.xhtml
+ bug301397_1.html
+ bug301397_2.html
+ bug301397_3.html
+ bug301397_4.html
+ bug301397_window.xhtml
+ bug303267.html
+ bug303267_window.xhtml
+ bug311007_window.xhtml
+ bug321671_window.xhtml
+ bug360511_case1.html
+ bug360511_case2.html
+ bug360511_window.xhtml
+ bug364461_window.xhtml
+ bug396519_window.xhtml
+ bug396649_window.xhtml
+ bug449778_window.xhtml
+ bug449780_window.xhtml
+ bug454235-subframe.xhtml
+ bug582176_window.xhtml
+ bug608669.xhtml
+ bug662200_window.xhtml
+ bug690056_window.xhtml
+ bug846906.html
+ bug89419_window.xhtml
+ bug909218.html
+ bug909218.js
+ bug92598_window.xhtml
+ docshell_helpers.js
+ file_viewsource_forbidden_in_iframe.html
+ generic.html
+ mozFrameType_window.xhtml
+ test_docRedirect.sjs
+
+[test_allowContentRetargeting.html]
+[test_bug112564.xhtml]
+[test_bug113934.xhtml]
+[test_bug215405.xhtml]
+[test_bug293235.xhtml]
+skip-if = true # bug 1393441
+[test_bug294258.xhtml]
+[test_bug298622.xhtml]
+[test_bug301397.xhtml]
+skip-if = (os == 'win' && processor == 'aarch64') # bug 1533819
+[test_bug303267.xhtml]
+[test_bug311007.xhtml]
+[test_bug321671.xhtml]
+[test_bug360511.xhtml]
+[test_bug364461.xhtml]
+skip-if = (os == 'win' && processor == 'aarch64') # bug 1533814
+[test_bug396519.xhtml]
+[test_bug396649.xhtml]
+[test_bug428288.html]
+[test_bug449778.xhtml]
+[test_bug449780.xhtml]
+[test_bug453650.xhtml]
+[test_bug454235.xhtml]
+[test_bug456980.xhtml]
+[test_bug565388.xhtml]
+skip-if = true # Bug 1026815,Bug 1546159
+[test_bug582176.xhtml]
+[test_bug608669.xhtml]
+[test_bug662200.xhtml]
+[test_bug690056.xhtml]
+[test_bug789773.xhtml]
+[test_bug846906.xhtml]
+[test_bug89419.xhtml]
+[test_bug909218.html]
+[test_bug92598.xhtml]
+[test_mozFrameType.xhtml]
+[test_viewsource_forbidden_in_iframe.xhtml]
+skip-if = true # bug 1019315
+[test_docRedirect.xhtml]
diff --git a/docshell/test/chrome/docshell_helpers.js b/docshell/test/chrome/docshell_helpers.js
new file mode 100644
index 0000000000..d759046fa7
--- /dev/null
+++ b/docshell/test/chrome/docshell_helpers.js
@@ -0,0 +1,614 @@
+if (!window.opener && window.arguments) {
+ window.opener = window.arguments[0];
+}
+/**
+ * Import common SimpleTest methods so that they're usable in this window.
+ */
+var imports = [
+ "SimpleTest",
+ "is",
+ "isnot",
+ "ok",
+ "onerror",
+ "todo",
+ "todo_is",
+ "todo_isnot",
+];
+for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+}
+const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm"
+);
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// Some functions assume chrome-harness.js has been loaded.
+/* import-globals-from ../../../testing/mochitest/chrome-harness.js */
+
+/**
+ * Define global constants and variables.
+ */
+const NAV_NONE = 0;
+const NAV_BACK = 1;
+const NAV_FORWARD = 2;
+const NAV_URI = 3;
+const NAV_RELOAD = 4;
+
+var gExpectedEvents; // an array of events which are expected to
+// be triggered by this navigation
+var gUnexpectedEvents; // an array of event names which are NOT expected
+// to be triggered by this navigation
+var gFinalEvent; // true if the last expected event has fired
+var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored
+// in the bfcache
+var gNavType = NAV_NONE; // defines the most recent navigation type
+// executed by doPageNavigation
+var gOrigMaxTotalViewers = undefined; // original value of max_total_viewers, // to be restored at end of test
+
+var gExtractedPath = null; // used to cache file path for extracting files from a .jar file
+
+/**
+ * The doPageNavigation() function performs page navigations asynchronously,
+ * listens for specified events, and compares actual events with a list of
+ * expected events. When all expected events have occurred, an optional
+ * callback can be notified. The parameter passed to this function is an
+ * object with the following properties:
+ *
+ * uri: if !undefined, the browser will navigate to this uri
+ *
+ * back: if true, the browser will execute goBack()
+ *
+ * forward: if true, the browser will execute goForward()
+ *
+ * reload: if true, the browser will execute reload()
+ *
+ * eventsToListenFor: an array containing one or more of the following event
+ * types to listen for: "pageshow", "pagehide", "onload",
+ * "onunload". If this property is undefined, only a
+ * single "pageshow" events will be listened for. If this
+ * property is explicitly empty, [], then no events will
+ * be listened for.
+ *
+ * expectedEvents: an array of one or more expectedEvent objects,
+ * corresponding to the events which are expected to be
+ * fired for this navigation. Each object has the
+ * following properties:
+ *
+ * type: one of the event type strings
+ * title (optional): the title of the window the
+ * event belongs to
+ * persisted (optional): the event's expected
+ * .persisted attribute
+ *
+ * This function will verify that events with the
+ * specified properties are fired in the same order as
+ * specified in the array. If .title or .persisted
+ * properties for an expectedEvent are undefined, those
+ * properties will not be verified for that particular
+ * event.
+ *
+ * This property is ignored if eventsToListenFor is
+ * undefined or [].
+ *
+ * preventBFCache: if true, an unload handler will be added to the loaded
+ * page to prevent it from being bfcached. This property
+ * has no effect when eventsToListenFor is [].
+ *
+ * onNavComplete: a callback which is notified after all expected events
+ * have occurred, or after a timeout has elapsed. This
+ * callback is not notified if eventsToListenFor is [].
+ *
+ * There must be an expectedEvent object for each event of the types in
+ * eventsToListenFor which is triggered by this navigation. For example, if
+ * eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents
+ * must contain an object for each pagehide and pageshow event which occurs as
+ * a result of this navigation.
+ */
+// eslint-disable-next-line complexity
+function doPageNavigation(params) {
+ // Parse the parameters.
+ let back = params.back ? params.back : false;
+ let forward = params.forward ? params.forward : false;
+ let reload = params.reload ? params.reload : false;
+ let uri = params.uri ? params.uri : false;
+ let eventsToListenFor =
+ typeof params.eventsToListenFor != "undefined"
+ ? params.eventsToListenFor
+ : ["pageshow"];
+ gExpectedEvents =
+ typeof params.eventsToListenFor == "undefined" ||
+ eventsToListenFor.length == 0
+ ? undefined
+ : params.expectedEvents;
+ gUnexpectedEvents =
+ typeof params.eventsToListenFor == "undefined" ||
+ eventsToListenFor.length == 0
+ ? undefined
+ : params.unexpectedEvents;
+ let preventBFCache =
+ typeof [params.preventBFCache] == "undefined"
+ ? false
+ : params.preventBFCache;
+ let waitOnly =
+ typeof params.waitForEventsOnly == "boolean" && params.waitForEventsOnly;
+
+ // Do some sanity checking on arguments.
+ if (back && forward) {
+ throw new Error("Can't specify both back and forward");
+ }
+ if (back && uri) {
+ throw new Error("Can't specify both back and a uri");
+ }
+ if (forward && uri) {
+ throw new Error("Can't specify both forward and a uri");
+ }
+ if (reload && (forward || back || uri)) {
+ throw new Error("Can't specify reload and another navigation type");
+ }
+ if (!back && !forward && !uri && !reload && !waitOnly) {
+ throw new Error("Must specify back or foward or reload or uri");
+ }
+ if (params.onNavComplete && eventsToListenFor.length == 0) {
+ throw new Error("Can't use onNavComplete when eventsToListenFor == []");
+ }
+ if (params.preventBFCache && eventsToListenFor.length == 0) {
+ throw new Error("Can't use preventBFCache when eventsToListenFor == []");
+ }
+ if (params.preventBFCache && waitOnly) {
+ throw new Error("Can't prevent bfcaching when only waiting for events");
+ }
+ if (waitOnly && typeof params.onNavComplete == "undefined") {
+ throw new Error(
+ "Must specify onNavComplete when specifying waitForEventsOnly"
+ );
+ }
+ if (waitOnly && (back || forward || reload || uri)) {
+ throw new Error(
+ "Can't specify a navigation type when using waitForEventsOnly"
+ );
+ }
+ for (let anEventType of eventsToListenFor) {
+ let eventFound = false;
+ if (anEventType == "pageshow" && !gExpectedEvents) {
+ eventFound = true;
+ }
+ if (gExpectedEvents) {
+ for (let anExpectedEvent of gExpectedEvents) {
+ if (anExpectedEvent.type == anEventType) {
+ eventFound = true;
+ }
+ }
+ }
+ if (gUnexpectedEvents) {
+ for (let anExpectedEventType of gUnexpectedEvents) {
+ if (anExpectedEventType == anEventType) {
+ eventFound = true;
+ }
+ }
+ }
+ if (!eventFound) {
+ throw new Error(
+ `Event type ${anEventType} is specified in ` +
+ "eventsToListenFor, but not in expectedEvents"
+ );
+ }
+ }
+
+ // If the test explicitly sets .eventsToListenFor to [], don't wait for any
+ // events.
+ gFinalEvent = eventsToListenFor.length == 0;
+
+ // Add an event listener for each type of event in the .eventsToListenFor
+ // property of the input parameters.
+ for (let eventType of eventsToListenFor) {
+ dump("TEST: registering a listener for " + eventType + " events\n");
+ TestWindow.getBrowser().addEventListener(
+ eventType,
+ pageEventListener,
+ true
+ );
+ }
+
+ // Perform the specified navigation.
+ if (back) {
+ gNavType = NAV_BACK;
+ TestWindow.getBrowser().goBack();
+ } else if (forward) {
+ gNavType = NAV_FORWARD;
+ TestWindow.getBrowser().goForward();
+ } else if (uri) {
+ gNavType = NAV_URI;
+ BrowserTestUtils.loadURI(TestWindow.getBrowser(), uri);
+ } else if (reload) {
+ gNavType = NAV_RELOAD;
+ TestWindow.getBrowser().reload();
+ } else if (waitOnly) {
+ gNavType = NAV_NONE;
+ } else {
+ throw new Error("No valid navigation type passed to doPageNavigation!");
+ }
+
+ // If we're listening for events and there is an .onNavComplete callback,
+ // wait for all events to occur, and then call doPageNavigation_complete().
+ if (eventsToListenFor.length > 0 && params.onNavComplete) {
+ waitForTrue(
+ function() {
+ return gFinalEvent;
+ },
+ function() {
+ doPageNavigation_complete(
+ eventsToListenFor,
+ params.onNavComplete,
+ preventBFCache
+ );
+ }
+ );
+ }
+}
+
+/**
+ * Finish doPageNavigation(), by removing event listeners, adding an unload
+ * handler if appropriate, and calling the onNavComplete callback. This
+ * function is called after all the expected events for this navigation have
+ * occurred.
+ */
+function doPageNavigation_complete(
+ eventsToListenFor,
+ onNavComplete,
+ preventBFCache
+) {
+ // Unregister our event listeners.
+ dump("TEST: removing event listeners\n");
+ for (let eventType of eventsToListenFor) {
+ TestWindow.getBrowser().removeEventListener(
+ eventType,
+ pageEventListener,
+ true
+ );
+ }
+
+ // If the .preventBFCache property was set, add an empty unload handler to
+ // prevent the page from being bfcached.
+ let uri = TestWindow.getBrowser().currentURI.spec;
+ if (preventBFCache) {
+ TestWindow.getWindow().addEventListener(
+ "unload",
+ function() {
+ dump(
+ "TEST: Called dummy unload function to prevent page from " +
+ "being bfcached.\n"
+ );
+ },
+ true
+ );
+
+ // Save the current uri in an array of uri's which shouldn't be
+ // stored in the bfcache, for later verification.
+ if (!(uri in gUrisNotInBFCache)) {
+ gUrisNotInBFCache.push(uri);
+ }
+ } else if (gNavType == NAV_URI) {
+ // If we're navigating to a uri and .preventBFCache was not
+ // specified, splice it out of gUrisNotInBFCache if it's there.
+ gUrisNotInBFCache.forEach(function(element, index, array) {
+ if (element == uri) {
+ array.splice(index, 1);
+ }
+ }, this);
+ }
+
+ // Notify the callback now that we're done.
+ onNavComplete.call();
+}
+
+/**
+ * Allows a test to wait for page navigation events, and notify a
+ * callback when they've all been received. This works exactly the
+ * same as doPageNavigation(), except that no navigation is initiated.
+ */
+function waitForPageEvents(params) {
+ params.waitForEventsOnly = true;
+ doPageNavigation(params);
+}
+
+/**
+ * The event listener which listens for expectedEvents.
+ */
+function pageEventListener(event) {
+ try {
+ dump(
+ "TEST: eventListener received a " +
+ event.type +
+ " event for page " +
+ event.originalTarget.title +
+ ", persisted=" +
+ event.persisted +
+ "\n"
+ );
+ } catch (e) {
+ // Ignore any exception.
+ }
+
+ // If this page shouldn't be in the bfcache because it was previously
+ // loaded with .preventBFCache, make sure that its pageshow event
+ // has .persisted = false, even if the test doesn't explicitly test
+ // for .persisted.
+ if (
+ event.type == "pageshow" &&
+ (gNavType == NAV_BACK || gNavType == NAV_FORWARD)
+ ) {
+ let uri = TestWindow.getBrowser().currentURI.spec;
+ if (uri in gUrisNotInBFCache) {
+ ok(
+ !event.persisted,
+ "pageshow event has .persisted = false, even " +
+ "though it was loaded with .preventBFCache previously\n"
+ );
+ }
+ }
+
+ if (typeof gUnexpectedEvents != "undefined") {
+ is(
+ gUnexpectedEvents.indexOf(event.type),
+ -1,
+ "Should not get unexpected event " + event.type
+ );
+ }
+
+ // If no expected events were specified, mark the final event as having been
+ // triggered when a pageshow event is fired; this will allow
+ // doPageNavigation() to return.
+ if (typeof gExpectedEvents == "undefined" && event.type == "pageshow") {
+ waitForNextPaint(function() {
+ gFinalEvent = true;
+ });
+ return;
+ }
+
+ // If there are explicitly no expected events, but we receive one, it's an
+ // error.
+ if (gExpectedEvents.length == 0) {
+ ok(false, "Unexpected event (" + event.type + ") occurred");
+ return;
+ }
+
+ // Grab the next expected event, and compare its attributes against the
+ // actual event.
+ let expected = gExpectedEvents.shift();
+
+ is(
+ event.type,
+ expected.type,
+ "A " +
+ expected.type +
+ " event was expected, but a " +
+ event.type +
+ " event occurred"
+ );
+
+ if (typeof expected.title != "undefined") {
+ ok(
+ event.originalTarget instanceof HTMLDocument,
+ "originalTarget for last " + event.type + " event not an HTMLDocument"
+ );
+ is(
+ event.originalTarget.title,
+ expected.title,
+ "A " +
+ event.type +
+ " event was expected for page " +
+ expected.title +
+ ", but was fired for page " +
+ event.originalTarget.title
+ );
+ }
+
+ if (typeof expected.persisted != "undefined") {
+ is(
+ event.persisted,
+ expected.persisted,
+ "The persisted property of the " +
+ event.type +
+ " event on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ if ("visibilityState" in expected) {
+ is(
+ event.originalTarget.visibilityState,
+ expected.visibilityState,
+ "The visibilityState property of the document on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ if ("hidden" in expected) {
+ is(
+ event.originalTarget.hidden,
+ expected.hidden,
+ "The hidden property of the document on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ // If we're out of expected events, let doPageNavigation() return.
+ if (gExpectedEvents.length == 0) {
+ waitForNextPaint(function() {
+ gFinalEvent = true;
+ });
+ }
+}
+
+/**
+ * End a test.
+ */
+function finish() {
+ // Work around bug 467960.
+ let history;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ history = TestWindow.getBrowser().browsingContext?.sessionHistory;
+ } else {
+ history = TestWindow.getBrowser().webNavigation.sessionHistory
+ .legacySHistory;
+ }
+
+ if (history) {
+ history.purgeHistory(history.count);
+ }
+
+ // If the test changed the value of max_total_viewers via a call to
+ // enableBFCache(), then restore it now.
+ if (typeof gOrigMaxTotalViewers != "undefined") {
+ Services.prefs.setIntPref(
+ "browser.sessionhistory.max_total_viewers",
+ gOrigMaxTotalViewers
+ );
+ }
+
+ // Close the test window and signal the framework that the test is done.
+ let opener = window.opener;
+ let SimpleTest = opener.wrappedJSObject.SimpleTest;
+
+ // Wait for the window to be closed before finishing the test
+ Services.ww.registerNotification(function observer(subject, topic, data) {
+ if (topic == "domwindowclosed") {
+ Services.ww.unregisterNotification(observer);
+ SimpleTest.waitForFocus(SimpleTest.finish, opener);
+ }
+ });
+
+ window.close();
+}
+
+/**
+ * Helper function which waits until another function returns true, or until a
+ * timeout occurs, and then notifies a callback.
+ *
+ * Parameters:
+ *
+ * fn: a function which is evaluated repeatedly, and when it turns true,
+ * the onWaitComplete callback is notified.
+ *
+ * onWaitComplete: a callback which will be notified when fn() returns
+ * true, or when a timeout occurs.
+ *
+ * timeout: a timeout, in seconds or ms, after which waitForTrue() will
+ * fail an assertion and then return, even if the fn function never
+ * returns true. If timeout is undefined, waitForTrue() will never
+ * time out.
+ */
+function waitForTrue(fn, onWaitComplete, timeout) {
+ var start = new Date().valueOf();
+ if (typeof timeout != "undefined") {
+ // If timeoutWait is less than 500, assume it represents seconds, and
+ // convert to ms.
+ if (timeout < 500) {
+ timeout *= 1000;
+ }
+ }
+
+ // Loop until the test function returns true, or until a timeout occurs,
+ // if a timeout is defined.
+ var intervalid;
+ intervalid = setInterval(function() {
+ var timeoutHit = false;
+ if (typeof timeout != "undefined") {
+ timeoutHit = new Date().valueOf() - start >= timeout;
+ if (timeoutHit) {
+ ok(false, "Timed out waiting for condition");
+ }
+ }
+ if (timeoutHit || fn.call()) {
+ // Stop calling the test function and notify the callback.
+ clearInterval(intervalid);
+ onWaitComplete.call();
+ }
+ }, 20);
+}
+
+function waitForNextPaint(cb) {
+ requestAnimationFrame(_ => requestAnimationFrame(cb));
+}
+
+/**
+ * Enable or disable the bfcache.
+ *
+ * Parameters:
+ *
+ * enable: if true, set max_total_viewers to -1 (the default); if false, set
+ * to 0 (disabled), if a number, set it to that specific number
+ */
+function enableBFCache(enable) {
+ // If this is the first time the test called enableBFCache(),
+ // store the original value of max_total_viewers, so it can
+ // be restored at the end of the test.
+ if (typeof gOrigMaxTotalViewers == "undefined") {
+ gOrigMaxTotalViewers = Services.prefs.getIntPref(
+ "browser.sessionhistory.max_total_viewers"
+ );
+ }
+
+ if (typeof enable == "boolean") {
+ if (enable) {
+ Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
+ } else {
+ Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
+ }
+ } else if (typeof enable == "number") {
+ Services.prefs.setIntPref(
+ "browser.sessionhistory.max_total_viewers",
+ enable
+ );
+ }
+}
+
+/*
+ * get http root for local tests. Use a single extractJarToTmp instead of
+ * extracting for each test.
+ * Returns a file://path if we have a .jar file
+ */
+function getHttpRoot() {
+ var location = window.location.href;
+ location = getRootDirectory(location);
+ var jar = getJar(location);
+ if (jar != null) {
+ if (gExtractedPath == null) {
+ var resolved = extractJarToTmp(jar);
+ gExtractedPath = resolved.path;
+ }
+ } else {
+ return null;
+ }
+ return "file://" + gExtractedPath + "/";
+}
+
+/**
+ * Returns the full HTTP url for a file in the mochitest docshell test
+ * directory.
+ */
+function getHttpUrl(filename) {
+ var root = getHttpRoot();
+ if (root == null) {
+ root = "http://mochi.test:8888/chrome/docshell/test/chrome/";
+ }
+ return root + filename;
+}
+
+/**
+ * A convenience object with methods that return the current test window,
+ * browser, and document.
+ */
+var TestWindow = {};
+TestWindow.getWindow = function() {
+ return document.getElementById("content").contentWindow;
+};
+TestWindow.getBrowser = function() {
+ return document.getElementById("content");
+};
+TestWindow.getDocument = function() {
+ return document.getElementById("content").contentDocument;
+};
diff --git a/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html
new file mode 100644
index 0000000000..fdecbbdfe1
--- /dev/null
+++ b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test ifranes for view-source forbidden in iframe tests</title>
+</head>
+<body>
+ <iframe id="testIframe"></iframe>
+ <iframe id="refIframe"></iframe>
+</body>
+</html>
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 = <IN>)) {
+ $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 = <IN>)) {
+ $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 @@
+<html>
+<head>
+ <title>
+ generic page
+ </title>
+ </head>
+<body>
+<div id="div1" style="height: 1000px; border: thin solid black;">
+ A generic page which can be used any time a test needs to load an arbitrary page via http.
+ </div>
+</body>
+</html>
diff --git a/docshell/test/chrome/mozFrameType_window.xhtml b/docshell/test/chrome/mozFrameType_window.xhtml
new file mode 100644
index 0000000000..2ddd63ab49
--- /dev/null
+++ b/docshell/test/chrome/mozFrameType_window.xhtml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<window title="Test mozFrameType attribute"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests();">
+
+ <html:iframe id="normalFrame"/>
+ <html:iframe id="typeContentFrame" mozframetype="content"/>
+
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ function runTests() {
+ let opener = window.arguments[0];
+ let SimpleTest = opener.SimpleTest;
+
+ function getDocShellType(frame) {
+ return frame.contentWindow.docShell.itemType;
+ }
+
+ var normalFrame = document.getElementById("normalFrame");
+ var typeContentFrame = document.getElementById("typeContentFrame");
+
+ SimpleTest.is(getDocShellType(normalFrame), Ci.nsIDocShellTreeItem.typeChrome,
+ "normal iframe in chrome document is typeChrome");
+ SimpleTest.is(getDocShellType(typeContentFrame), Ci.nsIDocShellTreeItem.typeContent,
+ "iframe with mozFrameType='content' in chrome document is typeContent");
+
+ SimpleTest.executeSoon(function () {
+ // First focus the parent window and then close this one.
+ SimpleTest.waitForFocus(function() {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+ ww.registerNotification(function windowObs(subject, topic, data) {
+ if (topic == "domwindowclosed") {
+ ww.unregisterNotification(windowObs);
+
+ // Don't start the next test synchronously!
+ SimpleTest.executeSoon(function() {
+ SimpleTest.finish();
+ });
+ }
+ });
+
+ window.close();
+ }, opener);
+ });
+ }
+ ]]></script>
+</window>
+
diff --git a/docshell/test/chrome/red.png b/docshell/test/chrome/red.png
new file mode 100644
index 0000000000..aa9ce25263
--- /dev/null
+++ b/docshell/test/chrome/red.png
Binary files 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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}.xul
+-->
+<window title="Mozilla Bug {BUGNUMBER}"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <title>Test for Bug {BUGNUMBER}</title>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}">
+ Mozilla Bug {BUGNUMBER}</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug {BUGNUMBER} **/
+
+SimpleTest.waitForExplicitFinish();
+window.open("bug{BUGNUMBER}_window.xul", "bug{BUGNUMBER}",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runNextTest);
+
+var TEST_URL = "http://mochi.test:8888/tests/docshell/test/chrome/allowContentRetargeting.sjs";
+
+function runNextTest() {
+ var test = tests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return;
+ }
+ test();
+}
+
+var tests = [
+
+ // Set allowContentRetargeting = false, load a downloadable URL, verify the
+ // downloadable stops loading.
+ function basic() {
+ var iframe = insertIframe();
+ iframe.contentWindow.docShell.allowContentRetargeting = false;
+ loadIframe(iframe);
+ },
+
+ // Set allowContentRetargeting = false on parent docshell, load a downloadable
+ // URL, verify the downloadable stops loading.
+ function inherit() {
+ var docshell = window.docShell;
+ docshell.allowContentRetargeting = false;
+ loadIframe(insertIframe());
+ },
+];
+
+function insertIframe() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ return iframe;
+}
+
+function loadIframe(iframe) {
+ iframe.setAttribute("src", TEST_URL);
+ iframe.contentWindow.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+}
+
+var progressListener = {
+ onStateChange(webProgress, req, flags, status) {
+ if (!(flags & Ci.nsIWebProgressListener.STATE_STOP))
+ return;
+ is(Components.isSuccessCode(status), false,
+ "Downloadable should have failed to load");
+ document.querySelector("iframe").remove();
+ runNextTest();
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
+};
+
+ </script>
+</head>
+<body>
+<p id="display">
+</p>
+</body>
+</html>
diff --git a/docshell/test/chrome/test_bug112564.xhtml b/docshell/test/chrome/test_bug112564.xhtml
new file mode 100644
index 0000000000..33fd187eb2
--- /dev/null
+++ b/docshell/test/chrome/test_bug112564.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=112564
+-->
+<window title="Mozilla Bug 112564"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=112564">Mozilla Bug 112564</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 112564 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug112564_window.xhtml", "bug112564",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=113934
+-->
+<window title="Mozilla Bug 113934"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=113934"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug113934_window.xhtml?content", "bug113934",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug215405.xhtml b/docshell/test/chrome/test_bug215405.xhtml
new file mode 100644
index 0000000000..e40d1d046a
--- /dev/null
+++ b/docshell/test/chrome/test_bug215405.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=215405
+-->
+<window title="Mozilla Bug 215405"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=215405">Mozilla Bug 215405</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 215405 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug215405_window.xhtml", "bug215405",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug293235.xhtml b/docshell/test/chrome/test_bug293235.xhtml
new file mode 100644
index 0000000000..5b0f03f1e9
--- /dev/null
+++ b/docshell/test/chrome/test_bug293235.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=293235.xul
+-->
+<window title="Mozilla Bug 293235"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=293235">
+ Mozilla Bug 293235</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 293235 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug293235_window.xhtml", "bug293235",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug294258.xhtml b/docshell/test/chrome/test_bug294258.xhtml
new file mode 100644
index 0000000000..97588b6ab2
--- /dev/null
+++ b/docshell/test/chrome/test_bug294258.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=294258.xul
+-->
+<window title="Mozilla Bug 294258"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=294258">
+ Mozilla Bug 294258</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 294258 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug294258_window.xhtml", "bug294258",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug298622.xhtml b/docshell/test/chrome/test_bug298622.xhtml
new file mode 100644
index 0000000000..56eae4df37
--- /dev/null
+++ b/docshell/test/chrome/test_bug298622.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=298622.xul
+-->
+<window title="Mozilla Bug 298622"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=298622">
+ Mozilla Bug 298622</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 298622 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug298622_window.xhtml", "bug298622",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug301397.xhtml b/docshell/test/chrome/test_bug301397.xhtml
new file mode 100644
index 0000000000..9bcd2f5212
--- /dev/null
+++ b/docshell/test/chrome/test_bug301397.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=301397.xul
+-->
+<window title="Mozilla Bug 301397"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=301397">
+ Mozilla Bug 301397</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 301397 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug301397_window.xhtml", "bug301397",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug303267.xhtml b/docshell/test/chrome/test_bug303267.xhtml
new file mode 100644
index 0000000000..105abff0d9
--- /dev/null
+++ b/docshell/test/chrome/test_bug303267.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=303267.xul
+-->
+<window title="Mozilla Bug 303267"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=303267">Mozilla Bug 303267</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.expectAssertions(0, 1);
+
+/** Test for Bug 303267 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug303267_window.xhtml", "bug303267",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug311007.xhtml b/docshell/test/chrome/test_bug311007.xhtml
new file mode 100644
index 0000000000..b4f13e6d9d
--- /dev/null
+++ b/docshell/test/chrome/test_bug311007.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=311007.xul
+-->
+<window title="Mozilla Bug 311007"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=311007">
+ Mozilla Bug 311007</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 311007 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug311007_window.xhtml", "bug311007",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug321671.xhtml b/docshell/test/chrome/test_bug321671.xhtml
new file mode 100644
index 0000000000..aa1712bf1f
--- /dev/null
+++ b/docshell/test/chrome/test_bug321671.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=321671.xul
+-->
+<window title="Mozilla Bug 321671"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=321671">
+ Mozilla Bug 321671</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 321671 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug321671_window.xhtml", "bug321671",
+ "chrome,width=600,height=600,scrollbars,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug360511.xhtml b/docshell/test/chrome/test_bug360511.xhtml
new file mode 100644
index 0000000000..cbbadcdd45
--- /dev/null
+++ b/docshell/test/chrome/test_bug360511.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=360511.xul
+-->
+<window title="Mozilla Bug 360511"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=360511">
+ Mozilla Bug 360511</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 360511 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug360511_window.xhtml", "bug360511",
+ "chrome,scrollbars,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug364461.xhtml b/docshell/test/chrome/test_bug364461.xhtml
new file mode 100644
index 0000000000..c753f7825b
--- /dev/null
+++ b/docshell/test/chrome/test_bug364461.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364461
+-->
+<window title="Mozilla Bug 364461"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=364461">Mozilla Bug 364461</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 364461 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({
+ "set":[["security.data_uri.block_toplevel_data_uri_navigations", false]]
+}, runTests);
+
+function runTests() {
+ window.openDialog("bug364461_window.xhtml", "bug364461",
+ "chrome,width=600,height=600,noopener", window);
+}
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug396519.xhtml b/docshell/test/chrome/test_bug396519.xhtml
new file mode 100644
index 0000000000..a6faea79ae
--- /dev/null
+++ b/docshell/test/chrome/test_bug396519.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396519
+-->
+<window title="Mozilla Bug 396519"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=396519"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 396519 **/
+
+ SimpleTest.waitForExplicitFinish();
+ window.openDialog("bug396519_window.xhtml", "bug396519",
+ "chrome,width=600,height=600,noopener", window);
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug396649.xhtml b/docshell/test/chrome/test_bug396649.xhtml
new file mode 100644
index 0000000000..eda8e924da
--- /dev/null
+++ b/docshell/test/chrome/test_bug396649.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396649.xul
+-->
+<window title="Mozilla Bug 396649"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src=
+ "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396649">
+ Mozilla Bug 396649</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 396649 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug396649_window.xhtml", "bug396649",
+ "chrome,width=600,height=600,scrollbars,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug428288.html b/docshell/test/chrome/test_bug428288.html
new file mode 100644
index 0000000000..29b90c677d
--- /dev/null
+++ b/docshell/test/chrome/test_bug428288.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428288
+-->
+<head>
+ <title>Test for Bug 428288</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428288">Mozilla Bug 428288</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="target"></iframe>
+ <a id="crashy" target="target" href="about:blank">crash me</a>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 428288 **/
+
+function makeClick() {
+ var event = document.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ document.getElementById("crashy").dispatchEvent(event);
+ return true;
+}
+
+ok(makeClick(), "Crashes if bug 428288 is present");
+
+</script>
+</pre>
+</body>
+</html>
+
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449778
+-->
+<window title="Mozilla Bug 449778"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449778"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug449778_window.xhtml", "bug449778",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449780
+-->
+<window title="Mozilla Bug 449780"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449780"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug449780_window.xhtml", "bug449780",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug453650.xhtml b/docshell/test/chrome/test_bug453650.xhtml
new file mode 100644
index 0000000000..3263e2bfca
--- /dev/null
+++ b/docshell/test/chrome/test_bug453650.xhtml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453650
+-->
+<window title="Mozilla Bug 453650"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 453650 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var iter = runTests();
+ nextTest();
+
+ function* runTests() {
+ var iframe = document.createXULElement("iframe");
+ iframe.style.width = "300px";
+ iframe.style.height = "300px";
+ iframe.setAttribute("src", "data:text/html,<h1 id='h'>hello</h1>");
+
+ document.documentElement.appendChild(iframe);
+ yield whenLoaded(iframe);
+ info("iframe loaded");
+
+ var h1 = iframe.contentDocument.getElementById("h");
+ let myCallback = function() { h1.style.width = "400px"; };
+ info("Calling waitForInterruptibleReflow");
+ yield waitForInterruptibleReflow(iframe.docShell, myCallback);
+ info("got past top-level waitForInterruptibleReflow");
+
+ myCallback = function() { h1.style.width = "300px"; };
+ info("Calling waitForReflow");
+ waitForReflow(iframe.docShell, myCallback);
+ info("got past top-level waitForReflow");
+ yield is(300, h1.offsetWidth, "h1 has correct width");
+
+ SimpleTest.finish();
+ }
+
+ function waitForInterruptibleReflow(docShell,
+ callbackThatShouldTriggerReflow) {
+ waitForReflow(docShell, callbackThatShouldTriggerReflow, true);
+ }
+
+ function waitForReflow(docShell, callbackThatShouldTriggerReflow,
+ interruptible = false) {
+ info("Entering waitForReflow");
+ function done() {
+ info("Entering done (inside of waitForReflow)");
+
+ docShell.removeWeakReflowObserver(observer);
+ SimpleTest.executeSoon(nextTest);
+ }
+
+ var observer = {
+ reflow: function (start, end) {
+ info("Entering observer.reflow");
+ if (interruptible) {
+ ok(false, "expected interruptible reflow");
+ } else {
+ ok(true, "observed uninterruptible reflow");
+ }
+
+ info("times: " + start + ", " + end);
+ ok(start <= end, "reflow start time lower than end time");
+ done();
+ },
+
+ reflowInterruptible: function (start, end) {
+ info("Entering observer.reflowInterruptible");
+ if (!interruptible) {
+ ok(false, "expected uninterruptible reflow");
+ } else {
+ ok(true, "observed interruptible reflow");
+ }
+
+ info("times: " + start + ", " + end);
+ ok(start <= end, "reflow start time lower than end time");
+ done();
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIReflowObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ info("waitForReflow is adding a reflow observer");
+ docShell.addWeakReflowObserver(observer);
+ callbackThatShouldTriggerReflow();
+ }
+
+ function whenLoaded(iframe) {
+ info("entering whenLoaded");
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ SimpleTest.executeSoon(nextTest);
+ });
+ }
+
+ function nextTest() {
+ info("entering nextTest");
+ iter.next();
+ }
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=453650"
+ target="_blank">Mozilla Bug 453650</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug454235.xhtml b/docshell/test/chrome/test_bug454235.xhtml
new file mode 100644
index 0000000000..bbec39afe0
--- /dev/null
+++ b/docshell/test/chrome/test_bug454235.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454235
+-->
+<window title="Mozilla Bug 454235"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=454235"
+ target="_blank">Mozilla Bug 454235</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 454235 **/
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(doTest);
+
+function doTest() {
+ var shownBrowser = document.getElementById("shownBrowser");
+ var hiddenBrowser = document.getElementById("hiddenBrowser");
+ var offScreenBrowser = document.getElementById("offScreenBrowser");
+ var offScreenSubBrowser = offScreenBrowser.contentDocument.getElementById("topBrowser");
+ var offScreenBurriedBrowser = offScreenBrowser.contentDocument.getElementById("burriedBrowser");
+
+ hiddenBrowser.contentWindow.focus();
+ ok(!hiddenBrowser.contentDocument.hasFocus(),"hidden browser is visible");
+
+ offScreenBrowser.docShell.isOffScreenBrowser = true;
+ offScreenBrowser.contentWindow.focus();
+ ok(offScreenBrowser.contentDocument.hasFocus(),"offscreen browser is not visible");
+
+ offScreenSubBrowser.contentWindow.focus();
+ todo(offScreenSubBrowser.contentDocument.hasFocus(),"visible browser in offscreen browser is not visible");
+
+ offScreenBurriedBrowser.contentWindow.focus();
+ ok(!offScreenBurriedBrowser.contentDocument.hasFocus(),"hidden browser in offscreen browser is visible");
+
+ SimpleTest.finish();
+}
+
+
+
+ ]]></script>
+ <box flex="1" style="visibility: hidden; border:5px black solid">
+ <browser style="border:5px blue solid" id="hiddenBrowser" src="bug454235-subframe.xhtml"/>
+ <browser style="border:5px yellow solid" id="offScreenBrowser" src="bug454235-subframe.xhtml"/>
+ </box>
+</window>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=456980
+-->
+<window title="Mozilla Bug 456980"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=456980"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug113934_window.xhtml?chrome", "bug456980",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug565388.xhtml b/docshell/test/chrome/test_bug565388.xhtml
new file mode 100644
index 0000000000..9bc27de16a
--- /dev/null
+++ b/docshell/test/chrome/test_bug565388.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565388
+-->
+<window title="Mozilla Bug 565388"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 565388 **/
+ SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var progressListener = {
+ add: function(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+ },
+
+ finish: function() {
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback();
+ },
+
+ onStateChange: function (webProgress, req, flags, status) {
+ if (req.name.startsWith("data:application/vnd.mozilla.xul")) {
+ if (flags & Ci.nsIWebProgressListener.STATE_STOP)
+ this.finish();
+ }
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsISupportsWeakReference))
+ return this;
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ }
+
+ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .getService(Ci.nsIPrincipal);
+ var webNav = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService)
+ .createWindowlessBrowser(true);
+ var docShell = webNav.docShell;
+ docShell.createAboutBlankContentViewer(systemPrincipal, systemPrincipal);
+ var win = docShell.contentViewer.DOMDocument.defaultView;
+
+ progressListener.add(docShell, function(){
+ is(win.document.documentURI, "data:application/xhtml+xml;charset=utf-8,<window/>");
+ webNav.close();
+ SimpleTest.finish();
+ });
+
+ win.location = "data:application/xhtml+xml;charset=utf-8,<window/>";
+}
+
+addLoadEvent(function onLoad() {
+ test();
+});
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565388"
+ target="_blank">Mozilla Bug 565388</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug582176.xhtml b/docshell/test/chrome/test_bug582176.xhtml
new file mode 100644
index 0000000000..718a500cdc
--- /dev/null
+++ b/docshell/test/chrome/test_bug582176.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582176.xul
+-->
+<window title="Mozilla Bug 582176"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=582176">
+ Mozilla Bug 582176</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 582176 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug582176_window.xhtml", "bug582176",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug608669.xhtml b/docshell/test/chrome/test_bug608669.xhtml
new file mode 100644
index 0000000000..16150eaa87
--- /dev/null
+++ b/docshell/test/chrome/test_bug608669.xhtml
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=608669
+-->
+<window title="Mozilla Bug 608669"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=608669"
+ target="_blank">Mozilla Bug 608669</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+/** Test for Bug 608669 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(nextTest);
+
+gen = doTest();
+
+function nextTest() {
+ gen.next();
+}
+
+let chromeWindow = window.browsingContext.topChromeWindow;
+
+function* doTest() {
+ var notificationCount = 0;
+ var observer = {
+ observe: function(aSubject, aTopic, aData) {
+ is(aTopic, "chrome-document-global-created",
+ "correct topic");
+ is(aData, "null",
+ "correct data");
+ notificationCount++;
+ }
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(observer, "chrome-document-global-created");
+ os.addObserver(observer, "content-document-global-created");
+
+ is(notificationCount, 0, "initial count");
+
+ // create a new window
+ var testWin = chromeWindow.open("", "bug 608669", "chrome,width=600,height=600");
+ testWin.x = "y";
+ is(notificationCount, 1, "after created window");
+
+ // Try loading in the window
+ testWin.location = "bug608669.xhtml";
+ chromeWindow.onmessage = nextTest;
+ yield undefined;
+ is(notificationCount, 1, "after first load");
+ is(testWin.x, "y", "reused window");
+
+ // Try loading again in the window
+ testWin.location = "bug608669.xhtml?x";
+ chromeWindow.onmessage = nextTest;
+ yield undefined;
+ is(notificationCount, 2, "after second load");
+ is("x" in testWin, false, "didn't reuse window");
+
+ chromeWindow.onmessage = null;
+
+ testWin.close();
+
+ os.removeObserver(observer, "chrome-document-global-created");
+ os.removeObserver(observer, "content-document-global-created");
+ SimpleTest.finish();
+}
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug662200.xhtml b/docshell/test/chrome/test_bug662200.xhtml
new file mode 100644
index 0000000000..5af4e200e2
--- /dev/null
+++ b/docshell/test/chrome/test_bug662200.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=662200.xul
+-->
+<window title="Mozilla Bug 662200"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=662200">
+ Mozilla Bug 662200</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 662200 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug662200_window.xhtml", "bug662200",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug690056.xhtml b/docshell/test/chrome/test_bug690056.xhtml
new file mode 100644
index 0000000000..f1b330a3da
--- /dev/null
+++ b/docshell/test/chrome/test_bug690056.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690056
+-->
+<window title="Mozilla Bug 690056"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056"
+ target="_blank">Mozilla Bug 690056</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 690056 **/
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug690056_window.xhtml", "bug690056",
+ "chrome,width=600,height=600,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/docshell/test/chrome/test_bug789773.xhtml b/docshell/test/chrome/test_bug789773.xhtml
new file mode 100644
index 0000000000..377f48eac6
--- /dev/null
+++ b/docshell/test/chrome/test_bug789773.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789773
+-->
+<window title="Mozilla Bug 789773"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=789773"
+ target="_blank">Mozilla Bug 789773</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /* Test for Bug 789773.
+ *
+ * See comment 50 for the situation we're testing against here.
+ *
+ * Note that the failure mode of this test is to hang, and hang the browser on quit.
+ * This is an unfortunate occurance, but that's why we're testing it.
+ */
+ SimpleTest.waitForExplicitFinish();
+
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+ var calledListenerForBrowserChromeURL = false;
+ var testProgressListener = {
+ START_DOC: Ci.nsIWebProgressListener.STATE_START | Ci.nsIWebProgressListener.STATE_IS_DOCUMENT,
+ onStateChange: function(wp, req, stateFlags, status) {
+ let browserChromeFileName = AppConstants.BROWSER_CHROME_URL.split("/").reverse()[0];
+ if (req.name.includes(browserChromeFileName)) {
+ wp.DOMWindow; // Force the lazy creation of a DOM window.
+ calledListenerForBrowserChromeURL = true;
+ }
+ if (req.name.includes("mozilla.html") && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
+ finishTest();
+ },
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsIWebProgressListener))
+ return this;
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ }
+
+ // Add our progress listener
+ var webProgress = Cc['@mozilla.org/docloaderservice;1'].getService(Ci.nsIWebProgress);
+ webProgress.addProgressListener(testProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+
+ // Open the window.
+ var popup = window.open("about:mozilla", "_blank", "width=640,height=400");
+
+ // Wait for the window to load.
+ function finishTest() {
+ webProgress.removeProgressListener(testProgressListener);
+ ok(true, "Loaded the popup window without spinning forever in the event loop!");
+ ok(calledListenerForBrowserChromeURL, "Should have called the progress listener for browser.xhtml");
+ popup.close();
+ SimpleTest.finish();
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/docshell/test/chrome/test_bug846906.xhtml b/docshell/test/chrome/test_bug846906.xhtml
new file mode 100644
index 0000000000..152ab53a6b
--- /dev/null
+++ b/docshell/test/chrome/test_bug846906.xhtml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=846906
+-->
+<window title="Mozilla Bug 846906"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 846906 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var appShellService = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService);
+ ok(appShellService, "Should be able to get app shell service");
+
+ var windowlessBrowser = appShellService.createWindowlessBrowser();
+ ok(windowlessBrowser, "Should be able to create windowless browser");
+
+ ok(windowlessBrowser instanceof Ci.nsIWindowlessBrowser,
+ "Windowless browser should implement nsIWindowlessBrowser");
+
+ var webNavigation = windowlessBrowser.QueryInterface(Ci.nsIWebNavigation);
+ ok(webNavigation, "Windowless browser should implement nsIWebNavigation");
+
+ var interfaceRequestor = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor);
+ ok(interfaceRequestor, "Should be able to query interface requestor interface");
+
+ var docShell = windowlessBrowser.docShell;
+ ok(docShell, "Should be able to get doc shell interface");
+
+ var document = webNavigation.document;
+ ok(document, "Should be able to get document");
+
+ var iframe = document.createXULElement("iframe");
+ ok(iframe, "Should be able to create iframe");
+
+ iframe.onload = function () {
+ ok(true, "Should receive initial onload event");
+
+ iframe.onload = function () {
+ ok(true, "Should receive onload event");
+
+ var contentDocument = iframe.contentDocument;
+ ok(contentDocument, "Should be able to get content document");
+
+ var div = contentDocument.getElementById("div1");
+ ok(div, "Should be able to get element by id");
+
+ var rect = div.getBoundingClientRect();
+ ok(rect, "Should be able to get bounding client rect");
+
+ // xxx: can we do better than hardcoding these values here?
+ is(rect.width, 1024);
+ is(rect.height, 768);
+
+ windowlessBrowser.close();
+
+ // Once the browser is closed, nsIWebNavigation and
+ // nsIInterfaceRequestor methods should no longer be accessible.
+ try {
+ windowlessBrowser.getInterface(Ci.nsIDocShell);
+ ok(false);
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ try {
+ windowlessBrowser.document;
+ ok(false);
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ SimpleTest.finish();
+ };
+ iframe.setAttribute("src", "http://mochi.test:8888/chrome/docshell/test/chrome/bug846906.html");
+ };
+ document.documentElement.appendChild(iframe);
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=846906"
+ target="_blank">Mozilla Bug 846906</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug89419.xhtml b/docshell/test/chrome/test_bug89419.xhtml
new file mode 100644
index 0000000000..6a00080aa6
--- /dev/null
+++ b/docshell/test/chrome/test_bug89419.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=89419.xul
+-->
+<window title="Mozilla Bug 89419"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=89419">
+ Mozilla Bug 89419</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 89419 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug89419_window.xhtml", "bug89419",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+// The default flags we will stick on the docShell - every request made by the
+// docShell should include those flags.
+const TEST_FLAGS = Ci.nsIRequest.LOAD_ANONYMOUS |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING;
+
+var TEST_URL = "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.html";
+
+// These are the requests we expect to see loading TEST_URL into our iframe.
+
+// The test entry-point. The basic outline is:
+// * Create an iframe and set defaultLoadFlags on its docShell.
+// * Add a web progress listener to observe each request as the iframe is
+// loaded, and check that each request has the flags we specified.
+// * Load our test URL into the iframe and wait for the load to complete.
+function test() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var docShell = iframe.contentWindow.docShell;
+ // Add our progress listener - when it notices the top-level document is
+ // complete, the test will end.
+ RequestWatcher.init(docShell, SimpleTest.finish);
+ // Set the flags we care about, then load our test URL.
+ docShell.defaultLoadFlags = TEST_FLAGS;
+ iframe.setAttribute("src", TEST_URL);
+}
+
+// an nsIWebProgressListener that checks all requests made by the docShell
+// have the flags we expect.
+var RequestWatcher = {
+ init(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ // These are the requests we expect to see - initialize each to have a
+ // count of zero.
+ this.requestCounts = {};
+ for (var url of [
+ TEST_URL,
+ // content loaded by the above test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.js",
+ "http://mochi.test:8888/tests/SimpleTest/test.css",
+ "http://mochi.test:8888/tests/docshell/test/chrome/red.png",
+ // the content of an iframe in the test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/generic.html",
+ ]) {
+ this.requestCounts[url] = 0;
+ }
+ },
+
+ // Finalize the test after we detect a completed load. We check we saw the
+ // correct requests and make a callback to exit.
+ finalize() {
+ ok(Object.keys(this.requestCounts).length, "we expected some requests");
+ for (var url in this.requestCounts) {
+ var count = this.requestCounts[url];
+ // As we are looking at all request states, we expect more than 1 for
+ // each URL - 0 or 1 would imply something went wrong - >1 just means
+ // multiple states for each request were recorded, which we don't care
+ // about (we do care they all have the correct flags though - but we
+ // do that in onStateChange)
+ ok(count > 1, url + " saw " + count + " requests");
+ }
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback();
+ },
+
+ onStateChange(webProgress, req, flags, status) {
+ // We are checking requests - if there isn't one, ignore it.
+ if (!req) {
+ return;
+ }
+ // We will usually see requests for 'about:document-onload-blocker' not
+ // have the flag, so we just ignore them.
+ // We also see, eg, resource://gre-resources/loading-image.png, so
+ // skip resource:// URLs too.
+ // We may also see, eg, chrome://global/skin/icons/chevron.svg, so
+ // skip chrome:// URLs too.
+ if (req.name.startsWith("about:") || req.name.startsWith("resource:") ||
+ req.name.startsWith("chrome:") || req.name.startsWith("documentchannel:")) {
+ return;
+ }
+ is(req.loadFlags & TEST_FLAGS, TEST_FLAGS, "request " + req.name + " has the expected flags");
+ this.requestCounts[req.name] += 1;
+ var stopFlags = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+ if (req.name == TEST_URL && (flags & stopFlags) == stopFlags) {
+ this.finalize();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+</script>
+</head>
+</html>
diff --git a/docshell/test/chrome/test_bug92598.xhtml b/docshell/test/chrome/test_bug92598.xhtml
new file mode 100644
index 0000000000..9c3a46866a
--- /dev/null
+++ b/docshell/test/chrome/test_bug92598.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=92598
+-->
+<window title="Mozilla Bug 92598"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=92598">Mozilla Bug 92598</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 92598 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug92598_window.xhtml", "bug92598",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_docRedirect.sjs b/docshell/test/chrome/test_docRedirect.sjs
new file mode 100644
index 0000000000..76b7dcee58
--- /dev/null
+++ b/docshell/test/chrome/test_docRedirect.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ 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..893a492917
--- /dev/null
+++ b/docshell/test/chrome/test_docRedirect.xhtml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1342989
+-->
+<window title="Mozilla Bug 1342989"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ const WEB_PROGRESS_LISTENER_FLAGS =
+ Object.keys(Ci.nsIWebProgressListener).filter(
+ propName => propName.startsWith("STATE_")
+ );
+
+ function bitFlagsToNames(flags, knownNames, intf) {
+ return knownNames.map( (F) => {
+ return (flags & intf[F]) ? F : undefined;
+ }).filter( (s) => !!s );
+ }
+
+ var progressListener = {
+ add: function(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+ },
+
+ finish: function(success) {
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback(success);
+ },
+
+ onStateChange: function (webProgress, req, flags, status) {
+ if (!(flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) &&
+ !(flags & Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT))
+ return;
+
+ var channel = req.QueryInterface(Ci.nsIChannel);
+
+ if (flags & Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT) {
+ SimpleTest.is(channel.URI.host, "example.org",
+ "Should be redirected to example.org (see test_docRedirect.sjs)");
+ this.finish(true);
+ }
+
+ // Fail in case we didn't receive document redirection event.
+ if (flags & Ci.nsIWebProgressListener.STATE_STOP)
+ this.finish(false);
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsISupportsWeakReference))
+ return this;
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+ }
+
+ var webNav = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService).createWindowlessBrowser(true);
+ let docShell = webNav.docShell;
+ let system = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal);
+ docShell.createAboutBlankContentViewer(system, system);
+
+ progressListener.add(docShell, function(success) {
+ webNav.close();
+ SimpleTest.is(success, true, "Received document redirect event");
+ SimpleTest.finish();
+ });
+
+ var win = docShell.contentViewer.DOMDocument.defaultView;
+ win.location = "http://example.com/chrome/docshell/test/chrome/test_docRedirect.sjs"
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342989"
+ target="_blank">Mozilla Bug 1342989</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_mozFrameType.xhtml b/docshell/test/chrome/test_mozFrameType.xhtml
new file mode 100644
index 0000000000..6e8bfd8f85
--- /dev/null
+++ b/docshell/test/chrome/test_mozFrameType.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=769771
+-->
+<window title="Test mozFrameType attribute"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 769771 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+ window.openDialog("mozFrameType_window.xhtml", "mozFrameType",
+ "chrome,width=600,height=600,noopener", window);
+});
+
+]]>
+</script>
+
+</window>
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..3e1d3aba08
--- /dev/null
+++ b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml
@@ -0,0 +1,159 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=624883
+-->
+<window title="Mozilla Bug 624883"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=624883"
+ target="_blank">Mozilla Bug 624883</a>
+ </body>
+
+ <!-- test code goes here -->
+ <iframe type="content" onload="startTest()" src="file_viewsource_forbidden_in_iframe.html"></iframe>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We create a promise that will resolve with the error message
+ // on a network error page load and reject on any other load.
+ function createNetworkErrorMessagePromise(frame) {
+ return new Promise(function(resolve, reject) {
+
+ // Error pages do not fire "load" events, so use a progressListener.
+ var originalDocumentURI = frame.contentDocument.documentURI;
+ var progressListener = {
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ // Make sure nothing other than an error page is loaded.
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) {
+ reject("location change was not to an error page");
+ }
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Wait until the documentURI changes (from about:blank) this should
+ // be the error page URI.
+ var documentURI = frame.contentDocument.documentURI;
+ if (documentURI == originalDocumentURI) {
+ return;
+ }
+
+ aWebProgress.removeProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ var matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI);
+ if (!matchArray) {
+ reject("no network error message found in URI")
+ return;
+ }
+
+ var errorMsg = matchArray[1];
+ resolve(decodeURIComponent(errorMsg));
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
+ "nsISupportsWeakReference"])
+ };
+
+ frame.contentWindow.docShell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION |
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+ });
+ }
+
+ function startTest() {
+ // Get a reference message that we know will be an unknown protocol message,
+ // so we can use it for comparisons in the test cases.
+ var refIframe = window[0].document.getElementById("refIframe");
+ var refErrorPromise = createNetworkErrorMessagePromise(refIframe);
+
+ refErrorPromise.then(
+ function(msg) {
+ window.refErrorMsg = msg;
+ var testIframe = window[0].document.getElementById("testIframe");
+
+ // Run test cases on load of "about:blank", so that the URI always changes
+ // and we can detect this in our Promise.
+ testIframe.onload = runNextTestCase;
+ testIframe.src = "about:blank";
+ },
+ function(reason) {
+ ok(false, "Could not get reference error message", reason);
+ SimpleTest.finish();
+ })
+ .catch(function(e) {
+ ok(false, "Unexpected exception thrown getting reference error message", exception);
+ });
+
+ refIframe.src = "wibble://example.com";
+ }
+
+ function runTestCase(testCase) {
+ var testIframe = window[0].document.getElementById("testIframe");
+ var expectedErrorMsg = window.refErrorMsg.replace("wibble", testCase.expectedProtocolList);
+
+ var testErrorPromise = createNetworkErrorMessagePromise(testIframe);
+ testErrorPromise.then(
+ function(actualErrorMsg) {
+ is(actualErrorMsg, expectedErrorMsg, testCase.desc);
+ testIframe.src = "about:blank";
+ },
+ function(reason) {
+ ok(false, testCase.desc, reason);
+ testIframe.src = "about:blank";
+ })
+ .catch(function(e) {
+ ok(false, testCase.desc + " - unexpected exception thrown", exception);
+ });
+
+ testIframe.src = testCase.protocols + "://example.com/!/";
+ }
+
+ var testCaseIndex = -1;
+ testCases = [
+ {
+ desc: "Test 1: view-source should not be allowed in an iframe",
+ protocols: "view-source:http",
+ expectedProtocolList: "view-source, http"
+ },
+ {
+ desc: "Test 2: jar:view-source should not be allowed in an iframe",
+ protocols: "jar:view-source:http",
+ expectedProtocolList: "jar, view-source, http"
+ },
+ {
+ desc: "Test 3: if invalid protocol first should report before view-source",
+ protocols: "wibble:view-source:http",
+ // Nothing after the invalid protocol gets set as a proper nested URI,
+ // so the list stops there.
+ expectedProtocolList: "wibble"
+ },
+ {
+ desc: "Test 4: if view-source first should report before invalid protocol",
+ protocols: "view-source:wibble:http",
+ expectedProtocolList: "view-source, wibble"
+ }
+ ];
+
+ function runNextTestCase() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runTestCase(testCases[testCaseIndex]);
+ }
+
+ ]]>
+ </script>
+</window>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="{BUGNUMBER}Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest,0);"
+ title="bug {BUGNUMBER} test">
+
+ <script type="application/javascript"
+ src="docshell_helpers.js">
+ </script>
+
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug {BUGNUMBER}:
+ // Description goes here.
+ //
+ function testIterator()
+ {
+ // Test steps go here. See bug303267_window.xhtml for an example.
+
+ // Tell the framework the test is finished. Include the final 'yield'
+ // statement to prevent a StopIteration exception from being thrown.
+ finish();
+ yield undefined;
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/iframesandbox/.eslintrc.js b/docshell/test/iframesandbox/.eslintrc.js
new file mode 100644
index 0000000000..845ed3f013
--- /dev/null
+++ b/docshell/test/iframesandbox/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/mochitest-test"],
+};
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 @@
+<script>function onNav() { parent.parent.postMessage("childIframe", "*"); } window.onload = onNav; window.onhashchange = onNav;</script>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test marquee attribute event handlers in iframe sandbox</title>
+</head>
+<body>
+ <!-- Note that the width here is slightly longer than the contents, to make
+ sure we bounce and finish very quickly. -->
+ <marquee loop="2" width="145" behavior="alternate" truespeed scrolldelay="1"
+ onstart="parent.postMessage(window.name + ' marquee onstart', '*');"
+ onbounce="parent.postMessage(window.name + ' marquee onbounce', '*');"
+ onfinish="parent.postMessage(window.name + ' marquee onfinish', '*');">
+ Will bounce and finish
+ </marquee>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for other auxiliary navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for our auxiliary navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for parent navigation by location tests</title>
+<script>
+ function onNav() {
+ parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+<body>
+ <iframe name="childIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for sibling navigation by location tests</title>
+<script>
+ function onNav() {
+ parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for top navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+<body>
+ <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe>
+ <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+ <iframe name="if3" sandbox="allow-scripts allow-top-navigation"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for top navigation by location tests</title>
+<script>
+ function onBlock() {
+ opener.postMessage({ name: window.name, blocked: true }, "*");
+ }
+
+ function onNav() {
+ opener.postMessage({ name: window.name, blocked: false }, "*");
+ }
+
+ function setOwnHref() {
+ // eslint-disable-next-line no-self-assign
+ location.href = location.href;
+ }
+
+ window.onload = onNav;
+</script>
+</head>
+<body>
+ <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe>
+ <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/mochitest.ini b/docshell/test/iframesandbox/mochitest.ini
new file mode 100644
index 0000000000..f74c23ffb4
--- /dev/null
+++ b/docshell/test/iframesandbox/mochitest.ini
@@ -0,0 +1,24 @@
+[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]
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox child navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testDataUri = "file_child_navigation_by_location.html";
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "childIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'childIframe'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - child navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.parentIframe.eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: cross origin child location.replace should NOT be blocked",
+ script: "window['crossOriginChildIframe'].location.replace(\"" + testDataUri + "\")",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 2: cross origin child location.assign should be blocked",
+ script: "window['crossOriginChildIframe'].location.assign(\"" + testDataUri + "\")",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: same origin child location.assign should NOT be blocked",
+ script: "window['sameOriginChildIframe'].location.assign(\"" + testDataUri + "\")",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 4: cross origin child location.href should NOT be blocked",
+ script: "window['crossOriginChildIframe'].location.href = \"" + testDataUri + "\"",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 5: cross origin child location.hash should be blocked",
+ script: "window['crossOriginChildIframe'].location.hash = 'wibble'",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 6: same origin child location.hash should NOT be blocked",
+ script: "window['sameOriginChildIframe'].location.hash = 'wibble'",
+ shouldBeBlocked: false,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ addLoadEvent(runNextTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="parentIframe" sandbox="allow-scripts allow-same-origin" srcdoc="<iframe name='sameOriginChildIframe'></iframe><iframe name='crossOriginChildIframe' sandbox='allow-scripts'></iframe>"</iframe>
+
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_marquee_event_handlers.html b/docshell/test/iframesandbox/test_marquee_event_handlers.html
new file mode 100644
index 0000000000..80added8ab
--- /dev/null
+++ b/docshell/test/iframesandbox/test_marquee_event_handlers.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1277475
+html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1277475 - html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1277475">Mozilla Bug 1277475</a>
+<p id="display"></p>
+<div id="content">Tests for Bug 1277475</div>
+
+<iframe id="if1" name="if1" src="file_marquee_event_handlers.html"
+ sandbox="allow-same-origin allow-forms allow-top-navigation allow-pointer-lock allow-orientation-lock allow-popups allow-modals allow-popups-to-escape-sandbox">
+</iframe>
+
+<iframe id="if2" name="if2" src="file_marquee_event_handlers.html"
+ sandbox="allow-scripts"></iframe>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var expectedMessages = new Set();
+ var numberOfMessagesExpected = 4;
+ var unexpectedMessages = new Set();
+
+ window.onmessage = function(event) {
+ info(event.data + " message received");
+ if (event.data.startsWith("if2") || event.data == "if1 chaser") {
+ expectedMessages.add(event.data);
+ if (expectedMessages.size == numberOfMessagesExpected) {
+ checkRecievedMessages();
+ }
+ } else {
+ unexpectedMessages.add(event.data);
+ }
+ };
+
+ function checkRecievedMessages() {
+ // Check the expected messages explicitly as a cross-check.
+ ok(expectedMessages.has("if1 chaser"),
+ "if1 chaser message should have been received");
+ ok(expectedMessages.has("if2 marquee onstart"),
+ "if2 marquee onstart should have run in iframe sandbox with allow-scripts");
+ ok(expectedMessages.has("if2 marquee onbounce"),
+ "if2 marquee onbounce should have run in iframe sandbox with allow-scripts");
+ ok(expectedMessages.has("if2 marquee onfinish"),
+ "if2 marquee onfinish should have run in iframe sandbox with allow-scripts");
+
+ unexpectedMessages.forEach(
+ (v) => {
+ ok(false, v + " should NOT have run in iframe sandbox without allow-scripts");
+ }
+ );
+
+ SimpleTest.finish();
+ }
+
+ // If things are working properly the attribute event handlers won't run on
+ // the marquee in if1, so add our own capturing listeners on its window, so we
+ // know when they have fired. (These will run as we are not sandboxed.)
+ var if1FiredEvents = new Set();
+ var if1NumberOfEventsExpected = 3;
+ var if1Win = document.getElementById("if1").contentWindow;
+ if1Win.addEventListener("start", () => { checkMarqueeEvent("start"); }, true);
+ if1Win.addEventListener("bounce", () => { checkMarqueeEvent("bounce"); }, true);
+ if1Win.addEventListener("finish", () => { checkMarqueeEvent("finish"); }, true);
+
+ function checkMarqueeEvent(eventType) {
+ info("if1 event " + eventType + " fired");
+ if1FiredEvents.add(eventType);
+ if (if1FiredEvents.size == if1NumberOfEventsExpected) {
+ // Only send the chasing message after a tick of the event loop to allow
+ // event handlers on the marquee to process.
+ SimpleTest.executeSoon(sendChasingMessage);
+ }
+ }
+
+ function sendChasingMessage() {
+ // Add our own message listener to if1's window and echo back a chasing
+ // message to make sure that any messages from incorrectly run marquee
+ // attribute event handlers should have arrived before it.
+ if1Win.addEventListener("message",
+ (e) => { if1Win.parent.postMessage(e.data, "*"); });
+ if1Win.postMessage("if1 chaser", "*");
+ info("if1 chaser message sent");
+ }
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox other auxiliary navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "otherWindow") {
+ ok(false, "event.data: got '" + event.data + "', expected 'otherWindow'");
+ }
+ ok(false, testCase.desc + " - auxiliary navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.testIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.replace('file_other_auxiliary_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: location.assign on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.assign('file_other_auxiliary_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: location.href on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.href = 'file_other_auxiliary_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: location.hash on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ window.openedWindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+
+ window.onload = function() {
+ window.openedWindow = window.open("file_other_auxiliary_navigation_by_location.html", "otherWindow");
+ };
+</script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation allow-popups"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox our auxiliary navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "ourWindow") {
+ ok(false, "event.data: got '" + event.data + "', expected 'ourWindow'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - auxiliary navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ SpecialPowers.wrap(window.testIframe).eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + SpecialPowers.wrap(e).message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace on auxiliary opened by us should NOT be blocked",
+ script: "openedWindow.location.replace('file_our_auxiliary_navigation_by_location.html')",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 2: location.assign on auxiliary opened by us should be blocked without allow-same-origin",
+ script: "openedWindow.location.assign('file_our_auxiliary_navigation_by_location.html')",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: location.href on auxiliary opened by us should NOT be blocked",
+ script: "openedWindow.location.href = 'file_our_auxiliary_navigation_by_location.html'",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 4: location.hash on auxiliary opened by us should be blocked without allow-same-origin",
+ script: "openedWindow.location.hash = 'wibble'",
+ shouldBeBlocked: true,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SpecialPowers.wrap(window.testIframe).eval("openedWindow.close()");
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+
+ window.onload = function() {
+ SpecialPowers.wrap(window.testIframe).eval("var openedWindow = window.open('file_our_auxiliary_navigation_by_location.html', 'ourWindow')");
+ };
+</script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-popups"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox parent navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "parentIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'parentIframe'");
+ }
+ ok(false, testCase.desc + " - parent navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.parentIframe.childIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: parent.location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.replace('file_parent_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: parent.location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.assign('file_parent_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: parent.location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.href = 'file_parent_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: parent.location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="parentIframe" src="file_parent_navigation_by_location.html"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox sibling navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "siblingIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'siblingIframe'");
+ }
+
+ ok(false, testCase.desc + " - sibling navigation was NOT blocked");
+ runNextTest();
+ };
+
+ try {
+ window.testIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: sibling location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.replace('file_sibling_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: sibling location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.assign('file_sibling_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: sibling location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.href = 'file_sibling_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: sibling location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+<iframe name="siblingIframe" src="file_sibling_navigation_by_location.html"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox top navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testWin;
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "newTop") {
+ ok(false, "event.data: got '" + event.data + "', expected 'newTop'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - top navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ SpecialPowers.wrap(testWin[testCase.iframeName]).eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + SpecialPowers.wrap(e).message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: top.location.replace should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 2: top.location.assign should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: top.location.href should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 4: top.location.pathname should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 5: top.location should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 6: top.location.hash should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 7: top.location.replace should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 8: top.location.assign should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 9: top.location.href should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 10: top.location.pathname should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 11: top.location should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 12: top.location.hash should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 13: top.location.replace should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 14: top.location.assign should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 15: top.location.href should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 16: top.location.pathname should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 17: top.location should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 18: top.location.hash should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ testWin.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+ testWin = window.open("file_top_navigation_by_location.html", "newTop");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox top navigation by location via exotic means tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testWin;
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data.name != "newWindow") {
+ ok(false, "event.data.name: got '" + event.data.name + "', expected 'newWindow'");
+ }
+ var diag = "top navigation was " + (event.data.blocked ? "" : "NOT ") + "blocked";
+ ok((testCase.shouldBeBlocked == event.data.blocked), testCase.desc + " - " + diag);
+ runNextTest();
+ };
+ try {
+ testWin[testCase.iframeName].eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace.call(top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 2: location.replace.bind(top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: Function.bind.call(location.replace, top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 4: location.replace.call(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 5: location.replace.bind(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 6: Function.bind.call(location.replace, top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 7: top.location.href, via setTimeout, should be blocked when sandboxed without allow-top-navigation",
+ script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch (e) { top.onBlock() } }, 0)",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 8: top.location.href, via setTimeout, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch(e) { top.onBlock() } }, 0)",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 9: top.location.href, via eval, should be blocked when sandboxed without allow-top-navigation",
+ script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 10: top.location.href, via eval, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 11: top.location.href, via anonymous function, should be blocked when sandboxed without allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 12: top.location.href, via anonymous function, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 13: top.location.href, via function inserted in top, should be blocked when sandboxed without allow-top-navigation",
+ script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 14: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 15: top.location.href, via function inserted in us by top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.eval('window[\"if1\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 16: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.eval('window[\"if2\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 17: top.location.href, via function in top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.setOwnHref()",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 18: top.location.href, via function in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.setOwnHref()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 19: top.location.href, via eval in top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 20: top.location.href, via eval in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 21: top.location.href, via eval in top calling us, should be blocked when sandboxed without allow-top-navigation",
+ script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if1\"].doTest()');",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 22: top.location.href, via eval in top calling us, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if2\"].doTest()');",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 23: top.location.href, via function bound to top, should be blocked when sandboxed without allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 24: top.location.href, via function bound to top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ testWin.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+ testWin = window.open("file_top_navigation_by_location_exotic.html", "newWindow");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+</body>
+</html>
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 @@
+<html>
+ <body>You should never see this</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onload="setTimeout(function() { window.location = 'bug404548-subframe_window.html'; }, 10)">
+<iframe srcdoc="<body onpagehide='var p = window.parent.opener; var e = window.frameElement; e.parentNode.removeChild(e); if (e.parentNode == null && e.contentWindow == null) { p.firstRemoved = true; }'>">
+</iframe>
+<iframe srcdoc="<body onpagehide='window.parent.opener.secondHidden = true;'>">
+</iframe>
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 @@
+<body onload='window.opener.finishTest()'>
diff --git a/docshell/test/mochitest/bug413310-post.sjs b/docshell/test/mochitest/bug413310-post.sjs
new file mode 100644
index 0000000000..b8d85dd3cf
--- /dev/null
+++ b/docshell/test/mochitest/bug413310-post.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.write("<body onload='window.parent.onloadCount++'>" +
+ request.method + " " +
+ Date.now() +
+ "</body>");
+}
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 @@
+<!DOCTYPE html>
+<html>
+ <body onload="window.parent.onloadCount++">
+ <form action="bug413310-post.sjs" method="POST">
+ </form>
+ </body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119, sub-window</title>
+<body onload="window.opener.windowLoaded();">
+</body>
+</html>
diff --git a/docshell/test/mochitest/bug530396-noref.sjs b/docshell/test/mochitest/bug530396-noref.sjs
new file mode 100644
index 0000000000..1ed5f7aa21
--- /dev/null
+++ b/docshell/test/mochitest/bug530396-noref.sjs
@@ -0,0 +1,20 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Cache-Control", "no-cache");
+ response.write("<body onload='");
+
+ if (!request.hasHeader('Referer')) {
+ response.write("window.parent.onloadCount++;");
+ }
+
+ if (request.queryString == "newwindow") {
+ response.write("if (window.opener) { window.opener.parent.onloadCount++; window.opener.parent.doNextStep(); }");
+ response.write("if (!window.opener) window.close();");
+ response.write("'>");
+ } else {
+ response.write("window.parent.doNextStep();'>");
+ }
+
+ response.write(request.method + " " + Date.now());
+ response.write("</body>");
+}
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 @@
+<!DOCTYPE html>
+<html>
+ <body onload="window.parent.onloadCount++">
+ <a href="bug530396-noref.sjs" rel="noreferrer foo" id="target1">bug530396-noref.sjs</a>
+ <a href="bug530396-noref.sjs?newwindow" rel="nofollow noreferrer" id="target2" target="newwindow">bug530396-noref.sjs with new window</a>
+ </body>
+</html>
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 @@
+<html>
+<head>
+<script>
+ var start = Date.now();
+ window._testing_js_start = Date.now();
+ window["_testing_js_after_" + document.readyState] = start;
+ document.addEventListener("DOMContentLoaded",
+ function() {
+ window._testing_evt_DOMContentLoaded = Date.now();
+ }, true);
+ document.addEventListener("readystatechange", function() {
+ window["_testing_evt_DOM_" + document.readyState] = Date.now();
+ }, true);
+ function recordLoad() {
+ window._testing_evt_load = Date.now();
+ }
+</script>
+</head>
+<body onload="recordLoad()">This document collects time
+for events related to the page load progress.</body>
+</html>
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 @@
+<html><body>This document is redirected to a blank document.</body></html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691547
+-->
+<head>
+ <title>Test for Bug 691547</title>
+</head>
+<body>
+<iframe style="width:95%"></iframe>
+</body>
+</html>
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 @@
+<!doctype html>
+<script>
+ "use strict";
+ let target = window.opener ? window.opener : window.parent;
+
+ onmessage = ({data}) => target.postMessage({}, "*");
+</script>
diff --git a/docshell/test/mochitest/double_submit.sjs b/docshell/test/mochitest/double_submit.sjs
new file mode 100644
index 0000000000..542c81c3e3
--- /dev/null
+++ b/docshell/test/mochitest/double_submit.sjs
@@ -0,0 +1,122 @@
+"use strict";
+
+let self = this;
+
+Cu.import("resource://gre/modules/Timer.jsm");
+
+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`);
+}
+
+function* generateBody(fragments, size) {
+ let result = [];
+ let chunkSize = (size / fragments) | 0;
+ let remaining = size;
+
+ log(`Chunk size ${chunkSize}`)
+ while (remaining > 0) {
+ let data = new Uint8Array(Math.min(remaining, chunkSize));
+ for (let i = 0; i < data.length; ++i) {
+ // Generate a character in the [a-z] range.
+ data[i] = 97 + Math.random() * (123 - 97);
+ }
+
+ yield data;
+ log(`Remaining to chunk ${remaining}`)
+ remaining -= data.length;
+ }
+}
+
+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 delay = parseInt(params.get("delay")) || 0;
+ let delayUntil = now() + delay;
+ log(`Delay until ${delayUntil}`);
+
+ 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 fragments = parseInt(params.get("fragments")) || 1;
+ let size = parseInt(params.get("size")) || 1024;
+
+ let outputStream = new BinaryOutputStream(response.bodyOutputStream);
+
+ let header = "<!doctype html><!-- ";
+ let footer = ` --><script>"use strict"; let target = (opener || parent); target.postMessage('${message}', '*');</script>`;
+
+ log("Set headers")
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", `${size + header.length + footer.length}`, false);
+ response.setStatusLine(request.httpVersion, "200", "OK");
+
+ response.processAsync();
+ log("Write header");
+ response.write(header);
+ log("Write body")
+ for (let data of generateBody(fragments, size)) {
+ delay = Math.max(0, delayUntil - now())
+ log(`Delay sending fragment for ${delay / fragments}`);
+ let failed = false;
+ await new Promise(resolve => {
+ setTimeout(() => {
+ try {
+ outputStream.writeByteArray(data, data.length);
+ } catch (e) {
+ log(e.message);
+ failed = true;
+ }
+ resolve();
+ }, delay / fragments);
+ });
+
+ if (failed) {
+ log("Stopped sending data");
+ break;
+ }
+
+ fragments = Math.max(--fragments, 1);
+ log(`Fragments left ${fragments}`)
+ }
+
+ log("Write footer")
+ response.write(footer);
+
+ response.finish();
+}
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 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<script>
+ if (location.hash == "#target") {
+ parent.postMessage("haveHash", "*");
+ } else {
+ document.addEventListener("DOMContentLoaded", function() {
+ document.open();
+ document.write("<!DOCTYPE html><html style='height: 100%'><body style='height: 100%'><div style='height: 200%'></div><div id='target'></div></body></html>");
+ document.close();
+ // Notify parent via postMessage, since otherwise exceptions will not get
+ // caught by its onerror handler.
+ parent.postMessage("doTest", "*");
+ });
+ }
+</script>
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 @@
+<html><body>
+ Popup 1
+ <script type="application/javascript">
+ var bc = new BroadcastChannel("bug646641_1");
+ window.onload = () => {
+ bc.postMessage({ message: "childLoad", num: 1 })
+ }
+
+ window.onpageshow = () => {
+ bc.postMessage({ message: "childPageshow", num: 1 })
+ }
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ if (msg == "pushState") {
+ history.pushState("", "", "");
+ location = "file_bfcache_plus_hash_2.html";
+ } else if (msg == "close") {
+ bc.postMessage({ message: "closed" });
+ bc.close();
+ window.close();
+ }
+ }
+ </script>
+</body></html>
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 @@
+<html><body>
+ Popup 2
+ <script type="application/javascript">
+ var bc = new BroadcastChannel("bug646641_2");
+ window.onload = () => {
+ bc.postMessage({ message: "childLoad", num: 2 })
+ requestAnimationFrame(() => bc.postMessage({message: "childPageshow", num: 2}));
+ }
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ if (msg == "go-2") {
+ history.go(-2);
+ bc.close();
+ }
+ }
+ </script>
+</body></html>
diff --git a/docshell/test/mochitest/file_bug1121701_1.html b/docshell/test/mochitest/file_bug1121701_1.html
new file mode 100644
index 0000000000..0e755b3902
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1121701_1.html
@@ -0,0 +1 @@
+<script>window.onpageshow = function(e) { opener.child1PageShow(e); }; </script>
diff --git a/docshell/test/mochitest/file_bug1121701_2.html b/docshell/test/mochitest/file_bug1121701_2.html
new file mode 100644
index 0000000000..235f9e4e78
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1121701_2.html
@@ -0,0 +1 @@
+<script>window.onpageshow = function(e) { opener.child2PageShow(e); }; </script>
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 @@
+<html>
+<head>
+<style>
+body, html {
+ height: 100%;
+}
+.spacer {
+ height: 80%;
+}
+</style>
+</head>
+<body onload='(parent || opener).childLoad()'>
+
+<div class="spacer"></div>
+<div id="content">content</div>
+<div class="spacer"></div>
+
+</body>
+</html>
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 @@
+<div style='height: 9000px;'></div>
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 @@
+<html>
+ <head>
+ <script>
+ function go() {
+ var a = window.history.state;
+ window.history.replaceState(a, "", "1");
+ var ok = opener.ok;
+ var SimpleTest = opener.SimpleTest;
+ ok("Addition of history in unload did not crash browser");
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onunload="go()">
+ </body>
+</html>
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 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ function hashchange() {
+ parent.onIframeHashchange();
+ }
+
+ function load() {
+ parent.onIframeLoad();
+ }
+
+ function scroll() {
+ parent.onIframeScroll();
+ }
+ </script>
+</head>
+
+<body onscroll="scroll()" onload="load()" onhashchange="hashchange()">
+<a href="#link1" id="link1">link1</a>
+<!-- Our parent loads us in an iframe with height 100px, so this spacer ensures
+ that switching between #link1 and #link2 causes us to scroll -->
+<div style="height:200px;"></div>
+<a href="#link2" id="link2">link2</a>
+</body>
+</html>
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 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ function hashchange(e) {
+ // pass the event back to the parent so it can check its properties.
+ parent.gSampleEvent = e;
+
+ parent.statusMsg("Hashchange in 2.");
+ parent.onIframeHashchange();
+ }
+
+ function load() {
+ parent.statusMsg("Loading 2.");
+ parent.onIframeLoad();
+ }
+ </script>
+</head>
+
+<frameset onload="load()" onhashchange="hashchange(event)">
+ <frame src="about:blank" />
+</frameset>
+</html>
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 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ // Notify our parent if we have a hashchange and once we're done loading.
+ window.addEventListener("hashchange", parent.onIframeHashchange);
+
+ window.addEventListener("DOMContentLoaded", function() {
+ // This also should trigger a hashchange, becuase the readystate is
+ // "interactive", not "complete" during DOMContentLoaded.
+ window.location.hash = "2";
+ });
+
+ </script>
+</head>
+
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug475636.sjs b/docshell/test/mochitest/file_bug475636.sjs
new file mode 100644
index 0000000000..38da6a6a50
--- /dev/null
+++ b/docshell/test/mochitest/file_bug475636.sjs
@@ -0,0 +1,90 @@
+jsURL = "javascript:" + escape('window.parent.postMessage("JS uri ran", "*");\
+return \'\
+<script>\
+window.parent.postMessage("Able to access private: " +\
+ window.parent.private, "*");\
+</script>\'');
+dataURL = "data:text/html," + escape('<!DOCTYPE HTML>\
+<script>\
+try {\
+ window.parent.postMessage("Able to access private: " +\
+ window.parent.private, "*");\
+}\
+catch (e) {\
+ window.parent.postMessage("pass", "*");\
+}\
+</script>');
+
+tests = [
+// Plain document should work as normal
+'<!DOCTYPE HTML>\
+<script>\
+try {\
+ window.parent.private;\
+ window.parent.postMessage("pass", "*");\
+}\
+catch (e) {\
+ window.parent.postMessage("Unble to access private", "*");\
+}\
+</script>',
+
+// refresh to plain doc
+{ refresh: "file_bug475636.sjs?1",
+ doc: '<!DOCTYPE HTML>' },
+
+// meta-refresh to plain doc
+'<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=file_bug475636.sjs?1">\
+</head>',
+
+// refresh to data url
+{ refresh: dataURL,
+ doc: '<!DOCTYPE HTML>' },
+
+// meta-refresh to data url
+'<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=' + dataURL + '">\
+</head>',
+
+// refresh to js url should not be followed
+{ refresh: jsURL,
+ doc:
+'<!DOCTYPE HTML>\
+<script>\
+setTimeout(function() {\
+ window.parent.postMessage("pass", "*");\
+}, 2000);\
+</script>' },
+
+// meta refresh to js url should not be followed
+'<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=' + jsURL + '">\
+</head>\
+<script>\
+setTimeout(function() {\
+ window.parent.postMessage("pass", "*");\
+}, 2000);\
+</script>'
+];
+
+
+function handleRequest(request, response)
+{
+ dump("@@@@@@@@@hi there: " + request.queryString + "\n");
+ test = tests[parseInt(request.queryString, 10) - 1];
+ response.setHeader("Content-Type", "text/html");
+
+ if (!test) {
+ response.write('<script>parent.postMessage("done", "*");</script>');
+ }
+ 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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test inner frame for bug 509055</title>
+</head>
+<body onhashchange="hashchangeCallback()">
+ file_bug509055.html
+</body>
+</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 @@
+<!DOCTYPE HTML>
+<title>Used in test for bug 511449</title>
+<input type="text" id="input">
+<script type="text/javascript">
+ document.getElementById("input").focus();
+</script>
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 @@
+<html>
+ <head>
+ <script>
+ // <!--
+ function test() {
+ document.open();
+ document.write(
+ `<html>
+ <body onload='opener.documentWriteLoad(); rel();'>
+ <a href='foo.html'>foo</a>
+ <script>
+ function rel() { setTimeout('location.reload()', 0); }
+ <\/script>
+ </body>
+ </html>`
+ );
+ document.close();
+ }
+ // -->
+ </script>
+ </head>
+ <body onload="setTimeout('test()', 0)">
+ Test for bug 540462
+ </body>
+</html>
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 @@
+<html>
+<body onload='parent.page1Load();'>
+file_bug580069_1.html
+
+<form id='form' action='file_bug580069_2.sjs' method='POST'></form>
+
+</body>
+</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..0c76c1f16e
--- /dev/null
+++ b/docshell/test/mochitest/file_bug580069_2.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.write('<html><body onload=\'parent.page2Load("' + request.method + '")\'>file_bug580069_2.sjs</body></html>');
+}
diff --git a/docshell/test/mochitest/file_bug590573_1.html b/docshell/test/mochitest/file_bug590573_1.html
new file mode 100644
index 0000000000..850d418bde
--- /dev/null
+++ b/docshell/test/mochitest/file_bug590573_1.html
@@ -0,0 +1,8 @@
+<html>
+<body onpopstate='opener.page1Popstate();' onload='opener.page1Load();'
+ onpageshow='opener.page1PageShow();'>
+
+<div style='height:10000px' id='div1'>This is a very tall div.</div>
+
+</body>
+</html>
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 @@
+<html>
+<body onpopstate='opener.page2Popstate();' onload='opener.page2Load();'
+ onpageshow='opener.page2PageShow();'>
+
+<div style='height:300%' id='div2'>The second page also has a big div.</div>
+
+</body>
+</html>
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 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body>Should show</body>
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 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body></body>
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 @@
+<html>
+<body>
+Nothing to see here; just an empty page.
+</body>
+</html>
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 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body>Should show</body>
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 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body></body>
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 @@
+<html>
+<body onhashchange='hashchange()' onload='load()' onpopstate='popstate()'>
+
+<script>
+function hashchange() {
+ var f = (opener || parent).childHashchange;
+ if (f)
+ f();
+}
+
+function load() {
+ var f = (opener || parent).childLoad;
+ if (f)
+ f();
+}
+
+function popstate() {
+ var f = (opener || parent).childPopstate;
+ if (f)
+ f();
+}
+</script>
+
+Not much to see here...
+</body>
+</html>
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 @@
+<html>
+<body onload='(parent || opener).childLoad()'>
+
+<div style='height:500px; background:yellow'>
+<a id='#top'>Top of the page</a>
+</div>
+
+<div id='bottom'>
+<a id='#bottom'>Bottom of the page</a>
+</div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug660404 b/docshell/test/mochitest/file_bug660404
new file mode 100644
index 0000000000..0737a5c4d3
--- /dev/null
+++ b/docshell/test/mochitest/file_bug660404
@@ -0,0 +1,5 @@
+--testingtesting
+Content-Type: text/html
+
+<script>opener.finishTest();</script>
+--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..34e4d66c27
--- /dev/null
+++ b/docshell/test/mochitest/file_bug660404-1.html
@@ -0,0 +1 @@
+<script>window.onload = function() { opener.continueTest(); };</script>
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 @@
+<html>
+<body onload='(parent || opener).childLoad()'>
+
+<div style='height:500px; background:yellow'>
+<a id='#top'>Top of the page</a>
+</div>
+
+<div id='bottom'>
+<a id='#bottom'>Bottom of the page</a>
+</div>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for Bug 668513</title>
+<script>
+ var SimpleTest = opener.SimpleTest;
+ var ok = opener.ok;
+ var is = opener.is;
+
+ function finish() {
+ SimpleTest.finish();
+ close();
+ }
+
+ function onload_test() {
+ var win = frames[0];
+ ok(win.performance, "Window.performance should be defined");
+ ok(win.performance.navigation, "Window.performance.navigation should be defined");
+ var navigation = win.performance && win.performance.navigation;
+ if (navigation === undefined) {
+ // avoid script errors
+ finish();
+ return;
+ }
+
+ // do this with a timeout to see the visuals of the navigations.
+ setTimeout(nav_frame, 100);
+ }
+
+ var step = 1;
+ function nav_frame() {
+ var navigation_frame = frames[0];
+ var navigation = navigation_frame.performance.navigation;
+ switch (step) {
+ case 1:
+ {
+ navigation_frame.location.href = "bug570341_recordevents.html";
+ step++;
+ break;
+ }
+ case 2:
+ {
+ is(navigation.type, navigation.TYPE_NAVIGATE,
+ "Expected window.performance.navigation.type == TYPE_NAVIGATE");
+ navigation_frame.history.back();
+ step++;
+ break;
+ }
+ case 3:
+ {
+ is(navigation.type, navigation.TYPE_BACK_FORWARD,
+ "Expected window.performance.navigation.type == TYPE_BACK_FORWARD");
+ step++;
+ navigation_frame.history.forward();
+ break;
+ }
+ case 4:
+ {
+ is(navigation.type, navigation.TYPE_BACK_FORWARD,
+ "Expected window.performance.navigation.type == TYPE_BACK_FORWARD");
+ navigation_frame.location.href = "bug668513_redirect.html";
+ step++;
+ break;
+ }
+ case 5:
+ {
+ is(navigation.type, navigation.TYPE_NAVIGATE,
+ "Expected timing.navigation.type as TYPE_NAVIGATE");
+ is(navigation.redirectCount, 1,
+ "Expected navigation.redirectCount == 1 on an server redirected navigation");
+
+ var timing = navigation_frame.performance && navigation_frame.performance.timing;
+ if (timing === undefined) {
+ // avoid script errors
+ finish();
+ break;
+ }
+ ok(timing.navigationStart > 0, "navigationStart should be > 0");
+ var sequence = ["navigationStart", "redirectStart", "redirectEnd", "fetchStart"];
+ for (var j = 1; j < sequence.length; ++j) {
+ var prop = sequence[j];
+ var prevProp = sequence[j - 1];
+ ok(timing[prevProp] <= timing[prop],
+ ["Expected ", prevProp, " to happen before ", prop,
+ ", got ", prevProp, " = ", timing[prevProp],
+ ", ", prop, " = ", timing[prop]].join(""));
+ }
+ step++;
+ finish();
+ break;
+ }
+ }
+ }
+</script>
+</head>
+<body>
+<div id="frames">
+<iframe name="child0" onload="onload_test();" src="navigation/blank.html"></iframe>
+</div>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug669671.sjs b/docshell/test/mochitest/file_bug669671.sjs
new file mode 100644
index 0000000000..b6fd1ec7c5
--- /dev/null
+++ b/docshell/test/mochitest/file_bug669671.sjs
@@ -0,0 +1,14 @@
+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('<html><body onload="opener.onChildLoad()" ' +
+ 'onunload="parseInt(\'0\')">' +
+ count + '</body></html>');
+}
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 @@
+<script>location.hash = "";</script>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <style type='text/css'>
+ a { color: black; }
+ a:target { color: red; }
+ </style>
+</head>
+
+<body onload='(opener || parent).popupLoaded()'>
+
+<a id='a' href='#a'>link</a>
+<a id='b' href='#b'>link2</a>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<!-- Just need an empty file here, as long as it's served over HTTP -->
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 @@
+<html>
+<body onload="opener.popupLoaded()">file_bug728939</body>
+</html>
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 @@
+<script>
+ window.onload = () => {
+ opener.postMessage("initial", "*");
+ };
+</script>
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 @@
+<script>
+ window.onload = () => {
+ opener.postMessage("second", "*");
+ };
+</script>;
diff --git a/docshell/test/mochitest/file_compressed_multipart b/docshell/test/mochitest/file_compressed_multipart
new file mode 100644
index 0000000000..3c56226951
--- /dev/null
+++ b/docshell/test/mochitest/file_compressed_multipart
Binary files 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_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 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<iframe id="iframe" src="historyframes.html"></iframe>
+<script type="application/javascript">
+
+var SimpleTest = window.opener.SimpleTest;
+var is = window.opener.is;
+
+function done() {
+ window.opener.done();
+}
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_history_length_during_pageload.html b/docshell/test/mochitest/file_history_length_during_pageload.html
new file mode 100644
index 0000000000..22fc0a0162
--- /dev/null
+++ b/docshell/test/mochitest/file_history_length_during_pageload.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <script>
+ function loadNextPage() {
+ if (location.search.includes("testAsyncLength=true")) {
+ var shell = SpecialPowers.wrap(window).docShell;
+ var shistory = SpecialPowers.do_QueryInterface(shell, "nsIWebNavigation").sessionHistory;
+ shistory.asyncHistoryLength = true;
+ }
+ opener.postMessage({ initialLength: history.length}, "*");
+ location.href = 'file_history_length_during_pageload_2.html';
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(loadNextPage);">
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_history_length_during_pageload_2.html b/docshell/test/mochitest/file_history_length_during_pageload_2.html
new file mode 100644
index 0000000000..28e5089e13
--- /dev/null
+++ b/docshell/test/mochitest/file_history_length_during_pageload_2.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <script>
+ opener.postMessage({ length: history.length}, "*");
+
+ function done() {
+ // Ensure history.length doesn't change after load event has fired.
+ opener.postMessage({ length: history.length}, "*");
+
+ var shell = SpecialPowers.wrap(window).docShell;
+ var shistory = SpecialPowers.do_QueryInterface(shell, "nsIWebNavigation").sessionHistory;
+ if (shistory.asyncHistoryLength) {
+ shistory.asyncHistoryLength = false;
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(done);">
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<script>
+ document.addEventListener("DOMContentLoaded", function() {
+ document.open();
+ document.write("<!DOCTYPE html>New Document here");
+ document.close();
+ // Notify parent via postMessage, since otherwise exceptions will not get
+ // caught by its onerror handler.
+ parent.postMessage("doTest", "*");
+ });
+</script>
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 @@
+<html>
+ <head>
+ <script>
+ function loaded() {
+ addEventListener("message", ({ data }) => {
+ document.getElementById("form").action = data;
+ document.getElementById("button").click();
+ }, { once: true });
+ opener.postMessage("loaded", "*");
+ }
+ </script>
+ </head>
+ <body onload="loaded();">
+ <form id="form" method="POST">
+ <input id="button" type="submit" />
+ </form>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/form_submit.sjs b/docshell/test/mochitest/form_submit.sjs
new file mode 100644
index 0000000000..8f22caf4e6
--- /dev/null
+++ b/docshell/test/mochitest/form_submit.sjs
@@ -0,0 +1,40 @@
+"use strict";
+
+Cu.import("resource://gre/modules/Timer.jsm");
+
+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") {
+ message = "bad";
+ } 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('<script>"use strict"; let target = opener ? opener : parent; target.postMessage("done", "*");</script>');
+ 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..9953f28989
--- /dev/null
+++ b/docshell/test/mochitest/form_submit_redirect.sjs
@@ -0,0 +1,10 @@
+"use strict";
+
+async function handleRequest(request, response) {
+ if (request.method !== "POST") {
+ message = "bad";
+ } else {
+ response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+ response.setHeader("Location", request.getHeader("referer"));
+ }
+}
diff --git a/docshell/test/mochitest/historyframes.html b/docshell/test/mochitest/historyframes.html
new file mode 100644
index 0000000000..846cdd9f9d
--- /dev/null
+++ b/docshell/test/mochitest/historyframes.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+</head>
+<body onload="SimpleTest.executeSoon(run_test)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<div id="content">
+ <iframe id="iframe" src="start_historyframe.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+var testWin = window.opener ? window.opener : window.parent;
+
+var SimpleTest = testWin.SimpleTest;
+function is() { testWin.is.apply(testWin, arguments); }
+
+var gFrame = null;
+
+function gState() {
+ return location.hash.replace(/^#/, "");
+}
+
+function waitForLoad(aCallback) {
+ function listener() {
+ gFrame.removeEventListener("load", listener);
+ SimpleTest.executeSoon(aCallback);
+ }
+
+ gFrame.addEventListener("load", listener);
+}
+
+function loadContent(aURL, aCallback) {
+ waitForLoad(aCallback);
+
+ gFrame.src = aURL;
+}
+
+function getURL() {
+ return gFrame.contentDocument.documentURI;
+}
+
+function getContent() {
+ return gFrame.contentDocument.getElementById("text").textContent;
+}
+
+var BASE_URI = "http://mochi.test:8888/tests/docshell/test/mochitest/";
+var START = BASE_URI + "start_historyframe.html";
+var URL1 = BASE_URI + "url1_historyframe.html";
+var URL2 = BASE_URI + "url2_historyframe.html";
+
+function run_test() {
+ window.location.hash = "START";
+
+ gFrame = document.getElementById("iframe");
+
+ test_basic_inner_navigation();
+}
+
+function end_test() {
+ testWin.done();
+}
+
+var gTestContinuation = null;
+function continueAsync() {
+ setTimeout(function() { gTestContinuation.next(); })
+}
+
+function test_basic_inner_navigation() {
+ // Navigate the inner frame a few times
+ loadContent(URL1, function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ loadContent(URL2, function() {
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ // Test that history is working
+ waitForLoad(function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ waitForLoad(function() {
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ gTestContinuation = test_state_navigation();
+ gTestContinuation.next();
+ });
+ window.history.forward();
+ });
+ window.history.back();
+ });
+ });
+}
+
+function* test_state_navigation() {
+ window.location.hash = "STATE1";
+
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.location.hash = "STATE2";
+
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ is(gState(), "STATE1", "State should be correct after going back");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.forward();
+ yield;
+
+ is(gState(), "STATE2", "State should be correct after going forward");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ is(gState(), "START", "State should be correct");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ waitForLoad(function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ waitForLoad(function() {
+ is(gState(), "START", "State should be correct");
+ is(getURL(), START, "URL should be correct");
+ is(getContent(), "Start", "Page should be correct");
+
+ end_test();
+ });
+
+ window.history.back();
+
+ is(gState(), "START", "State should be correct after going back twice");
+ });
+
+ window.history.back();
+ continueAsync();
+ yield;
+ is(gState(), "START", "State should be correct");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/mochitest.ini b/docshell/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..402da4f50c
--- /dev/null
+++ b/docshell/test/mochitest/mochitest.ini
@@ -0,0 +1,145 @@
+[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
+prefs =
+ gfx.font_rendering.fallback.async=false
+
+[test_anchor_scroll_after_document_open.html]
+[test_bfcache_plus_hash.html]
+skip-if = fission # bug 1665165
+[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]
+[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]
+[test_bug529119-2.html]
+[test_bug530396.html]
+support-files = bug530396-noref.sjs bug530396-subframe.html
+[test_bug540462.html]
+skip-if = toolkit == 'android' && debug && !is_fennec
+[test_bug551225.html]
+[test_bug570341.html]
+skip-if = (verify && !debug && (os == 'win'))
+[test_bug580069.html]
+skip-if = (verify && !debug && (os == 'win'))
+[test_bug590573.html]
+skip-if = fission # Bug 1667389
+[test_bug598895.html]
+[test_bug634834.html]
+[test_bug637644.html]
+[test_bug640387_1.html]
+[test_bug640387_2.html]
+[test_bug653741.html]
+[test_bug660404.html]
+skip-if = fission # bug 1666449
+[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]
+skip-if = fission # bug 1666449
+[test_bug1151421.html]
+[test_bug1186774.html]
+[test_bug1450164.html]
+[test_close_onpagehide_by_history_back.html]
+[test_close_onpagehide_by_window_close.html]
+[test_compressed_multipart.html]
+[test_forceinheritprincipal_overrule_owner.html]
+[test_framedhistoryframes.html]
+support-files = file_framedhistoryframes.html
+[test_history_length_during_pageload.html]
+support-files =
+ file_history_length_during_pageload.html
+ file_history_length_during_pageload_2.html
+[test_pushState_after_document_open.html]
+[test_navigate_after_pagehide.html]
+[test_redirect_history.html]
+support-files =
+ file_redirect_history.html
+ form_submit_redirect.sjs
+[test_windowedhistoryframes.html]
+skip-if = (!debug && os == 'android') # Bug 1573892
+[test_triggeringprincipal_location_seturi.html]
+[test_bug1507702.html]
+[test_bug1645781.html]
+support-files =
+ form_submit.sjs
+[test_double_submit.html]
+support-files =
+ clicker.html
+ double_submit.sjs
+[test_iframe_srcdoc_to_remote.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 @@
+<!doctype html>
+<script>
+ "use strict";
+ let target = (window.opener || window.parent);
+ target.postMessage("ping", "*");
+</script>
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 @@
+<p id='text'>Start</p>
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..6e415e3cbe
--- /dev/null
+++ b/docshell/test/mochitest/test_anchor_scroll_after_document_open.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=881487
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 881487</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 881487 **/
+ SimpleTest.waitForExplicitFinish();
+ // Child needs to invoke us, otherwise our onload will fire before the child
+ // has done the write/close bit.
+ var gotOnload = false;
+ addLoadEvent(function() {
+ gotOnload = true;
+ });
+ onmessage = function handleMessage(msg) {
+ if (msg.data == "doTest") {
+ if (!gotOnload) {
+ addLoadEvent(function() { handleMessage(msg); });
+ return;
+ }
+ frames[0].onscroll = function() {
+ ok(true, "Got a scroll event");
+ SimpleTest.finish();
+ };
+ frames[0].location.hash = "#target";
+ return;
+ }
+ if (msg.data == "haveHash") {
+ ok(false, "Child got reloaded");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=881487">Mozilla Bug 881487</a>
+<p id="display">
+ <!-- iframe goes here so it can scroll -->
+<iframe src="file_anchor_scroll_after_document_open.html"></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
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..b13f8a3492
--- /dev/null
+++ b/docshell/test/mochitest/test_bfcache_plus_hash.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646641
+-->
+<head>
+ <title>Test for Bug 646641</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646641">Mozilla Bug 646641</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 646641 **/
+
+/**
+ * Steps:
+ * - Main page (this one) opens file_bfcache_plus_hash_1.html (subpage 1)
+ * - subpage 1 sends msg { "childLoad", 1 }
+ * - subpage 1 sends msg { "childPageshow", 1 }
+ * - main page sends message "pushState"
+ * - subpage 1 does pushState()
+ * - subpage 1 navigates to file_bfcache_plus_hash_2.html (subpage 2)
+ * - subpage 2 sends msg { "childLoad", 2 }
+ * - subpage 2 sends msg { "childPageshow", 2 }
+ * - main page sends msg "go-2"
+ * - subpage 2 goes back two history entries
+ * - subpage 1 sends msg { "childPageshow", 1 }
+ * - Receiving only this msg shows we have retrieved the document from bfcache
+ * - main page sends msg "close"
+ * - subpage 1 sends msg "closed"
+ */
+SimpleTest.waitForExplicitFinish();
+
+function debug(msg) {
+ // Wrap dump so we can turn debug messages on and off easily.
+ dump(msg + "\n");
+}
+
+var expectedLoadNum = -1;
+var expectedPageshowNum = -1;
+
+function waitForLoad(n) {
+ debug("Waiting for load " + n);
+ expectedLoadNum = n;
+}
+
+function waitForShow(n) {
+ debug("Waiting for show " + n);
+ expectedPageshowNum = n;
+}
+
+function* test() {
+ window.open("file_bfcache_plus_hash_1.html", "", "noopener");
+ waitForLoad(1);
+ waitForShow(1);
+ yield undefined;
+ yield undefined;
+
+ bc1.postMessage("pushState");
+
+ waitForLoad(2);
+ waitForShow(2);
+ yield undefined;
+ yield undefined;
+
+ // Now go back 2. The first page should be retrieved from bfcache.
+ bc2.postMessage("go-2");
+ waitForShow(1);
+ yield undefined;
+
+ bc1.postMessage("close");
+}
+
+var bc1 = new BroadcastChannel("bug646641_1");
+var bc2 = new BroadcastChannel("bug646641_2");
+bc1.onmessage = (msgEvent) => {
+ var msg = msgEvent.data.message;
+ var n = msgEvent.data.num;
+ if (msg == "childLoad") {
+ if (n == expectedLoadNum) {
+ debug("Got load " + n);
+ expectedLoadNum = -1;
+
+ // Spin the event loop before calling gGen.next() so the generator runs
+ // outside the onload handler. This prevents us from encountering all
+ // sorts of docshell quirks.
+ setTimeout(function() { gGen.next(); }, 0);
+ } else {
+ debug("Got unexpected load " + n);
+ ok(false, "Got unexpected load " + n);
+ }
+ } else if (msg == "childPageshow") {
+ if (n == expectedPageshowNum) {
+ debug("Got expected pageshow " + n);
+ expectedPageshowNum = -1;
+ ok(true, "Got expected pageshow " + n);
+ setTimeout(function() { gGen.next(); }, 0);
+ } else {
+ debug("Got unexpected pageshow " + n);
+ ok(false, "Got unexpected pageshow " + n);
+ }
+ } else if (msg == "closed") {
+ bc1.close();
+ bc2.close();
+ SimpleTest.finish();
+ }
+}
+
+bc2.onmessage = bc1.onmessage;
+
+var gGen = test();
+gGen.next();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1045096.html b/docshell/test/mochitest/test_bug1045096.html
new file mode 100644
index 0000000000..e9b8df7a5f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1045096.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1045096
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1045096</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045096">Mozilla Bug 1045096</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 1045096 **/
+ var i = document.createElement("iframe");
+ i.src = "javascript:false"; // This is required!
+ $("content").appendChild(i);
+ ok(i.contentWindow.performance, "Should have a performance object");
+ </script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1121701.html b/docshell/test/mochitest/test_bug1121701.html
new file mode 100644
index 0000000000..9e931a8997
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1121701.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1121701
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1121701</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1121701 **/
+
+ var testUrl1 = "file_bug1121701_1.html";
+ var testUrl2 = "file_bug1121701_2.html";
+ var testWin;
+
+ var page1LoadCount = 0;
+ function child1PageShow(e) {
+ ++page1LoadCount;
+ if (page1LoadCount == 1) {
+ SimpleTest.executeSoon(function() {
+ is(e.persisted, false, "Initial page load shouldn't be persisted.");
+ testWin.document.body.innerHTML = "modified";
+ testWin.onpagehide = function(event) {
+ testWin.onpagehide = null;
+ ok(event.persisted, "test page 1 should have been persisted");
+ is(testWin.document.body.innerHTML, "modified");
+ };
+ testWin.location.href = testUrl2;
+ });
+ } else if (page1LoadCount == 2) {
+ is(e.persisted, true, "Page load from bfcache should be persisted.");
+ is(testWin.document.body.innerHTML, "modified");
+ testWin.close();
+ SimpleTest.finish();
+ }
+ }
+
+ function child2PageShow(e) {
+ testWin.document.body.innerHTML = "<img>";
+ SimpleTest.executeSoon(function() {
+ testWin.onmessage = function() {
+ ok(true, "Got message");
+ testWin.document.body.firstChild.src = location.href;
+ };
+ testWin.onbeforeunload = function() {
+ testWin.postMessage("foo", "*");
+ };
+ testWin.history.back();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ testWin = window.open(testUrl1);
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121701">Mozilla Bug 1121701</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1151421.html b/docshell/test/mochitest/test_bug1151421.html
new file mode 100644
index 0000000000..4eb647e121
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1151421.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151421
+-->
+<head>
+ <title>Test for Bug 1151421</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151421">Mozilla Bug 1151421</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 1151421 **/
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let iframe = document.getElementById("iframe");
+ let cw = iframe.contentWindow;
+ let content = cw.document.getElementById("content");
+
+ // Create a function to calculate an invariant.
+ let topPlusOffset = function() {
+ return Math.round(content.getBoundingClientRect().top + cw.pageYOffset);
+ };
+
+ let initialTPO = topPlusOffset();
+
+ // Scroll the iframe to various positions, and check the TPO.
+ // Scrolling down to the bottom will adjust the page offset by a fractional amount.
+ let positions = [-100, 0.17, 0, 1.5, 10.41, 1e6, 12.1];
+
+ // Run some tests with scrollTo() and ensure we have the same invariant after scrolling.
+ positions.forEach(function(pos) {
+ cw.scrollTo(0, pos);
+ is(topPlusOffset(), initialTPO, "Top plus offset should remain invariant across scrolling.");
+ });
+
+ positions.reverse().forEach(function(pos) {
+ cw.scrollTo(0, pos);
+ is(topPlusOffset(), initialTPO, "(reverse) Top plus offset should remain invariant across scrolling.");
+ });
+
+ SimpleTest.finish();
+}
+
+</script>
+
+<!-- When the iframe loads, it calls childLoad(). -->
+<br>
+<iframe height='100px' id='iframe' src='file_bug1151421.html'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1186774.html b/docshell/test/mochitest/test_bug1186774.html
new file mode 100644
index 0000000000..afaf3b0f34
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1186774.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1186774
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1186774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1186774 **/
+
+var child;
+
+function runTest() {
+ child = window.open("file_bug1186774.html", "", "width=100,height=100");
+ child.onload = function() {
+ setTimeout(function() {
+ child.scrollTo(0, 0);
+ child.history.pushState({}, "initial");
+ child.scrollTo(0, 3000);
+ child.history.pushState({}, "scrolled");
+ child.scrollTo(0, 6000);
+ child.history.back();
+ });
+ };
+
+ child.onpopstate = function() {
+ is(Math.round(child.scrollY), 6000, "Shouldn't have scrolled before popstate");
+ child.close();
+ SimpleTest.finish();
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186774">Mozilla Bug 1186774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Ensure that reload after replaceState after 3xx redirect does the right thing.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ var ifr = document.querySelector("iframe");
+ var win = ifr.contentWindow;
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html?x=y"),
+ "Should have the right location on initial load");
+
+ win.history.replaceState(null, '', win.location.pathname);
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html"),
+ "Should have the right location after replaceState call");
+
+ ifr.onload = function() {
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html"),
+ "Should have the right location after reload");
+ SimpleTest.finish();
+ }
+ win.location.reload();
+ });
+ </script>
+</head>
+<body>
+<p id="display"><iframe src="bug1422334_redirect.html"></iframe></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1450164.html b/docshell/test/mochitest/test_bug1450164.html
new file mode 100644
index 0000000000..190b2f349b
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1450164.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1450164
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1450164</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1450164 **/
+
+ function runTest() {
+ var child = window.open("file_bug1450164.html", "", "width=100,height=100");
+ child.onload = function() {
+ // After the window loads, close it. If we don't crash in debug, consider that a pass.
+ child.close();
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+
+ </script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1450164">Mozilla Bug 1450164</a>
+ </body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1507702
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1507702</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="icon" href="about:crashparent"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1507702">Mozilla Bug 1507702</a>
+<img src="about:crashparent">
+<img src="about:crashcontent">
+<iframe src="about:crashparent"></iframe>
+<iframe src="about:crashcontent"></iframe>
+<script>
+ let urls = ["about:crashparent", "about:crashcontent"];
+ async function testFetch() {
+ const url = urls.shift();
+ if (!url) {
+ return Promise.resolve();
+ }
+
+ let threw;
+ try {
+ await fetch(url);
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+
+ ok(threw === true, "fetch should reject");
+ return testFetch();
+ }
+
+ document.body.onload = async () => {
+ for (const url of ["about:crashparent", "about:crashcontent"]) {
+ SimpleTest.doesThrow(() => {
+ top.location.href = url;
+ }, "navigation should throw");
+
+ SimpleTest.doesThrow(() => {
+ location.href = url;
+ }, "navigation should throw");
+ }
+
+ await testFetch();
+ SimpleTest.finish();
+ };
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1645781.html b/docshell/test/mochitest/test_bug1645781.html
new file mode 100644
index 0000000000..07bc55996a
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1645781.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for Bug 1590762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <form id="form" action="form_submit.sjs" method="POST" target="targetFrame">
+ <input id="input" type="text" name="name" value="">
+ <input id="button" type="submit">
+ </form>
+ <script>
+ "use strict";
+ const PATH = "/tests/docshell/test/mochitest/";
+ const SAME_ORIGIN = new URL(PATH, window.location.origin);;
+ const CROSS_ORIGIN_1 = new URL(PATH, "http://test1.example.com/");
+ const CROSS_ORIGIN_2 = new URL(PATH, "https://example.com/");
+ const TARGET = "ping.html";
+ const ACTION = "form_submit.sjs";
+
+ function generateBody(size) {
+ let data = new Uint8Array(size);
+ for (let i = 0; i < size; ++i) {
+ data[i] = 97 + Math.random() * (123 - 97);
+ }
+
+ return new TextDecoder().decode(data);
+ }
+
+ async function withFrame(url) {
+ info("Creating frame");
+ let frame = document.createElement('iframe');
+ frame.name = "targetFrame";
+
+ return new Promise(resolve => {
+ addEventListener('message', async function({source}) {
+ info("Frame loaded");
+ if (frame.contentWindow == source) {
+ resolve(frame);
+ }
+ }, { once: true });
+ frame.src = url;
+ document.body.appendChild(frame);
+ });
+ }
+
+ function click() {
+ synthesizeMouse(document.getElementById('button'), 5, 5, {});
+ }
+
+ function* spec() {
+ let urls = [SAME_ORIGIN, CROSS_ORIGIN_1, CROSS_ORIGIN_2];
+ for (let action of urls) {
+ for (let target of urls) {
+ yield { action: new URL(ACTION, action),
+ target: new URL(TARGET, target) };
+ }
+ }
+ }
+
+ info("Starting tests");
+ let form = document.getElementById('form');
+
+ // The body of the POST needs to be large to trigger this.
+ // 1024*1024 seems to be enough, but scaling to get a margin.
+ document.getElementById('input').value = generateBody(1024*1024);
+ for (let { target, action } of spec()) {
+ add_task(async function runTest() {
+ info(`Running test ${target} with ${action}`);
+ form.action = action;
+ let frame = await withFrame(target);
+ await new Promise(resolve => {
+ addEventListener('message', async function() {
+ info("Form loaded");
+ frame.remove();
+ resolve();
+ }, { once: true });
+
+ click();
+ });
+
+ ok(true, `Submitted to ${origin} with target ${action}`)
+ });
+ };
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/test_bug385434.html b/docshell/test/mochitest/test_bug385434.html
new file mode 100644
index 0000000000..8c3ea4a266
--- /dev/null
+++ b/docshell/test/mochitest/test_bug385434.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<head>
+ <title>Test for Bug 385434</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=385434">Mozilla Bug 385434</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="frame" style="height:100px; width:100px; border:0"></iframe>
+ <div id="status" style="display: none"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 385434 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.expectAssertions(0, 1); // bug 1333702
+
+var gNumHashchanges = 0;
+var gCallbackOnIframeLoad = false;
+var gSampleEvent;
+
+function statusMsg(msg) {
+ var msgElem = document.createElement("p");
+ msgElem.appendChild(document.createTextNode(msg));
+
+ document.getElementById("status").appendChild(msgElem);
+}
+
+function longWait() {
+ setTimeout(function() { gGen.next(); }, 1000);
+}
+
+// onIframeHashchange, onIframeLoad, and onIframeScroll are all called by the
+// content we load into our iframe in order to notify the parent frame of an
+// event which was fired.
+function onIframeHashchange() {
+ gNumHashchanges++;
+ gGen.next();
+}
+
+function onIframeLoad() {
+ if (gCallbackOnIframeLoad) {
+ gCallbackOnIframeLoad = false;
+ gGen.next();
+ }
+}
+
+function onIframeScroll() {
+ is(gNumHashchanges, 0, "onscroll should fire before onhashchange.");
+}
+
+function enableIframeLoadCallback() {
+ gCallbackOnIframeLoad = true;
+}
+
+function noEventExpected(msg) {
+ is(gNumHashchanges, 0, msg);
+
+ // Even if there's an error, set gNumHashchanges to 0 so other tests don't
+ // fail.
+ gNumHashchanges = 0;
+}
+
+function eventExpected(msg) {
+ is(gNumHashchanges, 1, msg);
+
+ // Eat up this event, whether the test above was true or not
+ gNumHashchanges = 0;
+}
+
+/*
+ * The hashchange event is dispatched asynchronously, so if we want to observe
+ * it, we have to yield within run_test(), transferring control back to the
+ * event loop.
+ *
+ * When we're expecting our iframe to observe a hashchange event after we poke
+ * it, we just yield and wait for onIframeHashchange() to call gGen.next() and
+ * wake us up.
+ *
+ * When we're testing to ensure that the iframe doesn't dispatch a hashchange
+ * event, we try to hook onto the iframe's load event. We call
+ * enableIframeLoadCallback(), which causes onIframeLoad() to call gGen.next()
+ * upon the next observed load. After we get our callback, we check that a
+ * hashchange didn't occur.
+ *
+ * We can't always just wait for page load in order to observe that a
+ * hashchange didn't happen. In these cases, we call longWait() and yield
+ * until either a hashchange occurs or longWait's callback is scheduled. This
+ * is something of a hack; it's entirely possible that longWait won't wait long
+ * enough, and we won't observe what should have been a failure of the test.
+ * But it shouldn't happen that good code will randomly *fail* this test.
+ */
+function* run_test() {
+ /*
+ * TEST 1 tests that:
+ * <body onhashchange = ... > works,
+ * the event is (not) fired at the correct times
+ */
+ var frame = document.getElementById("frame");
+ var frameCw = frame.contentWindow;
+
+ enableIframeLoadCallback();
+ frameCw.document.location = "file_bug385434_1.html";
+ // Wait for the iframe to load and for our callback to fire
+ yield undefined;
+
+ noEventExpected("No hashchange expected initially.");
+
+ sendMouseEvent({type: "click"}, "link1", frameCw);
+ yield undefined;
+ eventExpected("Clicking link1 should trigger a hashchange.");
+
+ sendMouseEvent({type: "click"}, "link1", frameCw);
+ longWait();
+ yield undefined;
+ // succeed if a hashchange event wasn't triggered while we were waiting
+ noEventExpected("Clicking link1 again should not trigger a hashchange.");
+
+ sendMouseEvent({type: "click"}, "link2", frameCw);
+ yield undefined;
+ eventExpected("Clicking link2 should trigger a hashchange.");
+
+ frameCw.history.go(-1);
+ yield undefined;
+ eventExpected("Going back should trigger a hashchange.");
+
+ frameCw.history.go(1);
+ yield undefined;
+ eventExpected("Going forward should trigger a hashchange.");
+
+ // window.location has a trailing '#' right now, so we append "link1", not
+ // "#link1".
+ frameCw.window.location = frameCw.window.location + "link1";
+ yield undefined;
+ eventExpected("Assigning to window.location should trigger a hashchange.");
+
+ // Set up history in the iframe which looks like:
+ // file_bug385434_1.html#link1
+ // file_bug385434_2.html
+ // file_bug385434_1.html#foo <-- current page
+ enableIframeLoadCallback();
+ frameCw.window.location = "file_bug385434_2.html";
+ yield undefined;
+
+ enableIframeLoadCallback();
+ frameCw.window.location = "file_bug385434_1.html#foo";
+ yield undefined;
+
+ // Now when we do history.go(-2) on the frame, it *shouldn't* fire a
+ // hashchange. Although the URIs differ only by their hashes, they belong to
+ // two different Documents.
+ frameCw.history.go(-2);
+ longWait();
+ yield undefined;
+ noEventExpected("Moving between different Documents shouldn't " +
+ "trigger a hashchange.");
+
+ /*
+ * TEST 2 tests that:
+ * <frameset onhashchange = ... > works,
+ * the event is targeted at the window object
+ * the event's cancelable, bubbles settings are correct
+ */
+
+ enableIframeLoadCallback();
+ frameCw.document.location = "file_bug385434_2.html";
+ yield undefined;
+
+ frameCw.document.location = "file_bug385434_2.html#foo";
+ yield undefined;
+
+ eventExpected("frame onhashchange should fire events.");
+ // iframe should set gSampleEvent
+ is(gSampleEvent.target, frameCw,
+ "The hashchange event should be targeted to the window.");
+ is(gSampleEvent.type, "hashchange",
+ "Event type should be 'hashchange'.");
+ is(gSampleEvent.cancelable, false,
+ "The hashchange event shouldn't be cancelable.");
+ is(gSampleEvent.bubbles, false,
+ "The hashchange event should not bubble.");
+
+ /*
+ * TEST 3 tests that:
+ * hashchange is dispatched if the current document readyState is
+ * not "complete" (bug 504837).
+ */
+ frameCw.document.location = "file_bug385434_3.html";
+ yield undefined;
+ eventExpected("Hashchange should fire even if the document " +
+ "hasn't finished loading.");
+
+ SimpleTest.finish();
+}
+
+var gGen = run_test();
+gGen.next();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug387979.html b/docshell/test/mochitest/test_bug387979.html
new file mode 100644
index 0000000000..bd92b0ad08
--- /dev/null
+++ b/docshell/test/mochitest/test_bug387979.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=387979
+-->
+<head>
+ <title>Test for Bug 387979</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=387979">Mozilla Bug 387979</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 387979 **/
+function a(s) {
+ var r;
+ try { r = frames[0].document.body; } catch (e) { r = e; }
+ is(r instanceof frames[0].HTMLBodyElement, true, "Can't get body" + s);
+}
+var p = 0;
+function b() {
+ switch (++p) {
+ case 1:
+ frames[0].location = "about:blank";
+ break;
+ case 2:
+ a("before reload");
+ frames[0].location.reload();
+ break;
+ case 3:
+ a("after reload");
+ SimpleTest.finish();
+ break;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<p id="display">
+ <iframe onload="b()"></iframe>
+ <pre id="p">-</pre>
+</p>
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+While working on bug 402210, it came up that the code was doing
+
+a.href = proto + host
+
+which technically produces "https:host" instead of "https://host" and
+that the code was relying on href's setting having fixup behaviour
+for this kind of thing.
+
+If we rely on it, we might as well test for it, even if it isn't the
+problem 402210 was meant to fix.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=402210
+-->
+<head>
+ <title>Test for Bug 402210</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402210">Mozilla Bug 402210</a>
+<p id="display">
+ <a id="testlink">Test Link</a>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ $("testlink").href = "https:example.com";
+ is($("testlink").href, "https://example.com/", "Setting href on an anchor tag should fixup missing slashes after https protocol");
+
+ $("testlink").href = "ftp:example.com";
+ is($("testlink").href, "ftp://example.com/", "Setting href on an anchor tag should fixup missing slashes after non-http protocol");
+
+ SimpleTest.finish();
+}
+
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug404548.html b/docshell/test/mochitest/test_bug404548.html
new file mode 100644
index 0000000000..495341e7b5
--- /dev/null
+++ b/docshell/test/mochitest/test_bug404548.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404548
+-->
+<head>
+ <title>Test for Bug 404548</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404548">Mozilla Bug 404548</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 404548 **/
+var firstRemoved = false;
+var secondHidden = false;
+
+SimpleTest.waitForExplicitFinish();
+
+var w = window.open("bug404548-subframe.html", "", "width=10,height=10");
+
+function finishTest() {
+ is(firstRemoved, true, "Should have removed iframe from the DOM");
+ is(secondHidden, true, "Should have fired pagehide on second kid");
+ w.close();
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug413310.html b/docshell/test/mochitest/test_bug413310.html
new file mode 100644
index 0000000000..3d3ba56252
--- /dev/null
+++ b/docshell/test/mochitest/test_bug413310.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413310
+-->
+<head>
+ <title>Test for Bug 413310</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=413310">Mozilla Bug 413310</a>
+<p id="display">
+<script class="testbody" type="text/javascript">
+
+if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 2);
+} else {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 413310 **/
+
+// NOTE: If we ever make subframes do bfcache stuff, this test will need to be
+// modified accordingly! It assumes that subframes do NOT get bfcached.
+var onloadCount = 0;
+
+var step = -1; // One increment will come from the initial subframe onload.
+ // Note that this script should come before the subframe,
+ // so that doNextStep is defined when its onload handler fires.
+
+var textContent;
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(doNextStep);
+
+function doNextStep() {
+ ++step;
+ switch (step) {
+ case 1:
+ is(onloadCount, 1, "Loaded initial page");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-subframe.html"),
+ "Unexpected subframe location after initial load");
+ $("i").contentDocument.forms[0].submit();
+ break;
+ case 2:
+ is(onloadCount, 2, "Loaded POST result");
+
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs"),
+ "Unexpected subframe location after POST load");
+
+ textContent = $("i").contentDocument.body.textContent;
+ isDeeply(textContent.match(/^POST /), ["POST "], "Not a POST?");
+
+ $("i").contentWindow.location.hash = "foo";
+ setTimeout(doNextStep, 0);
+ break;
+ case 3:
+ is(onloadCount, 2, "Anchor scroll should not fire onload");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs#foo"),
+ "Unexpected subframe location after anchor scroll");
+ is(textContent, $("i").contentDocument.body.textContent,
+ "Did a load when scrolling?");
+ $("i").contentWindow.location.href = "bug413310-subframe.html";
+ break;
+ case 4:
+ is(onloadCount, 3, "Done new load");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-subframe.html"),
+ "Unexpected subframe location after new load");
+ history.back();
+ break;
+ case 5:
+ is(onloadCount, 4,
+ "History traversal didn't fire onload: bfcache issues!");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs#foo"),
+ "Unexpected subframe location");
+ is(textContent, $("i").contentDocument.body.textContent,
+ "Did a load when going back?");
+ SimpleTest.finish();
+ break;
+ }
+}
+</script>
+<!-- Use a timeout in onload so that we don't do a load immediately inside onload -->
+<iframe id="i" src="bug413310-subframe.html" onload="setTimeout(doNextStep, 20)">
+</iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475636
+Test that refresh to data: URIs don't inherit the principal
+-->
+<head>
+ <title>Test for Bug 475636</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475636">Mozilla Bug 475636</a>
+
+<div id="content" style="display: none">
+
+</div>
+<iframe id=loader></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var gen = runTests();
+
+window.private = 42;
+
+window.addEventListener("message", function(e) {
+ gen.next(e.data);
+});
+
+var url = "file_bug475636.sjs?";
+
+function* runTests() {
+ var loader = document.getElementById("loader");
+ for (var testNum = 1; ; ++testNum) {
+ loader.src = url + testNum;
+ let res = (yield);
+ if (res == "done") {
+ SimpleTest.finish();
+ return;
+ }
+ is(res, "pass");
+ }
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug509055.html b/docshell/test/mochitest/test_bug509055.html
new file mode 100644
index 0000000000..3d92a4fe06
--- /dev/null
+++ b/docshell/test/mochitest/test_bug509055.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=509055
+-->
+<head>
+ <title>Test for Bug 509055</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=509055">Mozilla Bug 509055</a>
+<p id="display"></p>
+<div id="status"></div>
+<div id="content">
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 509055 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var gGen;
+
+ function shortWait() {
+ setTimeout(function() { gGen.next(); }, 0, false);
+ }
+
+ function onChildHashchange(e) {
+ // gGen might be undefined when we refresh the page, so we have to check here
+ dump("onChildHashchange() called.\n");
+ if (gGen)
+ gGen.next();
+ }
+
+ function onChildLoad(e) {
+ if (gGen)
+ gGen.next();
+ }
+
+ async function* runTest() {
+ var popup = window.open("file_bug509055.html", "popup 0",
+ "height=200,width=200,location=yes," +
+ "menubar=yes,status=yes,toolbar=yes,dependent=yes");
+ popup.hashchangeCallback = onChildHashchange;
+ popup.onload = onChildLoad;
+ dump("Waiting for initial load.\n");
+ yield undefined;
+
+ // Without this wait, the change to location.hash below doesn't create a
+ // SHEntry or enable the back button.
+ shortWait();
+ dump("Got initial load. Spinning event loop.\n");
+ yield undefined;
+
+ popup.location.hash = "#1";
+ dump("Waiting for hashchange.\n");
+ yield undefined;
+
+ popup.history.back();
+ dump("Waiting for second hashchange.\n");
+ yield undefined; // wait for hashchange
+
+ popup.document.title = "Changed";
+
+ // Wait for listeners to be notified of the title change.
+ shortWait();
+ dump("Got second hashchange. Spinning event loop.\n");
+ yield undefined;
+
+ let sheTitle = "";
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ var sh = SpecialPowers.wrap(popup)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+
+ // Get the title of the inner popup's current SHEntry
+ sheTitle = sh.legacySHistory.getEntryAtIndex(sh.index).title;
+ } else {
+ let chromeScript = SpecialPowers.loadChromeScript(() => {
+ // eslint-disable-next-line no-undef
+ addMessageListener("getTitle", browsingContext => {
+ // eslint-disable-next-line no-shadow
+ let sh = browsingContext.sessionHistory;
+ let title = sh.getEntryAtIndex(sh.index).title;
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage("title", title);
+ });
+ });
+
+ let p = chromeScript.promiseOneMessage("title");
+ let browsingContext = SpecialPowers.wrap(popup)
+ .docShell.browsingContext;
+ chromeScript.sendAsyncMessage("getTitle", browsingContext);
+ sheTitle = await p;
+ chromeScript.destroy();
+ }
+ is(sheTitle, "Changed", "SHEntry's title should change when we change.");
+
+ popup.close();
+
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }
+
+ window.addEventListener("load", function() {
+ gGen = runTest();
+ gGen.next();
+ });
+
+ </script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug511449.html b/docshell/test/mochitest/test_bug511449.html
new file mode 100644
index 0000000000..de66eb756d
--- /dev/null
+++ b/docshell/test/mochitest/test_bug511449.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=511449
+-->
+<head>
+ <title>Test for Bug 511449</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511449">Mozilla Bug 511449</a>
+<p id="display"></p>
+<div id="status"></div>
+<div id="content">
+</div>
+<input type="text" id="input">
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 511449 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", runTest);
+
+var win = null;
+
+function runTest() {
+ document.getElementById("input").focus();
+ win = window.open("file_bug511449.html", "");
+ SimpleTest.waitForFocus(runNextTest, win);
+}
+
+function runNextTest() {
+ var didClose = false;
+ win.onunload = function() {
+ didClose = true;
+ };
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_W, {metaKey: 1}, "w", "w");
+
+ setTimeout(function() {
+ ok(didClose, "Cmd+W should have closed the tab");
+ if (!didClose) {
+ win.close();
+ }
+ SimpleTest.finish();
+ }, 1000);
+}
+
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug529119-1.html b/docshell/test/mochitest/test_bug529119-1.html
new file mode 100644
index 0000000000..e8a77f6c00
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-1.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var workingURL = "http://mochi.test:8888/tests/docshell/test/mochitest/bug529119-window.html";
+var faultyURL = "http://www.some-nonexistent-domain-27489274c892748217cn2384.com/";
+
+var w = null;
+var phase = 0;
+var gotWrongPageOnTryAgainClick = false;
+// Token that represents which page we currently have loaded.
+var token = 0;
+
+function delay(msec) {
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+async function assignToken(tokenToAssign) {
+ await SpecialPowers.spawn(w, [tokenToAssign],
+ newToken => { this.content.token = newToken });
+}
+
+async function pollForPage(win) {
+ while (true) {
+ try {
+ // When we do our navigation, there may be an interstitial about:blank
+ // page if the navigation involves a process switch. That about:blank
+ // will exist between the new process's docshell being created and the
+ // actual page that's being loaded loading (which can happen async from
+ // the docshell creation). We want to avoid treating the initial
+ // about:blank as a new page.
+ //
+ // We could conceivably expose Document::IsInitialDocument() as a
+ // ChromeOnly thing and use it here, but let's just filter out all
+ // about:blank, since we don't expect any in this test.
+ var haveNewPage = await SpecialPowers.spawn(w, [token],
+ currentToken => this.content.token != currentToken &&
+ this.content.location.href != "about:blank");
+
+ if (haveNewPage) {
+ ++token;
+ assignToken(token);
+ break;
+ }
+ } catch (e) {
+ // Something went wrong; just keep waiting.
+ }
+
+ await delay(100);
+ }
+}
+
+async function windowLoaded() {
+ switch (phase) {
+ case 0:
+ assignToken(token);
+
+ /* 2. We have succeededfully loaded a page, now go to a faulty URL */
+ window.setTimeout(function() {
+ w.location.href = faultyURL;
+ }, 0);
+
+ phase = 1;
+
+ await pollForPage(w);
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ faultyURL,
+ "Is on an error page initially");
+
+ /* 3. now, while we are on the error page, try to reload it, actually
+ click the "Try Again" button */
+ SpecialPowers.spawn(w, [], () => this.content.location.reload());
+
+ await pollForPage(w);
+
+ /* 4-finish, check we are still on the error page */
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ faultyURL,
+ "Is on an error page");
+ is(gotWrongPageOnTryAgainClick, false,
+ "Must not get www.example.com page on reload of an error page");
+ w.close();
+ SimpleTest.finish();
+ break;
+
+ case 1:
+ /* 4-check, we must not get here! */
+ gotWrongPageOnTryAgainClick = true;
+ break;
+ }
+}
+
+function startTest() {
+ /* 1. load a URL that leads to an error page */
+ w = window.open(workingURL);
+}
+
+</script>
+</head>
+<body onload="startTest();">
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug529119-2.html b/docshell/test/mochitest/test_bug529119-2.html
new file mode 100644
index 0000000000..0db278ce63
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-2.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var workingURL = "http://mochi.test:8888/tests/docshell/test/mochitest/bug529119-window.html";
+var faultyURL = "http://some-nonexistent-domain-27489274c892748217cn2384.com/";
+
+var w = null;
+var phase = 0;
+var isWindowLoaded = false;
+// Token that represents which page we currently have loaded.
+var token = 0;
+
+function delay(msec) {
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+async function assignToken(tokenToAssign) {
+ await SpecialPowers.spawn(w, [tokenToAssign],
+ newToken => { this.content.token = newToken });
+}
+
+// Returns when a new page is loaded and returns whether that page is an
+// error page.
+async function pollForPage(win) {
+ while (true) {
+ try {
+ // When we do our navigation, there may be an interstitial about:blank
+ // page if the navigation involves a process switch. That about:blank
+ // will exist between the new process's docshell being created and the
+ // actual page that's being loaded loading (which can happen async from
+ // the docshell creation). We want to avoid treating the initial
+ // about:blank as a new page.
+ //
+ // We could conceivably expose Document::IsInitialDocument() as a
+ // ChromeOnly thing and use it here, but let's just filter out all
+ // about:blank, since we don't expect any in this test.
+ var haveNewPage = await SpecialPowers.spawn(w, [token],
+ currentToken => this.content.token != currentToken &&
+ this.content.location.href != "about:blank");
+
+ if (haveNewPage) {
+ ++token;
+ assignToken(token);
+
+ // In this test, error pages are non-same-origin with us, and non-error
+ // pages are same-origin.
+ let haveErrorPage = false;
+ try {
+ win.document.title;
+ } catch (ex) {
+ haveErrorPage = true;
+ }
+ return haveErrorPage;
+ }
+ } catch (e) {
+ // Something went wrong; just keep waiting.
+ }
+
+ await delay(100);
+ }
+}
+
+async function windowLoaded() {
+ // The code under here should only be run once
+ // The test popup window workingURL was already opened
+ if (isWindowLoaded)
+ return;
+ isWindowLoaded = true;
+
+ assignToken(token);
+
+ /* 2. We have successfully loaded a page, now go to a faulty URL */
+ // XXX The test fails when we change the location synchronously
+ window.setTimeout(function() {
+ w.location.href = faultyURL;
+ }, 0);
+
+ ok(await pollForPage(w), "Waiting for error page succeeded");
+ /* 3. now, while we are on the error page, navigate back */
+ try {
+ // We need the SpecialPowers bit, because this is a cross-origin window
+ // and we normally can't touch .history on those.
+ await SpecialPowers.spawn(w, [], () => this.content.history.back());
+ } catch (ex) {
+ ok(false, "w.history.back() threw " + ex);
+ }
+
+ ok(!await pollForPage(w), "Waiting for original page succeeded");
+ /* 4-finish, check we are back at the original page */
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ workingURL,
+ "Is on the previous page");
+ w.close();
+ SimpleTest.finish();
+}
+
+function startTest() {
+ /* 1. load a URL that leads to an error page */
+ w = window.open(workingURL);
+}
+
+</script>
+</head>
+<body onload="startTest();">
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=530396
+-->
+<head>
+ <title>Test for Bug 530396</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=530396">Mozilla Bug 530396</a>
+
+<p>
+
+<iframe id="testFrame" src="http://mochi.test:8888/tests/docshell/test/mochitest/bug530396-subframe.html"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// NOTE: If we ever make subframes do bfcache stuff, this test will need to be
+// modified accordingly! It assumes that subframes do NOT get bfcached.
+var onloadCount = 0;
+
+var step = 0;
+
+var gTestFrame = document.getElementById("testFrame");
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(doNextStep);
+
+function doNextStep() {
+ ++step;
+ switch (step) {
+ case 1:
+ is(onloadCount, 1, "Loaded initial page");
+ sendMouseEvent({type: "click"}, "target2", gTestFrame.contentWindow);
+ window.setTimeout(doNextStep, 1000);
+ break;
+
+ case 2:
+ is(onloadCount, 1, "opener must be null");
+ sendMouseEvent({type: "click"}, "target1", gTestFrame.contentWindow);
+ break;
+
+ case 3:
+ is(onloadCount, 2, "don't send referrer with rel=referrer");
+ SimpleTest.finish();
+ break;
+ }
+}
+</script>
+</pre>
+</html>
diff --git a/docshell/test/mochitest/test_bug540462.html b/docshell/test/mochitest/test_bug540462.html
new file mode 100644
index 0000000000..6560d604a2
--- /dev/null
+++ b/docshell/test/mochitest/test_bug540462.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=540462
+-->
+<head>
+ <title>Test for Bug 540462</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=540462">Mozilla Bug 540462</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 540462 **/
+
+var win;
+function runTest() {
+ win = window.open("file_bug540462.html", "", "width=100,height=100");
+}
+
+var dwlCount = 0;
+var originalURL;
+function documentWriteLoad() {
+ if (++dwlCount == 1) {
+ originalURL = win.document.body.firstChild.href;
+ } else if (dwlCount == 2) {
+ is(win.document.body.firstChild.href, originalURL, "Wrong href!");
+ win.close();
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug551225.html b/docshell/test/mochitest/test_bug551225.html
new file mode 100644
index 0000000000..999cb6b945
--- /dev/null
+++ b/docshell/test/mochitest/test_bug551225.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551225
+-->
+<head>
+ <title>Test for Bug 551225</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551225">Mozilla Bug 551225</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 551225 **/
+
+var obj = {
+ a: new Date("1/1/2000"),
+ b: /^foo$/,
+ c: "bar",
+};
+
+history.replaceState(obj, "", "");
+is(history.state.a.toString(), new Date("1/1/2000").toString(), "Date object.");
+is(history.state.b.toString(), "/^foo$/", "Regex");
+is(history.state.c, "bar", "Other state");
+
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=570341
+-->
+<head>
+ <title>Test for Bug 570341</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ var start = Date.now();
+ var moments = {};
+
+ var unload = 0;
+ var wasEnabled = true;
+
+ function collectMoments() {
+ var win = frames[0];
+ var timing = (win.performance && win.performance.timing) || {};
+ for (let p in timing) {
+ moments[p] = timing[p];
+ }
+ for (let p in win) {
+ if (p.substring(0, 9) == "_testing_") {
+ moments[p.substring(9)] = win[p];
+ }
+ }
+ moments.evt_unload = unload;
+ return moments;
+ }
+
+ function showSequence(node) {
+ while (node.firstChild) {
+ node.firstChild.remove();
+ }
+ var sequence = [];
+ for (var p in moments) {
+ sequence.push(p);
+ }
+ sequence.sort(function(a, b) {
+ return moments[a] - moments[b];
+ });
+ var table = document.createElement("table");
+ node.appendChild(table);
+ var row = document.createElement("tr");
+ table.appendChild(row);
+ var cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode("start"));
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(start));
+ for (var i = 0; i < sequence.length; ++i) {
+ var prop = sequence[i];
+ row = document.createElement("tr");
+ table.appendChild(row);
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(prop));
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(moments[prop]));
+ }
+ }
+
+ function checkValues() {
+ var win = frames[0];
+ ok(win.performance,
+ "window.performance is missing or not accessible for frame");
+ ok(!win.performance || win.performance.timing,
+ "window.performance.timing is missing or not accessible for frame");
+ collectMoments();
+
+ var sequences = [
+ ["navigationStart", "unloadEventStart", "unloadEventEnd"],
+ ["navigationStart", "fetchStart", "domainLookupStart", "domainLookupEnd",
+ "connectStart", "connectEnd", "requestStart", "responseStart", "responseEnd"],
+ ["responseStart", "domLoading", "domInteractive", "domComplete"],
+ ["domContentLoadedEventStart", "domContentLoadedEventEnd",
+ "loadEventStart", "loadEventEnd"],
+ ];
+
+ for (var i = 0; i < sequences.length; ++i) {
+ var seq = sequences[i];
+ for (var j = 0; j < seq.length; ++j) {
+ var prop = seq[j];
+ if (j > 0) {
+ var prevProp = seq[j - 1];
+ ok(moments[prevProp] <= moments[prop],
+ ["Expected ", prevProp, " to happen before ", prop,
+ ", got ", prevProp, " = ", moments[prevProp],
+ ", ", prop, " = ", moments[prop]].join(""));
+ }
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+window.onload = function() {
+ var win = frames[0];
+ win.addEventListener("unload", function() {
+ unload = Date.now();
+ }, true);
+ var seenLoad = 0;
+ win.addEventListener("load", function() {
+ seenLoad = Date.now();
+ }, true);
+ frames[0].location = "bug570341_recordevents.html";
+ var interval = setInterval(function() {
+ // time constants here are arbitrary, chosen to allow the test to pass
+ var stopPolling = (win.performance && win.performance.loadEventEnd) ||
+ (seenLoad && Date.now() >= seenLoad + 3000) ||
+ Date.now() >= start + 30000;
+ if (stopPolling) {
+ clearInterval(interval);
+ checkValues();
+ } else if (win._testing_evt_load) {
+ seenLoad = Date.now();
+ }
+ }, 100);
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a>
+<div id="frames">
+<iframe name="child0" src="navigation/blank.html"></iframe>
+</div>
+<button type="button" onclick="showSequence(document.getElementById('display'))">
+ Show Events</button>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=580069
+-->
+<head>
+ <title>Test for Bug 580069</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580069">Mozilla Bug 580069</a>
+
+<script type="application/javascript">
+
+add_task(async function() {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "file_bug580069_1.html");
+
+ // Insert the initial <iframe> document, and wait for page1Load to be called
+ // after it loads.
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ window.page1Load = resolve;
+ });
+ let iframeCw = iframe.contentWindow;
+
+ info("iframe's location is: " + iframeCw.location + "\n");
+
+ // Submit the forum and wait for the initial page load using a POST load.
+ iframeCw.document.getElementById("form").submit();
+ let method1 = await new Promise(resolve => {
+ window.page2Load = resolve;
+ });
+ info("iframe's location is: " + iframeCw.location + ", method is " + method1 + "\n");
+ is(method1, "POST", "Method for first load should be POST.");
+
+ // Push a new state, and refresh the page. This refresh shouldn't pop up the
+ // "are you sure you want to refresh a page with POST data?" dialog. If it
+ // does, this test will hang and fail, and we'll see 'Refreshing iframe...' at
+ // the end of the test log.
+ iframeCw.history.replaceState("", "", "?replaced");
+
+ info("Refreshing iframe...\n");
+ iframeCw.location.reload();
+ let method2 = await new Promise(resolve => {
+ window.page2Load = resolve;
+ });
+
+ info("iframe's location is: " + iframeCw.location + ", method is " + method2 + "\n");
+ is(method2, "GET", "Method for second load should be GET.");
+ is(iframeCw.location.search, "?replaced", "Wrong search on iframe after refresh.");
+});
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug590573.html b/docshell/test/mochitest/test_bug590573.html
new file mode 100644
index 0000000000..3378c707a9
--- /dev/null
+++ b/docshell/test/mochitest/test_bug590573.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590573
+-->
+<head>
+ <title>Test for Bug 590573</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590573">Mozilla Bug 590573</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+// Listen to the first callback, since this indicates that the page loaded.
+var page1LoadCallbackEnabled = true;
+function page1Load() {
+ if (page1LoadCallbackEnabled) {
+ page1LoadCallbackEnabled = false;
+ dump("Got page1 load.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page1 load.\n");
+ }
+}
+
+var page1PopstateCallbackEnabled = false;
+function page1Popstate() {
+ if (page1PopstateCallbackEnabled) {
+ page1PopstateCallbackEnabled = false;
+ dump("Got page1 popstate.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page1 popstate.\n");
+ }
+}
+
+var page1PageShowCallbackEnabled = false;
+function page1PageShow() {
+ if (page1PageShowCallbackEnabled) {
+ page1PageShowCallbackEnabled = false;
+ dump("Got page1 pageshow.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page1 pageshow.\n");
+ }
+}
+
+var page2LoadCallbackEnabled = false;
+function page2Load() {
+ if (page2LoadCallbackEnabled) {
+ page2LoadCallbackEnabled = false;
+ dump("Got page2 popstate.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 popstate.\n");
+ }
+}
+
+var page2PopstateCallbackEnabled = false;
+function page2Popstate() {
+ if (page2PopstateCallbackEnabled) {
+ page2PopstateCallbackEnabled = false;
+ dump("Got page2 popstate.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 popstate.\n");
+ }
+}
+
+var page2PageShowCallbackEnabled = false;
+function page2PageShow() {
+ if (page2PageShowCallbackEnabled) {
+ page2PageShowCallbackEnabled = false;
+ dump("Got page2 pageshow.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 pageshow.\n");
+ }
+}
+
+var popup = window.open("file_bug590573_1.html");
+
+var gTestContinuation = null;
+var loads = 0;
+function pageLoad() {
+ loads++;
+ dump("pageLoad(loads=" + loads + ", page location=" + popup.location + ")\n");
+
+ if (!gTestContinuation) {
+ gTestContinuation = testBody();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ }
+}
+
+function continueAsync() {
+ setTimeout(function() { gTestContinuation.next(); })
+}
+
+function* testBody() {
+ is(popup.scrollY, 0, "test 1");
+ popup.scroll(0, 100);
+
+ popup.history.pushState("", "", "?pushed");
+ is(Math.round(popup.scrollY), 100, "test 2");
+ popup.scroll(0, 200); // set state-2's position to 200
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 100, "test 3");
+ popup.scroll(0, 150); // set original page's position to 150
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 200, "test 4");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 150, "test 5");
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 200, "test 6");
+
+ // At this point, the history looks like:
+ // PATH POSITION
+ // file_bug590573_1.html 150 <-- oldest
+ // file_bug590573_1.html?pushed 200 <-- newest, current
+
+ // Now test that the scroll position is persisted when we have real
+ // navigations involved. First, we need to spin the event loop so that the
+ // navigation doesn't replace our current history entry.
+
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page2LoadCallbackEnabled = true;
+ popup.location = "file_bug590573_2.html";
+ yield;
+
+ ok(popup.location.href.match("file_bug590573_2.html$"),
+ "Location was " + popup.location +
+ " but should end with file_bug590573_2.html");
+
+ is(popup.scrollY, 0, "test 7");
+ popup.scroll(0, 300);
+
+ // We need to spin the event loop again before we go back, otherwise the
+ // scroll positions don't get updated properly.
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page1PageShowCallbackEnabled = true;
+ popup.history.back();
+ yield;
+
+ // Spin the event loop again so that we get the right scroll positions.
+ setTimeout(pageLoad, 0);
+ yield;
+
+ is(popup.location.search, "?pushed");
+ ok(popup.document.getElementById("div1"), "page should have div1.");
+
+ is(Math.round(popup.scrollY), 200, "test 8");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 150, "test 9");
+ popup.history.forward();
+ continueAsync();
+ yield;
+
+ is(Math.round(popup.scrollY), 200, "test 10");
+
+ // Spin one last time...
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page2PageShowCallbackEnabled = true;
+ popup.history.forward();
+ yield;
+
+ // Bug 821821, on Android tegras we get 299 instead of 300 sometimes
+ const scrollY = Math.floor(popup.scrollY);
+ if (scrollY >= 299 && scrollY <= 300) {
+ is(1, 1, "test 11");
+ } else {
+ is(1, 0, "test 11, got " + popup.scrollY + " for popup.scrollY instead of 299|300");
+ }
+ popup.close();
+}
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug598895.html b/docshell/test/mochitest/test_bug598895.html
new file mode 100644
index 0000000000..e5589a8c0f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug598895.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=598895
+-->
+<head>
+ <title>Test for Bug 598895</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=598895">Mozilla Bug 598895</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 598895 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+var win1 = window.open();
+win1.document.body.textContent = "Should show";
+
+var windowsLoaded = 0;
+
+window.onmessage = async function(ev) {
+ is(ev.data, "loaded", "Message should be 'loaded'");
+ if (++windowsLoaded == 2) {
+ var one = await snapshotWindow(win1);
+ var two = await snapshotWindow(win2);
+ var three = await snapshotWindow(win3);
+ win1.close();
+ win2.close();
+ win3.close();
+ ok(compareSnapshots(one, two, true)[0], "Popups should look identical");
+ ok(compareSnapshots(one, three, false)[0], "Popups should not look identical");
+
+ SimpleTest.finish();
+ }
+};
+
+var win2 = window.open("file_bug598895_1.html");
+var win3 = window.open("file_bug598895_2.html");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug634834.html b/docshell/test/mochitest/test_bug634834.html
new file mode 100644
index 0000000000..4c706eeec0
--- /dev/null
+++ b/docshell/test/mochitest/test_bug634834.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=634834
+-->
+<head>
+ <title>Test for Bug 634834</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=634834">Mozilla Bug 634834</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+function iframe_loaded() {
+ var loadedAfterPushstate = false;
+ $("iframe").onload = function() {
+ loadedAfterPushstate = true;
+ };
+
+ var obj = { name: "name" };
+ obj.__defineGetter__("a", function() {
+ $("iframe").contentWindow.location = "http://example.com";
+
+ // Wait until we've loaded example.com.
+ do {
+ var r = new XMLHttpRequest();
+ r.open("GET", location.href, false);
+ r.overrideMimeType("text/plain");
+ try { r.send(null); } catch (e) {}
+ } while (!loadedAfterPushstate);
+ });
+
+ try {
+ $("iframe").contentWindow.history.pushState(obj, "");
+ ok(false, "pushState should throw exception.");
+ } catch (e) {
+ ok(true, "pushState threw an exception.");
+ }
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe id='iframe' src='file_bug634834.html' onload='iframe_loaded()'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug637644.html b/docshell/test/mochitest/test_bug637644.html
new file mode 100644
index 0000000000..66ed6cb3c8
--- /dev/null
+++ b/docshell/test/mochitest/test_bug637644.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=637644
+-->
+<head>
+ <title>Test for Bug 637644</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=637644">Mozilla Bug 637644</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 637644 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+var win1 = window.open("", "", "height=500,width=500");
+win1.document.body.textContent = "Should show";
+
+var windowsLoaded = 0;
+
+window.onmessage = async function(ev) {
+ is(ev.data, "loaded", "Message should be 'loaded'");
+ if (++windowsLoaded == 2) {
+ var one = await snapshotWindow(win1);
+ var two = await snapshotWindow(win2);
+ var three = await snapshotWindow(win3);
+ win1.close();
+ win2.close();
+ win3.close();
+ ok(compareSnapshots(one, two, true)[0], "Popups should look identical");
+ ok(compareSnapshots(one, three, false)[0], "Popups should not look identical");
+
+ SimpleTest.finish();
+ }
+};
+
+var win2 = window.open("file_bug637644_1.html", "", "height=500,width=500");
+var win3 = window.open("file_bug637644_2.html", "", "height=500,width=500");
+});
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640387
+-->
+<head>
+ <title>Test for Bug 640387</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640387">Mozilla Bug 640387</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+function* test() {
+ /* Spin the event loop so we get out of the onload handler. */
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ popup.history.pushState("", "", "#hash1");
+ popup.history.pushState("", "", "#hash2");
+
+ // Now the history looks like:
+ // file_bug640387.html
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash2 <-- current
+
+ // Going back should trigger a hashchange, which will wake us up from the
+ // yield.
+ popup.history.back();
+ yield undefined;
+ ok(true, "Got first hashchange.");
+
+ // Going back should wake us up again.
+ popup.history.back();
+ yield undefined;
+ ok(true, "Got second hashchange.");
+
+ // Now the history looks like:
+ // file_bug640387.html <-- current
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash2
+
+ // Going forward should trigger a hashchange.
+ popup.history.forward();
+ yield undefined;
+ ok(true, "Got third hashchange.");
+
+ // Now modify the history so it looks like:
+ // file_bug640387.html
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash1 <-- current
+ popup.history.pushState("", "", "#hash1");
+
+ // Now when we go back, we should not get a hashchange. Instead, wait for a
+ // popstate. We need to asynchronously go back because popstate is fired
+ // sync.
+ gHashchangeExpected = false;
+ gCallbackOnPopstate = true;
+ SimpleTest.executeSoon(function() { popup.history.back(); });
+ yield undefined;
+ ok(true, "Got popstate.");
+ gCallbackOnPopstate = false;
+
+ // Spin the event loop so hashchange has a chance to fire, if it's going to.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+var gGen = null;
+function childLoad() {
+ gGen = test();
+ gGen.next();
+}
+
+var gHashchangeExpected = true;
+function childHashchange() {
+ if (gHashchangeExpected) {
+ gGen.next();
+ } else {
+ ok(false, "Got hashchange when we weren't expecting one.");
+ }
+}
+
+var gCallbackOnPopstate = false;
+function childPopstate() {
+ if (gCallbackOnPopstate) {
+ gGen.next();
+ }
+}
+
+/* We need to run this test in a popup, because navigating an iframe
+ * back/forwards tends to cause intermittent orange. */
+var popup = window.open("file_bug640387.html");
+
+/* Control now flows up to childLoad(), called once the popup loads. */
+
+</script>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640387
+-->
+<head>
+ <title>Test for Bug 640387</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640387">Mozilla Bug 640387</a>
+
+<!-- Test that, when going from
+
+ http://example.com/#foo
+
+to
+
+ http://example.com/
+
+via a non-history load, we do a true load, rather than a scroll. -->
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+var callbackOnLoad = false;
+function childLoad() {
+ if (callbackOnLoad) {
+ callbackOnLoad = false;
+ gGen.next();
+ }
+}
+
+var errorOnHashchange = false;
+var callbackOnHashchange = false;
+function childHashchange() {
+ if (errorOnHashchange) {
+ ok(false, "Got unexpected hashchange.");
+ }
+ if (callbackOnHashchange) {
+ callbackOnHashchange = false;
+ gGen.next();
+ }
+}
+
+function* run_test() {
+ var iframe = $("iframe").contentWindow;
+
+ ok(true, "Got first load");
+
+ // Spin the event loop so we exit the onload handler.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ let origLocation = iframe.location + "";
+ callbackOnHashchange = true;
+ iframe.location.hash = "#1";
+ // Wait for a hashchange event.
+ yield undefined;
+
+ ok(true, "Got hashchange.");
+
+ iframe.location = origLocation;
+ // This should produce a load event and *not* a hashchange, because the
+ // result of the load is a different document than we had previously.
+ callbackOnLoad = true;
+ errorOnHashchange = true;
+ yield undefined;
+
+ ok(true, "Got final load.");
+
+ // Spin the event loop to give hashchange a chance to fire, if it's going to.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ SimpleTest.finish();
+}
+
+callbackOnLoad = true;
+var gGen = run_test();
+
+</script>
+
+<iframe id='iframe' src='file_bug640387.html'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug653741.html b/docshell/test/mochitest/test_bug653741.html
new file mode 100644
index 0000000000..ca29c5ef08
--- /dev/null
+++ b/docshell/test/mochitest/test_bug653741.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=653741
+-->
+<head>
+ <title>Test for Bug 653741</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=653741">Mozilla Bug 653741</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 653741 **/
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let cw = $("iframe").contentWindow;
+
+ // Save the Y offset. For sanity's sake, make sure it's not 0, because we
+ // should be at the bottom of the page!
+ let origYOffset = Math.round(cw.pageYOffset);
+ ok(origYOffset != 0, "Original Y offset is not 0.");
+
+ // Scroll the iframe to the top, then navigate to #bottom again.
+ cw.scrollTo(0, 0);
+
+ // Our current location is #bottom, so this should scroll us down to the
+ // bottom again.
+ cw.location = cw.location + "";
+
+ is(Math.round(cw.pageYOffset), origYOffset, "Correct offset after reloading page.");
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe height='100px' id='iframe' src='file_bug653741.html#bottom'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug660404.html b/docshell/test/mochitest/test_bug660404.html
new file mode 100644
index 0000000000..a23467d3b6
--- /dev/null
+++ b/docshell/test/mochitest/test_bug660404.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660404
+-->
+<head>
+ <title>Test for Bug 660404</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660404">Mozilla Bug 660404</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 660404 **/
+SimpleTest.waitForExplicitFinish();
+
+var w;
+
+function continueTest() {
+ // Do this async so the load event can finish firing
+ SimpleTest.executeSoon(function() {
+ w.onpagehide = function(ev) {
+ is(ev.persisted, true, "Should be bfcached when navigating to multipart");
+ };
+ w.location.href = "file_bug660404";
+ });
+}
+
+function finishTest() {
+ is(w.document.documentElement.textContent, "opener.finishTest();");
+ is(w.document.documentElement.innerHTML, "<head><script>opener.finishTest();</" +
+ "script></head>");
+ w.close();
+ SimpleTest.finish();
+}
+
+// Have to open a new window, since there's no bfcache in subframes
+w = window.open("file_bug660404-1.html");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug662170.html b/docshell/test/mochitest/test_bug662170.html
new file mode 100644
index 0000000000..fa0f3b6d4c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug662170.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=662170
+-->
+<head>
+ <title>Test for Bug 662170</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=662170">Mozilla Bug 662170</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 662170 **/
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let cw = $("iframe").contentWindow;
+
+ // When we initially load the page, we should be at the top.
+ is(cw.pageYOffset, 0, "Initial Y offset should be 0.");
+
+ // Scroll the iframe to the bottom.
+ cw.scrollTo(0, 300);
+
+ // Did we actually scroll somewhere?
+ isnot(Math.round(cw.pageYOffset), 0, "Y offset should be non-zero after scrolling.");
+
+ // Now load file_bug662170.html#, which should take us to the top of the
+ // page.
+ cw.location = cw.location + "#";
+
+ is(cw.pageYOffset, 0, "Correct Y offset after loading #.");
+ SimpleTest.finish();
+}
+
+</script>
+
+<!-- When the iframe loads, it calls childLoad(). -->
+<iframe height='100px' id='iframe' src='file_bug662170.html'></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=668513
+-->
+<head>
+ <title>Test for Bug 668513</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=668513">Mozilla Bug 668513</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+SimpleTest.waitForExplicitFinish();
+window.open("file_bug668513.html");
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug669671.html b/docshell/test/mochitest/test_bug669671.html
new file mode 100644
index 0000000000..c45f4ea89b
--- /dev/null
+++ b/docshell/test/mochitest/test_bug669671.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=669671
+-->
+<head>
+ <title>Test for Bug 669671</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=669671">Mozilla Bug 669671</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 669671.
+ *
+ * This is a bit complicated. We have a script, file_bug669671.sjs, which counts
+ * how many times it's loaded and returns that count in the body of an HTML
+ * document. For brevity, call this page X.
+ *
+ * X is sent with Cache-Control: max-age=0 and can't be bfcached (it has an
+ * onunload handler). Our test does the following in a popup:
+ *
+ * 1) Load X?pushed, to prime the cache.
+ * 2) Navigate to X.
+ * 3) Call pushState and navigate from X to X?pushed.
+ * 4) Navigate to X?navigated.
+ * 5) Go back (to X?pushed).
+ *
+ * We do all this work so we can check that in step 5, we fetch X?pushed from
+ * the network -- we shouldn't use our cached copy, because of the
+ * cache-control header X sends.
+ *
+ * Then we go back and repeat the whole process but call history.replaceState
+ * instead of pushState. And for good measure, we test once more, this time
+ * modifying only the hash of the URI using replaceState. In this case, we
+ * *should* load from the cache.
+ *
+ **/
+SimpleTest.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+
+function onChildLoad() {
+ SimpleTest.executeSoon(function() { gGen.next(); });
+}
+
+var _loadCount = 0;
+function checkPopupLoadCount() {
+ is(popup.document.body.innerHTML, _loadCount + "", "Load count");
+
+ // We normally want to increment _loadCount here. But if the test fails
+ // because we didn't do a load we should have, let's not cause a cascade of
+ // failures by incrementing _loadCount.
+ var origCount = _loadCount;
+ if (popup.document.body.innerHTML >= _loadCount + "")
+ _loadCount++;
+ return origCount;
+}
+
+function* test() {
+ // Step 0 - Make sure the count is reset to 0 in case of reload
+ popup.location = "file_bug669671.sjs?countreset";
+ yield;
+ is(popup.document.body.innerHTML, "0",
+ "Load count should be reset to 0");
+
+ // Step 1 - The popup's body counts how many times we've requested the
+ // resource. This is the first time we've requested it, so it should be '0'.
+ checkPopupLoadCount();
+
+ // Step 2 - We'll get another onChildLoad when this finishes.
+ popup.location = "file_bug669671.sjs";
+ yield undefined;
+
+ // Step 3 - Call pushState and change the URI back to ?pushed.
+ checkPopupLoadCount();
+ popup.history.pushState("", "", "?pushed");
+
+ // Step 4 - Navigate away. This should trigger another onChildLoad.
+ popup.location = "file_bug669671.sjs?navigated-1";
+ yield undefined;
+
+ // Step 5 - Go back. This should result in another onload (because the file is
+ // not in bfcache) and should be the fourth time we've requested the sjs file.
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+
+ // This is the check which was failing before we fixed the bug.
+ checkPopupLoadCount();
+
+ popup.close();
+
+ // Do the whole thing again, but with replaceState.
+ popup = window.open("file_bug669671.sjs?replaced");
+ yield undefined;
+ checkPopupLoadCount();
+ popup.location = "file_bug669671.sjs";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.replaceState("", "", "?replaced");
+ popup.location = "file_bug669671.sjs?navigated-2";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+ checkPopupLoadCount();
+ popup.close();
+
+ // Once more, with feeling. Notice that we don't have to prime the cache
+ // with an extra load here, because X and X#hash share the same cache entry.
+ popup = window.open("file_bug669671.sjs?hash-test");
+ yield undefined;
+ var initialCount = checkPopupLoadCount();
+ popup.history.replaceState("", "", "#hash");
+ popup.location = "file_bug669671.sjs?navigated-3";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+ is(popup.document.body.innerHTML, initialCount + "",
+ "Load count (should be cached)");
+ popup.close();
+
+ SimpleTest.finish();
+}
+
+var gGen = test();
+var popup;
+
+// Disable RCWN to make cache behavior deterministic.
+SpecialPowers.pushPrefEnv({set: [["network.http.rcwn.enabled", false]]}, () => {
+ // This will call into onChildLoad once it loads.
+ popup = window.open("file_bug669671.sjs?pushed");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug675587.html b/docshell/test/mochitest/test_bug675587.html
new file mode 100644
index 0000000000..e6bd71a47f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug675587.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=675587
+-->
+<head>
+ <title>Test for Bug 675587</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=675587">Mozilla Bug 675587</a>
+<p id="display">
+ <iframe src="file_bug675587.html#hash"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 675587 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ ok(window.frames[0].location.href.endsWith("file_bug675587.html#"),
+ "Should have the right href");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=680257
+-->
+<head>
+ <title>Test for Bug 680257</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=680257">Mozilla Bug 680257</a>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var popup = window.open("file_bug680257.html");
+
+var gTestContinuation = null;
+function continueAsync() {
+ popup.addEventListener("hashchange",
+ function(e) { gTestContinuation.next(); }, { once: true });
+}
+
+// The popup will call into popupLoaded() once it loads.
+function popupLoaded() {
+ // runTests() needs to be called from outside popupLoaded's onload handler.
+ // Otherwise, the navigations we do in runTests won't create new SHEntries.
+ SimpleTest.executeSoon(function() {
+ if (!gTestContinuation) {
+ gTestContinuation = runTests();
+ }
+ gTestContinuation.next();
+ });
+}
+
+function* runTests() {
+ checkPopupLinkStyle(false, "Initial");
+
+ popup.location.hash = "a";
+ continueAsync();
+ yield;
+ checkPopupLinkStyle(true, "After setting hash");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+
+ checkPopupLinkStyle(false, "After going back");
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ checkPopupLinkStyle(true, "After going forward");
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+function checkPopupLinkStyle(isTarget, desc) {
+ var link = popup.document.getElementById("a");
+ var style = popup.getComputedStyle(link);
+ var color = style.getPropertyValue("color");
+
+ // Color is red if isTarget, black otherwise.
+ if (isTarget) {
+ is(color, "rgb(255, 0, 0)", desc);
+ } else {
+ is(color, "rgb(0, 0, 0)", desc);
+ }
+}
+
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691547
+-->
+<head>
+ <title>Test for Bug 691547</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var navStart = 0;
+ var beforeReload = 0;
+ function onContentLoad() {
+ var frame = frames[0];
+ if (!navStart) {
+ // First time we perform navigation in subframe. The bug is that
+ // load in subframe causes timing.navigationStart to be recorded
+ // as if it was a start of the next navigation.
+ var innerFrame = frame.frames[0];
+ navStart = frame.performance.timing.navigationStart;
+ innerFrame.location = "bug570341_recordevents.html";
+ // Let's wait a bit so the difference is clear anough.
+ setTimeout(reload, 3000);
+ } else {
+ // Content reloaded, time to check. We are allowing a huge time slack,
+ // in case clock is imprecise. If we have a bug, the difference is
+ // expected to be about the timeout value set above.
+ var diff = frame.performance.timing.navigationStart - beforeReload;
+ ok(diff >= -200,
+ "navigationStart should be set after reload request. " +
+ "Measured difference: " + diff + " (should be positive)");
+ SimpleTest.finish();
+ }
+ }
+ function reload() {
+ var frame = frames[0];
+ ok(navStart == frame.performance.timing.navigationStart,
+ "navigationStart should not change when frame loads.");
+ beforeReload = Date.now();
+ frame.location.reload();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a>
+<div id="frames">
+<iframe name="frame0" id="frame0" src="bug691547_frame.html" onload="onContentLoad()"></iframe>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug694612.html b/docshell/test/mochitest/test_bug694612.html
new file mode 100644
index 0000000000..445d0433d8
--- /dev/null
+++ b/docshell/test/mochitest/test_bug694612.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=694612
+-->
+<head>
+ <title>Test for Bug 694612</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=694612">Mozilla Bug 694612</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 694612 **/
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+ ok(event.data.result, "should have performance API in an <object>");
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+</script>
+<object type="text/html"
+ data="data:text/html,<script>parent.postMessage({result:performance!=null},'*');</script>">
+</object>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug703855.html b/docshell/test/mochitest/test_bug703855.html
new file mode 100644
index 0000000000..8e3d9b5a04
--- /dev/null
+++ b/docshell/test/mochitest/test_bug703855.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=703855
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 703855</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=703855">Mozilla Bug 703855</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="f" src="file_bug703855.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 703855 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var timingAttributes = [
+ "connectEnd",
+ "connectStart",
+ "domComplete",
+ "domContentLoadedEventEnd",
+ "domContentLoadedEventStart",
+ "domInteractive",
+ "domLoading",
+ "domainLookupEnd",
+ "domainLookupStart",
+ "fetchStart",
+ "loadEventEnd",
+ "loadEventStart",
+ "navigationStart",
+ "redirectEnd",
+ "redirectStart",
+ "requestStart",
+ "responseEnd",
+ "responseStart",
+ "unloadEventEnd",
+ "unloadEventStart",
+];
+var originalTiming = {};
+
+function runTest() {
+ var timing = $("f").contentWindow.performance.timing;
+ for (let i in timingAttributes) {
+ originalTiming[timingAttributes[i]] = timing[timingAttributes[i]];
+ }
+
+ var doc = $("f").contentDocument;
+ doc.open();
+ doc.write("<!DOCTYPE html>");
+ doc.close();
+
+ SimpleTest.executeSoon(function() {
+ var newTiming = $("f").contentWindow.performance.timing;
+ for (let i in timingAttributes) {
+ is(newTiming[timingAttributes[i]], originalTiming[timingAttributes[i]],
+ "document.open should not affect value of " + timingAttributes[i]);
+ }
+ SimpleTest.finish();
+ });
+}
+
+addLoadEvent(function() {
+ SimpleTest.executeSoon(runTest);
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=728939
+-->
+<head>
+ <title>Test for Bug 728939</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=728939">Mozilla Bug 728939</a>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Called when the popup finishes loading.
+function popupLoaded() {
+ popup.location.hash = "#foo";
+ is(popup.document.URL, popup.location.href, "After hashchange.");
+
+ popup.history.pushState("", "", "bar");
+ is(popup.document.URL, popup.location.href, "After pushState.");
+
+ popup.history.replaceState("", "", "baz");
+ is(popup.document.URL, popup.location.href, "After replaceState.");
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+var popup = window.open("file_bug728939.html");
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug797909.html b/docshell/test/mochitest/test_bug797909.html
new file mode 100644
index 0000000000..75e91d9052
--- /dev/null
+++ b/docshell/test/mochitest/test_bug797909.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=797909
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 797909</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=797909">Mozilla Bug 797909</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+ /** Test for Bug 797909 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ var iframe = document.getElementById("ifr");
+ try {
+ iframe.contentWindow.document;
+ ok(false, "Should have thrown an exception");
+ } catch (ex) {
+ ok(true, "Got an exception");
+ }
+
+ iframe = document.createElement("iframe");
+ // set sandbox attribute
+ iframe.sandbox = "allow-scripts";
+ // and then insert into the doc
+ document.body.appendChild(iframe);
+
+ try {
+ iframe.contentWindow.document;
+ ok(false, "Should have thrown an exception");
+ } catch (ex) {
+ ok(true, "Got an exception");
+ }
+
+ iframe = document.createElement("iframe");
+ // set sandbox attribute
+ iframe.sandbox = "allow-same-origin";
+ // and then insert into the doc
+ document.body.appendChild(iframe);
+
+ try {
+ iframe.contentWindow.document;
+ ok(true, "Shouldn't have thrown an exception");
+ } catch (ex) {
+ ok(false, "Got an unexpected exception");
+ }
+
+ SimpleTest.finish();
+ }
+
+</script>
+</pre>
+<iframe id="ifr" sandbox = "allow-scripts"></iframe>
+</body>
+</html>
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 @@
+<!doctype html>
+<title>Test for closing window in pagehide event callback caused by history.back()</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432396">Mozilla Bug 1432396</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const w = window.open("file_close_onpagehide1.html");
+window.addEventListener("message", e => {
+ is(e.data, "initial", "The initial page loaded");
+ window.addEventListener("message", evt => {
+ is(evt.data, "second", "The second page loaded");
+ w.onpagehide = () => {
+ w.close();
+ info("try to close the popped up window in onpagehide");
+ SimpleTest.finish();
+ };
+ w.history.back();
+ }, { once: true });
+ w.location = "file_close_onpagehide2.html";
+}, { once: true });
+</script>
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 @@
+<!doctype html>
+<title>Test for closing window in pagehide event callback caused by window.close()</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432396">Mozilla Bug 1432396</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const w = window.open("file_close_onpagehide1.html");
+window.addEventListener("message", e => {
+ is(e.data, "initial", "The initial page loaded");
+ w.onpagehide = () => {
+ w.close();
+ info("try to close the popped up window in onpagehide");
+ SimpleTest.finish();
+ };
+ w.close();
+}, { once: true });
+</script>
diff --git a/docshell/test/mochitest/test_compressed_multipart.html b/docshell/test/mochitest/test_compressed_multipart.html
new file mode 100644
index 0000000000..ff182d33fc
--- /dev/null
+++ b/docshell/test/mochitest/test_compressed_multipart.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1600211
+
+Loads a document that is served as multipart/x-mixed-replace as well as gzip compressed.
+Checks that we correctly decompress and display it (via running JS within the document to notify us).
+-->
+<head>
+ <title>Test for Bug 1600211</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1600211">Mozilla Bug 1600211</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1600211 **/
+SimpleTest.waitForExplicitFinish();
+
+var w;
+
+function finishTest() {
+ is(w.document.documentElement.textContent, "opener.finishTest();");
+ is(w.document.documentElement.innerHTML, "<head><script>opener.finishTest();</" +
+ "script></head>");
+ w.close();
+ SimpleTest.finish();
+}
+
+w = window.open("file_compressed_multipart");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_double_submit.html b/docshell/test/mochitest/test_double_submit.html
new file mode 100644
index 0000000000..f17b4138ee
--- /dev/null
+++ b/docshell/test/mochitest/test_double_submit.html
@@ -0,0 +1,108 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for Bug 1590762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <iframe name="targetFrame" id="targetFrame"></iframe>
+ <form id="form" action="double_submit.sjs?size=4096&fragments=10&delay=1000" method="POST" target="targetFrame">
+ <input id="input" type="text" name="text" value="value">
+ <input id="token" type="text" name="token" value="">
+ <input id="button" type="submit">
+ </form>
+ <script>
+ "use strict";
+
+ const CROSS_ORIGIN_URI = "http://test1.example.com/tests/docshell/test/mochitest/ping.html";
+
+ function generateBody(size) {
+ let data = new Uint8Array(size);
+ for (let i = 0; i < size; ++i) {
+ data[i] = 97 + Math.random() * (123 - 97);
+ }
+
+ return new TextDecoder().decode(data);
+ }
+
+ function asyncClick(counts) {
+ let frame = document.createElement('iframe');
+ frame.addEventListener(
+ 'load', () => frame.contentWindow.postMessage({command: "start"}, "*"),
+ { once:true });
+ frame.src = "clicker.html";
+
+ addEventListener('message', ({source}) => {
+ if (source === frame.contentWindow) {
+ counts.click++;
+ synthesizeMouse(document.getElementById('button'), 5, 5, {});
+ }
+ }, { once: true });
+
+ document.body.appendChild(frame);
+ return stop;
+ }
+
+ function click(button) {
+ synthesizeMouse(button, 5, 5, {});
+ }
+
+ add_task(async function runTest() {
+ let frame = document.getElementById('targetFrame');
+ await new Promise(resolve => {
+ addEventListener('message', resolve, {once: true});
+ frame.src = CROSS_ORIGIN_URI;
+ });
+
+ let form = document.getElementById('form');
+ let button = document.getElementById('button');
+
+ document.getElementById('input').value = generateBody(1024*1024);
+ let token = document.getElementById('token');
+ token.value = "first";
+
+ await new Promise((resolve, reject) => {
+ let counts = { click: 0, submit: 0 };
+ form.addEventListener('submit', () => counts.submit++);
+ asyncClick(counts);
+ form.requestSubmit(button);
+ token.value = "bad";
+ let steps = {
+ good: {
+ entered: false,
+ next: () => { steps.good.entered = true; resolve(); },
+ assertion: () => {
+ ok(steps.first.entered && !steps.bad.entered, "good comes after first, but not bad")
+ }
+ },
+ first: {
+ entered: false,
+ next: () => { steps.first.entered = true; token.value = "good"; click(button); },
+ assertion: () => {
+ ok(!steps.good.entered && !steps.bad.entered, "first message is first")
+ is(counts.click, 1, "clicked");
+ is(counts.submit, 2, "did submit");
+ }
+ },
+ bad: {
+ entered: false,
+ next: () => { reject(); },
+ assertion: () => ok(false, "we got a bad message")
+ }
+ };
+ addEventListener('message', ({source, data}) => {
+ if (source !== frame.contentWindow) {
+ return;
+ }
+
+ let step = steps[data] || reject;
+ step.assertion();
+ step.next();
+ })
+ });
+ });
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script type="text/javascript">
+
+var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+var loadInfo = channel.loadInfo;
+
+// 1) perform some sanity checks
+var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+var principalToInherit = channel.loadInfo.principalToInherit.asciiSpec;
+
+ok(triggeringPrincipal.startsWith("http://mochi.test:8888/")
+ || triggeringPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial triggeringPrincipal correct");
+ok(loadingPrincipal.startsWith("http://mochi.test:8888/")
+ || loadingPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial loadingPrincipal correct");
+ok(principalToInherit.startsWith("http://mochi.test:8888/")
+ || principalToInherit.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial principalToInherit correct");
+
+// reset principals on the loadinfo
+loadInfo.resetPrincipalToInheritToNullPrincipal();
+
+// 2) verify loadInfo contains the correct principals
+triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+principalToInherit = channel.loadInfo.principalToInherit;
+
+ok(triggeringPrincipal.startsWith("http://mochi.test:8888/")
+ || triggeringPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "triggeringPrincipal after resetting correct");
+ok(loadingPrincipal.startsWith("http://mochi.test:8888/")
+ || loadingPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "loadingPrincipal after resetting correct");
+ok(principalToInherit.isNullPrincipal
+ || principalToInherit.startsWith("http://mochi.xorigin-test:8888/"),
+ "principalToInherit after resetting correct");
+
+// 3) verify that getChannelResultPrincipal returns right principal
+var resultPrincipal = SpecialPowers.Services.scriptSecurityManager
+ .getChannelResultPrincipal(channel);
+
+ok(resultPrincipal.isNullPrincipal,
+ "resultPrincipal after resetting correct");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_framedhistoryframes.html b/docshell/test/mochitest/test_framedhistoryframes.html
new file mode 100644
index 0000000000..a1bffeed1a
--- /dev/null
+++ b/docshell/test/mochitest/test_framedhistoryframes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+SimpleTest.waitForExplicitFinish();
+var win = window.open("file_framedhistoryframes.html");
+
+function done() {
+ win.close();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_history_length_during_pageload.html b/docshell/test/mochitest/test_history_length_during_pageload.html
new file mode 100644
index 0000000000..c1ac332791
--- /dev/null
+++ b/docshell/test/mochitest/test_history_length_during_pageload.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test history.length during page load</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest(testAsyncLength) {
+ return new Promise(function(resolve) {
+ var initialLength;
+ var messageCount = 0;
+ var win;
+ window.onmessage = function(event) {
+ if (event.data.initialLength) {
+ initialLength = event.data.initialLength;
+ } else {
+ is(event.data.length, initialLength + 1,
+ "history.length should increase by one when loading a new page");
+ }
+ if (++messageCount == 3) {
+ win.close();
+ resolve();
+ }
+ }
+ win = window.open("file_history_length_during_pageload.html?testAsyncLength=" + testAsyncLength);
+ });
+ }
+
+ async function runTests() {
+ await runTest(!SpecialPowers.Services.appinfo.sessionHistoryInParent);
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTests();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
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..75c5602ccb
--- /dev/null
+++ b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body onload="test()">
+ <script>
+ /*
+ Test to verify that when we change an OOP iframe to one that has a
+ srcdoc it loads in the correct process, which in this case is this
+ test document.
+ */
+ SimpleTest.waitForExplicitFinish();
+ async function test() {
+ // Create an OOP iframe
+ let frame = document.createElement("iframe");
+ await new Promise(r => {
+ frame.onload = r;
+ document.body.appendChild(frame);
+ frame.contentWindow.location = "http://example.net/tests/docshell/test/dummy_page.html";
+ });
+ if (SpecialPowers.useRemoteSubframes) {
+ ok(SpecialPowers.Cu.isRemoteProxy(frame.contentWindow), "should be a remote frame");
+ }
+
+ // Remove the attribute so we can set a srcdoc attribute on it
+ frame.removeAttribute("src");
+
+ // Set a srcdoc attribute on this iframe and wait for the load
+ await new Promise(r => {
+ frame.onload = r;
+ frame.setAttribute("srcdoc", '<html><body>body of the srcdoc frame</body></html>');
+ });
+
+ // We should be in the same process as this test document
+ ok(!SpecialPowers.Cu.isRemoteProxy(frame.contentWindow), "should NOT be a remote frame");
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+
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..dbfbae98dd
--- /dev/null
+++ b/docshell/test/mochitest/test_navigate_after_pagehide.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for navigation attempts by scripts in inactive inner window</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+<body>
+<iframe src="dummy_page.html" id="iframe"></iframe>
+
+<script>
+"use strict";
+
+add_task(async function() {
+ let iframe = document.getElementById("iframe");
+
+ let navigate = iframe.contentWindow.eval(`(function() {
+ location.href = "/";
+ })`);
+
+ iframe.src = "http://example.com/";
+ await new Promise(resolve =>
+ iframe.addEventListener("load", resolve, { once: true })
+ );
+
+ // This should do nothing. But, importantly, it should especially not crash.
+ navigate();
+
+ ok(true, "We didn't crash");
+});
+</script>
+</body>
+</html>
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..20444d8e86
--- /dev/null
+++ b/docshell/test/mochitest/test_pushState_after_document_open.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=957479
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 957479</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 957479 **/
+ SimpleTest.waitForExplicitFinish();
+ // Child needs to invoke us, otherwise our onload will fire before the child
+ // has done the write/close bit.
+ onmessage = function doTest() {
+ is(frames[0].location.pathname, "/tests/docshell/test/mochitest/file_pushState_after_document_open.html",
+ "Should have the right path here");
+ is(frames[0].location.hash, "", "Should have the right hash here");
+ frames[0].history.pushState({}, "", frames[0].document.URL + "#foopy");
+ is(frames[0].location.pathname, "/tests/docshell/test/mochitest/file_pushState_after_document_open.html",
+ "Pathname should not have changed");
+ is(frames[0].location.hash, "#foopy", "Hash should have changed");
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957479">Mozilla Bug 957479</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="file_pushState_after_document_open.html"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_redirect_history.html b/docshell/test/mochitest/test_redirect_history.html
new file mode 100644
index 0000000000..82754ad7a4
--- /dev/null
+++ b/docshell/test/mochitest/test_redirect_history.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for redirect from POST</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script>
+ "use strict";
+
+ info("Starting tests");
+
+ let tests = new Map([
+ ["sameorigin", window.location.origin],
+ ["crossorigin", "http://test1.example.com"],
+ ]);
+ for (let [kind, origin] of tests) {
+ add_task(async function runTest() {
+ info(`Submitting to ${origin}`);
+
+ let win;
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Loading file_redirect_history.html");
+ win = window.open("file_redirect_history.html");
+ });
+ info("Done loading file_redirect_history.html");
+
+ let length = win.history.length;
+ let loc = win.location.toString();
+
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Posting");
+ win.postMessage(`${origin}/tests/docshell/test/mochitest/form_submit_redirect.sjs`, "*")
+ });
+ info("Done posting\n");
+ is(win.history.length, length, `Test ${kind}: history length should not change.`);
+ info(`Length=${win.history.length}`);
+ is(win.location.toString(), loc, `Test ${kind}: location should not change.`);
+
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Reloading");
+ win.location.reload();
+ });
+ info("Done reloading\n");
+ is(win.location.toString(), loc, `Test ${kind}: location should not change after reload.`);
+
+ win.close();
+ });
+ }
+ </script>
+ </body>
+</html>
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..7b25167d1e
--- /dev/null
+++ b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const SAME_ORIGIN_URI = "http://mochi.test:8888/tests/docshell/test/dummy_page.html";
+const CROSS_ORIGIN_URI = "http://example.com/tests/docshell/test/dummy_page.html";
+const NUMBER_OF_TESTS = 3;
+let testCounter = 0;
+
+function checkFinish() {
+ testCounter++;
+ if (testCounter < NUMBER_OF_TESTS) {
+ return;
+ }
+ SimpleTest.finish();
+}
+
+// ---- test 1 ----
+
+let myFrame1 = document.createElement("iframe");
+myFrame1.src = SAME_ORIGIN_URI;
+myFrame1.addEventListener("load", checkLoadFrame1);
+document.documentElement.appendChild(myFrame1);
+
+function checkLoadFrame1() {
+ myFrame1.removeEventListener("load", checkLoadFrame1);
+ // window.location.href is no longer cross-origin accessible in gecko.
+ is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI,
+ "initial same origin dummy loaded into frame1");
+
+ SpecialPowers.wrap(myFrame1.contentWindow).location.hash = "#bar";
+ is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
+ "initial same origin dummy#bar loaded into iframe1");
+
+ myFrame1.addEventListener("load", checkNavFrame1);
+ myFrame1.src = CROSS_ORIGIN_URI;
+}
+
+async function checkNavFrame1() {
+ myFrame1.removeEventListener("load", checkNavFrame1);
+ is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+ CROSS_ORIGIN_URI,
+ "cross origin dummy loaded into frame1");
+
+ myFrame1.addEventListener("load", checkBackNavFrame1);
+ myFrame1.src = SAME_ORIGIN_URI + "#bar";
+}
+
+async function checkBackNavFrame1() {
+ myFrame1.removeEventListener("load", checkBackNavFrame1);
+ is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+ SAME_ORIGIN_URI + "#bar",
+ "navagiating back to same origin dummy for frame1");
+ checkFinish();
+}
+
+// ---- test 2 ----
+
+let myFrame2 = document.createElement("iframe");
+myFrame2.src = "about:blank";
+myFrame2.addEventListener("load", checkLoadFrame2);
+document.documentElement.appendChild(myFrame2);
+
+function checkLoadFrame2() {
+ myFrame2.removeEventListener("load", checkLoadFrame2);
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+ "initial about:blank frame loaded");
+
+ myFrame2.contentWindow.location.hash = "#foo";
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank#foo",
+ "about:blank#foo frame loaded");
+
+ myFrame2.addEventListener("load", checkHistoryFrame2);
+ myFrame2.src = "about:blank";
+}
+
+function checkHistoryFrame2() {
+ myFrame2.removeEventListener("load", checkHistoryFrame2);
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+ "about:blank frame loaded again");
+ checkFinish();
+}
+
+// ---- test 3 ----
+
+let myFrame3 = document.createElement("frame");
+document.documentElement.appendChild(myFrame3);
+myFrame3.contentWindow.location.hash = "#foo";
+
+is(myFrame3.contentWindow.location.href, "about:blank#foo",
+ "created history entry with about:blank#foo");
+checkFinish();
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_windowedhistoryframes.html b/docshell/test/mochitest/test_windowedhistoryframes.html
new file mode 100644
index 0000000000..c2c148b838
--- /dev/null
+++ b/docshell/test/mochitest/test_windowedhistoryframes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+ subWin.close();
+ SimpleTest.finish();
+}
+
+var subWin = window.open("historyframes.html", "_blank");
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<p id='text'>Test1</p>
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 @@
+<p id='text'>Test2</p>
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/.eslintrc.js b/docshell/test/navigation/.eslintrc.js
new file mode 100644
index 0000000000..af3d7f7c7f
--- /dev/null
+++ b/docshell/test/navigation/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test", "plugin:mozilla/mochitest-test"],
+
+ plugins: ["no-unsanitized"],
+
+ rules: {
+ "no-unsanitized/method": "off",
+ },
+};
diff --git a/docshell/test/navigation/NavigationUtils.js b/docshell/test/navigation/NavigationUtils.js
new file mode 100644
index 0000000000..4d9d40a3c6
--- /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;
+
+async 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);
+ await 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 @@
+<html><body>This is a blank document.</body></html> \ 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 @@
+<html><head>
+<script> window.addEventListener("pageshow", function() { opener.nextTest(); }); </script>
+</head><body>
+<div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:blue">
+<p>This is a very tall blue box.</p>
+</div></body></html>
diff --git a/docshell/test/navigation/browser.ini b/docshell/test/navigation/browser.ini
new file mode 100644
index 0000000000..f5b7f427df
--- /dev/null
+++ b/docshell/test/navigation/browser.ini
@@ -0,0 +1,15 @@
+[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
+
+[browser_test_bfcache_eviction.js]
+skip-if = fission # bug 1665165
+[browser_bug343515.js]
+[browser_test-content-chromeflags.js]
+tags = openwindow
+[browser_ghistorymaxsize_is_0.js]
diff --git a/docshell/test/navigation/browser_bug343515.js b/docshell/test/navigation/browser_bug343515.js
new file mode 100644
index 0000000000..35acf9a6b5
--- /dev/null
+++ b/docshell/test/navigation/browser_bug343515.js
@@ -0,0 +1,267 @@
+// 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.loadURI(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.waitForContentEvent(
+ ctx.tab2Browser,
+ "pageshow"
+ );
+ 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.loadURI(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.waitForContentEvent(
+ ctx.tab2Browser,
+ "pageshow"
+ );
+ 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..4034f63bd9
--- /dev/null
+++ b/docshell/test/navigation/browser_ghistorymaxsize_is_0.js
@@ -0,0 +1,81 @@
+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.loadURI(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..af527b2dbc
--- /dev/null
+++ b/docshell/test/navigation/browser_test-content-chromeflags.js
@@ -0,0 +1,57 @@
+const TEST_PAGE = `data:text/html,<html><body><a href="about:blank" target="_blank">Test</a></body></html>`;
+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..70cb258b7c
--- /dev/null
+++ b/docshell/test/navigation/browser_test_bfcache_eviction.js
@@ -0,0 +1,96 @@
+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],
+ ],
+ });
+
+ // 1. Open a tab
+ var testPage =
+ "data:text/html,<html id='html1'><body id='body1'>First tab ever opened</body></html>";
+ 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,<html id='html1'><body id='body1'>I am a second tab!</body></html>`;
+ 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,<html id='html1'><body id='body1'>${i}</body></html>`;
+ let pagePromise = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURI(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/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 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 1
+ </body>
+</html>
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 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 2
+ <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 0</body></html>"></iframe>
+ <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 1</body></html>"></iframe>
+ </body>
+</html>
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 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 3
+ <iframe src="bug343515_pg3_1.html"></iframe>
+ <iframe src="bug343515_pg3_2.html"></iframe>
+ </body>
+</html>
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 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>pg3 - iframe 0
+ <iframe src="bug343515_pg3_1_1.html"></iframe>
+ </body>
+</html>
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 @@
+<html><head><meta charset="UTF-8"/></head><body>How far does the rabbit hole go?</body></html>
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 @@
+<html><head><meta charset="UTF-8"/></head><body>pg3 iframe 1</body></html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <body onload="test();">
+ <script>
+ /**
+ * Bug 1300461 identifies that if a history entry was not bfcached, and
+ * a http redirection happens when navigating to that entry, the history
+ * index would mess up.
+ *
+ * The test case emulates the circumstance by the following steps
+ * 1) Navigate to file_bug1300461_back.html which is not bf-cachable.
+ * 2) In file_bug1300461_back.html, replace its own history state to
+ * file_bug1300461_redirect.html.
+ * 3) Back, and then forward. Since the document is not in bfcache, it
+ * tries to load file_bug1300461_redirect.html directly.
+ * 4) file_bug1300461_redirect.html redirects UA to
+ * file_bug1300461_back.html through HTTP 301 header.
+ *
+ * We verify the history index, canGoBack, canGoForward, etc. keep correct
+ * in this process.
+ */
+ let Ci = SpecialPowers.Ci;
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ let testSteps = [
+ function() {
+ opener.is(shistory.count, 1, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(!webNav.canGoForward, "check canGoForward");
+ setTimeout(() => window.location = "file_bug1300461_back.html", 0);
+ },
+ function() {
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(webNav.canGoForward, "check canGoForward");
+ window.history.forward();
+ },
+ function() {
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(webNav.canGoForward, "check canGoForward");
+ opener.info("file_bug1300461.html tests finished");
+ opener.finishTest();
+ },
+ ];
+
+ function test() {
+ if (opener) {
+ opener.info("file_bug1300461.html test " + opener.testCount);
+ testSteps[opener.testCount++]();
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1300461_back.html b/docshell/test/navigation/file_bug1300461_back.html
new file mode 100644
index 0000000000..ea014cce34
--- /dev/null
+++ b/docshell/test/navigation/file_bug1300461_back.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <!-- The empty unload handler is to prevent bfcache. -->
+ <body onload="test();" onunload="">
+ <script>
+ let Ci = SpecialPowers.Ci;
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ async function test() {
+ if (opener) {
+ opener.info("file_bug1300461_back.html");
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 1, "check history index");
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ opener.is(shistory.legacySHistory.requestedIndex, -1, "check requestedIndex");
+ } else {
+ let index = await opener.getSHRequestedIndex();
+ opener.is(index, -1, "check requestedIndex");
+ }
+
+ opener.ok(webNav.canGoBack, "check canGoBack");
+ if (opener.testCount == 1) {
+ opener.info("replaceState to redirect.html");
+ window.history.replaceState({}, "", "file_bug1300461_redirect.html");
+ }
+ window.history.back();
+ }
+ }
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <body>
+ Redirect to file_bug1300461_back.html.
+ </body>
+</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..f252df98bf
--- /dev/null
+++ b/docshell/test/navigation/file_bug1326251.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1326251</title>
+ <script>
+ const BASE_URL = "http://mochi.test:8888/tests/docshell/test/navigation/";
+ let testSteps = [
+ async function() {
+ // Test 1: Create dynamic iframe with bfcache enabled.
+ // Navigate static / dynamic iframes, then navigate top level window
+ // and navigate back. Both iframes should still exist with history
+ // entries preserved.
+ window.onunload = null; // enable bfcache
+ await createDynamicFrame(document);
+ await loadUriInFrame(document.getElementById("staticFrame"), "frame1.html");
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame1.html");
+ await loadUriInFrame(document.getElementById("staticFrame"), "frame2.html");
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame2.html");
+ opener.is(history.length, 5, "history.length");
+ window.location = "goback.html";
+ },
+ async function() {
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ opener.is(webNav.canGoForward, true, "canGoForward");
+ opener.is(shistory.index, 4, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+ opener.is(document.getElementById("staticFrame").contentWindow.location.href, BASE_URL + "frame2.html", "staticFrame location");
+ opener.is(document.getElementById("dynamicFrame").contentWindow.location.href, BASE_URL + "frame2.html", "dynamicFrame location");
+
+ // Test 2: Load another page in dynamic iframe, canGoForward should be
+ // false.
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame3.html");
+ opener.is(webNav.canGoForward, false, "canGoForward");
+ opener.is(shistory.index, 5, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+
+ // Test 3: Navigate to antoher page with bfcache disabled, all dynamic
+ // iframe entries should be removed.
+ window.onunload = function() {}; // disable bfcache
+ window.location = "goback.html";
+ },
+ async function() {
+ let windowWrap = SpecialPowers.wrap(window);
+ let docShell = windowWrap.docShell;
+ let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+ // Now staticFrame has frame0 -> frame1 -> frame2.
+ opener.is(docShell.previousEntryIndex, 3, "docShell.previousEntryIndex");
+ opener.is(docShell.loadedEntryIndex, 2, "docShell.loadedEntryIndex");
+ opener.is(shistory.index, 2, "shistory.index");
+ opener.is(history.length, 4, "history.length");
+ opener.is(document.getElementById("staticFrame").contentWindow.location.href, BASE_URL + "frame2.html", "staticFrame location");
+ opener.ok(!document.getElementById("dynamicFrame"), "dynamicFrame should not exist");
+
+ // Test 4: Load a nested frame in the static frame, navigate the inner
+ // static frame, add a inner dynamic frame and navigate the dynamic
+ // frame. Then navigate the outer static frame and go back. The inner
+ // iframe should show the last entry of inner static frame.
+ let staticFrame = document.getElementById("staticFrame");
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ await loadUriInFrame(staticFrame, "iframe_static.html");
+ let innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ await loadUriInFrame(innerStaticFrame, "frame1.html");
+ let innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ opener.is(shistory.index, 5, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+
+ // Wait for 2 load events - navigation and goback.
+ let onloadPromise = awaitOnload(staticFrame, 2);
+ await loadUriInFrame(staticFrame, "goback.html");
+ await onloadPromise;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static -> goback
+ // innerStaticFrame: frame0 -> frame1
+ opener.is(shistory.index, 4, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+ innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ opener.ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+
+ // Test 5: Insert and navigate inner dynamic frame again with bfcache
+ // enabled, and navigate top level window to a special page which will
+ // evict bfcache then goback. Verify that dynamic entries are correctly
+ // removed in this case.
+ window.onunload = null; // enable bfcache
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ opener.is(shistory.index, 5, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+ window.location = "file_bug1326251_evict_cache.html";
+ },
+ async function() {
+ let windowWrap = SpecialPowers.wrap(window);
+ let docShell = windowWrap.docShell;
+ let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ opener.is(docShell.previousEntryIndex, 5, "docShell.previousEntryIndex");
+ opener.is(docShell.loadedEntryIndex, 4, "docShell.loadedEntryIndex");
+ opener.is(shistory.index, 4, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+ let staticFrame = document.getElementById("staticFrame");
+ let innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ opener.ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+
+ // Test 6: Insert and navigate inner dynamic frame and then reload outer
+ // frame. Verify that inner dynamic frame entries are all removed.
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ let innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ opener.is(shistory.index, 5, "shistory.index");
+ opener.is(history.length, 6, "history.length");
+ let staticFrameLoadPromise = new Promise(resolve => {
+ staticFrame.onload = resolve;
+ });
+ staticFrame.contentWindow.location.reload();
+ await staticFrameLoadPromise;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ opener.is(shistory.index, 4, "shistory.index");
+ opener.is(history.length, 5, "history.length");
+ innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ opener.ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+ opener.finishTest();
+ },
+ ];
+
+ function awaitOnload(frame, occurances = 1) {
+ return new Promise(function(resolve, reject) {
+ let count = 0;
+ frame.addEventListener("load", function listener(e) {
+ if (++count == occurances) {
+ frame.removeEventListener("load", listener);
+ setTimeout(resolve, 0);
+ }
+ });
+ });
+ }
+
+ async function createDynamicFrame(targetDocument, frameSrc = "frame0.html") {
+ let dynamicFrame = targetDocument.createElement("iframe");
+ let onloadPromise = awaitOnload(dynamicFrame);
+ dynamicFrame.id = "dynamicFrame";
+ dynamicFrame.src = frameSrc;
+ let container = targetDocument.getElementById("frameContainer");
+ container.appendChild(dynamicFrame);
+ await onloadPromise;
+ return dynamicFrame;
+ }
+
+ async function loadUriInFrame(frame, uri) {
+ let onloadPromise = awaitOnload(frame);
+ frame.src = uri;
+ return onloadPromise;
+ }
+
+ function test() {
+ if (opener) {
+ // Ensure the tests are not executed in onload hander.
+ setTimeout(testSteps[opener.testCount++], 0);
+ }
+ }
+ </script>
+ </head>
+ <body onpageshow="test();">
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
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..0f46a26b87
--- /dev/null
+++ b/docshell/test/navigation/file_bug1326251_evict_cache.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1326251</title>
+ <script>
+ SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+ // Evict bfcache and then go back.
+ async function evictCache() {
+ let shistory = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ shistory.legacySHistory.evictAllContentViewers();
+ } else {
+ opener.evictAllContentViewers();
+ }
+
+ history.back();
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(evictCache, 0);">
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>title</title>
+ </head>
+ <body onload="loadFramesAndNavigate();">
+ <p id="content"></p>
+ <div id="frameContainer">
+ </div>
+ <script type="application/javascript">
+ function waitForLoad(frame) {
+ return new Promise(r => frame.onload = () => setTimeout(r, 0));
+ }
+
+ async function loadFramesAndNavigate() {
+ let dynamicFrame = document.createElement("iframe");
+ dynamicFrame.src = "data:text/html,iframe1";
+ document.querySelector("#frameContainer").appendChild(dynamicFrame);
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe2";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe3";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe4";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe5";
+ await waitForLoad(dynamicFrame);
+ location.href = "file_bug1364364-2.html";
+ }
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>title</title>
+ </head>
+ <body onload="notifyOpener();">
+ <script type="application/javascript">
+ function notifyOpener() {
+ opener.postMessage("navigation-done", "*");
+ }
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>iframe test page 1</title>
+ </head>
+ <body>iframe test page 1</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>iframe test page 2</title>
+ </head>
+ <body>iframe test page 2</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Test for bug 1375833</title>
+ </head>
+ <body onload="test();">
+ <iframe id="testFrame" src="file_bug1375833-frame1.html"></iframe>
+ <script type="application/javascript">
+ function test() {
+ let iframe = document.querySelector("#testFrame");
+ setTimeout(function() { iframe.src = "file_bug1375833-frame1.html"; }, 0);
+ iframe.onload = function(e) {
+ setTimeout(function() { iframe.src = "file_bug1375833-frame2.html"; }, 0);
+ iframe.onload = function() {
+ opener.postMessage(iframe.contentWindow.location.href, "*");
+ };
+ };
+ }
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1379762</title>
+ </head>
+ <img srcset> <!-- This tries to add load blockers during bfcache restoration -->
+ <script>
+ onunload = null; // enable bfcache
+ var bc = new BroadcastChannel('bug1379762');
+ bc.postMessage("init");
+ onpageshow = function() {
+ bc.onmessage = (messageEvent) => {
+ let message = messageEvent.data;
+ if (message == "forward_back") {
+ // Navigate forward and then back.
+ // eslint-disable-next-line no-global-assign
+ setTimeout(function() { location = "goback.html"; }, 0);
+ } else if (message == "finish_test") {
+ // Do this async so our load event gets a chance to fire if it plans to
+ // do it.
+ setTimeout(function() {
+ bc.postMessage("finished");
+ bc.close();
+ window.close();
+ });
+ }
+ }
+ bc.postMessage("increment_testCount");
+ };
+ onload = function() {
+ bc.postMessage("increment_loadCount");
+ };
+ </script>
+</html>
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 @@
+<html>
+ <body onload="opener.bodyOnLoad()">
+ Nested Frame
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+
+ var loadCount = 0;
+ function loadListener(event) {
+ ++loadCount;
+ if (loadCount == 2) {
+ // Use a timer to ensure we don't get extra load events.
+ setTimeout(function() {
+ var doc1URI = document.getElementById("i1").contentDocument.documentURI;
+ opener.ok(doc1URI.includes("frame1.html"),
+ "Should have loaded the initial page to the first iframe. Got " + doc1URI);
+ var doc2URI = document.getElementById("i2").contentDocument.documentURI;
+ opener.ok(doc2URI.includes("frame1.html"),
+ "Should have loaded the initial page to the second iframe. Got " + doc2URI);
+ opener.finishTest();
+ }, 1000);
+ } else if (loadCount > 2) {
+ opener.ok(false, "Too many load events");
+ }
+ // if we don't get enough load events, the test will time out.
+ }
+
+ function setupIframe(id) {
+ var ifr = document.getElementById(id);
+ return new Promise(function(resolve) {
+ ifr.onload = function() {
+ // Replace load listener to catch page loads from the session history.
+ ifr.onload = loadListener;
+ // Need to use setTimeout, because triggering loads inside
+ // load event listener has special behavior since at the moment
+ // the docshell keeps track of whether it is executing a load handler or not.
+ setTimeout(resolve);
+ }
+ ifr.contentWindow.location.href = "frame2.html";
+ });
+ }
+
+ async function test() {
+ await setupIframe("i1");
+ await setupIframe("i2");
+ history.go(-2);
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test)">
+ <iframe id="i1" src="frame1.html"></iframe>
+ <iframe id="i2" src="frame1.html"></iframe>
+ </body>
+</html> \ No newline at end of file
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 @@
+<html><head><meta charset="utf-8"><script>window.addEventListener("pageshow", function(event) { window.opener.postMessage({persisted: event.persisted}, "*"); });</script></head><body contentEditable="true"><p>contentEditable</p></body></html> \ 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 @@
+<html><head><meta charset="utf-8"><script>window.addEventListener("pageshow", function(event) { window.opener.postMessage({persisted: event.persisted}, "*"); });</script></head><body><p>designModeDocument</p></body></html> \ 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 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body>
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ <script>
+ runTest();
+ </script>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="runTest();">
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(runTest, 0);">
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function dynFrameLoad() {
+ var ifrs = document.getElementsByTagName("iframe");
+ opener.ok(String(ifrs[0].contentWindow.location).includes(ifrs[0].src),
+ "Wrong document loaded (1)\n");
+ opener.ok(String(ifrs[1].contentWindow.location).includes(ifrs[1].src),
+ "Wrong document loaded (2)\n");
+ if (opener && ++opener.testCount == 1) {
+ window.location = "goback.html";
+ } else {
+ opener.finishTest();
+ }
+ }
+
+ window.addEventListener("load",
+ function() {
+ var container = document.getElementById("t1");
+ container.addEventListener("load", dynFrameLoad, true);
+ container.appendChild(container.appendChild(document.getElementById("i1")));
+ });
+ </script>
+ </head>
+ <body>
+ <h5>Container:</h5>
+ <div id="t1"></div>
+ <h5>Original frames:</h5>
+ <iframe id="i1" src="frame0.html"></iframe>
+ <iframe src="frame1.html"></iframe>
+ </body>
+</html>
+
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 @@
+<html>
+ <head>
+ <script>
+
+ function testDone() {
+ document.body.firstChild.remove();
+ var isOK = false;
+ try {
+ isOK = history.previous != location;
+ } catch (ex) {
+ // history.previous should throw if this is the first page in shistory.
+ isOK = true;
+ }
+ document.body.textContent = isOK ? "PASSED" : "FAILED";
+ opener.ok(isOK, "Duplicate session history entries should have been removed!");
+ opener.finishTest();
+ }
+ function ifrload() {
+ setTimeout(testDone, 0);
+ }
+ function test() {
+ var ifr = document.getElementsByTagName("iframe")[0];
+ ifr.onload = ifrload;
+ ifr.src = "data:text/html,doc2";
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0)"><iframe src="data:text/html,doc1"></iframe>
+ </body>
+</html>
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 @@
+<html>
+<body>
+This window should never be openend!
+</body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function loaded() {
+ if (location.search == "") {
+ if (opener.loadedInitialPage) {
+ opener.ok(true, "got back to the initial page.");
+ opener.setTimeout("SimpleTest.finish();");
+ window.close();
+ return;
+ }
+ opener.loadedInitialPage = true;
+ opener.info("Loaded initial page.");
+ // Load another page (which is this same document, but different URL.)
+ location.href = location.href + "?anotherPage";
+ } else {
+ opener.info("Loaded the second page.");
+ location.hash = "1";
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ location.hash = "2";
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ var docShell = SpecialPowers.wrap(window).docShell;
+ var webNavigation =
+ SpecialPowers.do_QueryInterface(docShell, "nsIWebNavigation");
+ webNavigation.gotoIndex(history.length - 2);
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ webNavigation.gotoIndex(history.length - 4);
+ }
+ }
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onpageshow="setTimeout(loaded)">
+ <a href="#1" name="1">1</a>
+ <a href="#2" name="2">2</a>
+ </body>
+</html> \ 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 @@
+<html>
+ <head>
+ <script>
+ function start() {
+ var length = history.length;
+ document.open();
+ document.write("<h5 id='dynamic'>document.written content</h5>");
+ document.close();
+ opener.is(history.length, length,
+ "document.open/close should not change history");
+ opener.finishTest();
+ }
+ </script>
+ </head>
+ <body onload="start();">
+ <h5>static content</h5>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function checkHaveLoadedNewDoc() {
+ let l = document.body.firstChild.contentWindow.location.href;
+ if (!l.endsWith("file_fragment_handling_during_load_frame2.sjs")) {
+ opener.ok(true, "Fine. We will check later.");
+ setTimeout(checkHaveLoadedNewDoc, 500);
+ return;
+ }
+ opener.ok(true, "Have loaded a new document.");
+ opener.finishTest();
+ }
+ function test() {
+ // Test that executing back() before load has started doesn't interrupt
+ // the load.
+ var ifr = document.getElementsByTagName("iframe")[0];
+ ifr.onload = checkHaveLoadedNewDoc;
+ ifr.contentWindow.location.hash = "b";
+ ifr.contentWindow.location.href = "file_fragment_handling_during_load_frame2.sjs";
+ history.back();
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0)"><iframe src="file_fragment_handling_during_load_frame1.html#a"></iframe>
+ </body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+foo
+</body>
+</html>
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..e00d896947
--- /dev/null
+++ b/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs
@@ -0,0 +1,19 @@
+/* -*- 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();
+ while (Date.now() - s < 1000);
+
+ response.write(`<!DOCTYPE HTML>
+ <html>
+ <body>
+ bar
+ </body>
+ </html>
+ `);
+} \ No newline at end of file
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 @@
+<!DOCTYPE html>
+<html>
+<body onpageshow="opener.bodyOnLoad()">
+<a id="link1" href="#1">Link 1</a>
+<a name="1">link 1</a>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onpageshow="opener.bodyOnLoad()">
+<a id="link1" href="#1">Link 1</a>
+<a id="link2" href="#2">Link 2</a>
+<a name="1">link 1</a>
+<a name="2">link 2</a>
+</body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function nestedIframeLoaded() {
+ var tf = document.getElementById("testframe");
+ var innerf = tf.contentDocument.getElementsByTagName("iframe")[0];
+ if (!innerf.contentDocument.documentURI.includes("frame0")) {
+ innerf.contentWindow.location.href = "http://mochi.test:8888/tests/docshell/test/navigation/frame0.html";
+ return;
+ }
+ innerf.onload = null;
+ innerf.src = "about:blank";
+ var d = innerf.contentDocument;
+ d.open();
+ d.write("test");
+ d.close();
+ opener.is(window.history.length, 1, "Unexpected history length");
+ opener.finishTest();
+ }
+ </script>
+ </head>
+ <body>
+ <iframe id="testframe" src="file_nested_frames_innerframe.html" onload="frameLoaded()"></iframe>
+ <script>
+ </script>
+ </body>
+</html>
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 @@
+<iframe onload='parent.nestedIframeLoaded();'></iframe>
diff --git a/docshell/test/navigation/file_scrollRestoration.html b/docshell/test/navigation/file_scrollRestoration.html
new file mode 100644
index 0000000000..90044894e4
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration.html
@@ -0,0 +1,153 @@
+<html>
+ <head>
+ <script>
+ var oldHistoryObject = null;
+
+ function test(event) {
+ if (!opener.scrollRestorationTest) {
+ opener.scrollRestorationTest = 0;
+ }
+ ++opener.scrollRestorationTest;
+
+ switch (opener.scrollRestorationTest) {
+ case 1: {
+ opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
+ opener.ok(history.scrollRestoration, "History object has scrollRestoration property.");
+ opener.is(history.scrollRestoration, "auto", "history.scrollRestoration's default value should be 'auto'.");
+ history.scrollRestoration = "foobar";
+ opener.is(history.scrollRestoration, "auto", "Invalid enum value should not change the value of an attribute.");
+ history.scrollRestoration = "manual";
+ opener.is(history.scrollRestoration, "manual", "Valid enum value should change the value of an attribute.");
+ history.scrollRestoration = "auto";
+ opener.is(history.scrollRestoration, "auto", "Valid enum value should change the value of an attribute.");
+ document.getElementById("bottom").scrollIntoView();
+ window.location.reload(false);
+ break;
+ }
+ case 2: {
+ opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
+ opener.isnot(Math.round(window.scrollY), 0, "Should have restored scrolling.");
+ opener.is(history.scrollRestoration, "auto", "Should have the same scrollRestoration as before reload.");
+ history.scrollRestoration = "manual";
+ window.onunload = function() {}; // Disable bfcache.
+ window.location.reload(false);
+ break;
+ }
+ case 3: {
+ opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
+ opener.is(window.scrollY, 0, "Should not have restored scrolling.");
+ opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload.");
+ document.getElementById("bottom").scrollIntoView();
+ window.onunload = null; // Should get bfcache behavior.
+ window.opener.case3WaitForBlank();
+ window.location.href = "about:blank";
+ break;
+ }
+ case 4: {
+ opener.is(event.persisted, true, "Should have persisted session history entry.");
+ opener.isnot(Math.round(window.scrollY), 0, "Should have kept the old scroll position.");
+ opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload.");
+ window.scrollTo(0, 0);
+ window.location.hash = "hash";
+ requestAnimationFrame(test);
+ break;
+ }
+ case 5: {
+ opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled to #hash.");
+ opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation.");
+ window.onunload = function() {
+ opener.setTimeout("is(SpecialPowers.wrap(testWindow).history.scrollRestoration, 'auto'); SpecialPowers.wrap(testWindow).history.back();", 250);
+ }; // Disable bfcache.
+ window.location.href = "about:blank";
+ break;
+ }
+ case 6: {
+ opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
+ opener.is(window.scrollY, 0, "Shouldn't have kept the old scroll position.");
+ opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation.");
+ history.scrollRestoration = "auto";
+ document.getElementById("bottom").scrollIntoView();
+ history.pushState({ state: "state1" }, "state1");
+ history.pushState({ state: "state2" }, "state2");
+ window.scrollTo(0, 0);
+ history.back();
+ setTimeout(test);
+ break;
+ }
+ case 7: {
+ opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state1's position");
+ opener.is(history.state.state, "state1", "Unexpected state.");
+
+ history.scrollRestoration = "manual";
+ document.getElementById("bottom").scrollIntoView();
+ history.pushState({ state: "state3" }, "state3");
+ history.pushState({ state: "state4" }, "state4");
+ window.scrollTo(0, 0);
+ history.back();
+ setTimeout(test);
+ break;
+ }
+ case 8: {
+ opener.is(Math.round(window.scrollY), 0, "Shouldn't have scrolled back to the state3's position");
+ opener.is(history.state.state, "state3", "Unexpected state.");
+
+ history.pushState({ state: "state5" }, "state5");
+ history.scrollRestoration = "auto";
+ document.getElementById("bottom").scrollIntoView();
+ opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled to 'bottom'.");
+ history.back();
+ setTimeout(test);
+ break;
+ }
+ case 9: {
+ window.scrollTo(0, 0);
+ history.forward();
+ setTimeout(test);
+ break;
+ }
+ case 10: {
+ opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state5's position");
+
+ var ifr = document.createElement("iframe");
+ ifr.src = "data:text/html,";
+ document.body.appendChild(ifr);
+ ifr.onload = test;
+ break;
+ }
+ case 11: {
+ oldHistoryObject = SpecialPowers.wrap(event.target).contentWindow.history;
+ event.target.src = "about:blank";
+ break;
+ }
+ case 12: {
+ try {
+ oldHistoryObject.scrollRestoration;
+ opener.ok(false, "Should have thrown an exception.");
+ } catch (ex) {
+ opener.isnot(ex, null, "Did get an exception");
+ }
+ try {
+ oldHistoryObject.scrollRestoration = "auto";
+ opener.ok(false, "Should have thrown an exception.");
+ } catch (ex) {
+ opener.isnot(ex, null, "Did get an exception");
+ }
+ opener.finishTest();
+ break;
+ }
+ }
+ }
+
+ window.addEventListener("pageshow",
+ function(e) {
+ setTimeout(test, 0, e);
+ });
+ </script>
+ </head>
+ <body>
+ <div style="border: 1px solid black; height: 5000px;">
+ &nbsp;</div>
+ <div id="bottom">Hello world</div>
+ <a href="#hash" name="hash">hash</a>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function test() {
+ try {
+ frames[0].history.pushState({}, "state", "?pushed");
+ } catch (ex) {
+ opener.ok(false, "history.pushState shouldn't throw");
+ }
+
+ if (!opener.shiftReloadPushStateFirstRound) {
+ opener.shiftReloadPushStateFirstRound = true;
+ window.location.reload(true);
+ } else {
+ opener.ok(true, "Did run history.push");
+ opener.finishTest();
+ }
+ }
+
+ window.addEventListener("load", function() { setTimeout(test, 0); });
+ </script>
+ </head>
+ <body>
+ <iframe src="frame0.html"></iframe>
+ <script>
+ </script>
+ </body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ function test() {
+ var ifr = document.createElement("iframe");
+ ifr.src = "frame0.html";
+ document.getElementById("dynamic").appendChild(ifr);
+ var staticFrame = document.getElementById("staticframe");
+ staticFrame.onload = window.location = "goback.html";
+ staticFrame.contentWindow.location = "frame1.html";
+ }
+
+ function start() {
+ if (++opener.testCount == 1) {
+ test();
+ } else {
+ var staticFrame = document.getElementById("staticframe");
+ opener.ok(String(staticFrame.contentWindow.location).includes(staticFrame.src),
+ "Wrong document loaded!");
+ opener.finishTest();
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout('start()', 0)">
+ <h5>Dynamic</h5>
+ <div id="dynamic"></div>
+ <h5>Static</h5>
+ <div id="static"><iframe id="staticframe" src="frame0.html"></iframe></div>
+ </body>
+</html>
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 @@
+<html>
+ <body onload="bodyLoaded()">Frame 1</body>
+ <script>
+ function bodyLoaded() {
+ opener.postMessage("body-loaded", "*");
+ }
+ </script>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<b>Frame 1</b><br/>
+<a href="#"" id="testlink" onclick="parent.frames[1].frames[0].location='http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html'">click me</a>
+
+<script type="application/javascript">
+ // make sure to set document.domain to the same domain as the subframe
+ window.onload = function() {
+ document.domain = "mochi.test";
+ };
+ window.addEventListener("message", receiveMessage);
+ function receiveMessage(event) {
+ // make sure to get the right start command, otherwise
+ // let the parent know and fail the test
+ if (event.data.start !== "startTest") {
+ window.removeEventListener("message", receiveMessage);
+ window.parent.postMessage({triggeringPrincipalURI: "false"}, "*");
+ }
+ // click the link to navigate the subframe
+ document.getElementById("testlink").click();
+ }
+</script>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<b>Frame 2</b><br/>
+<iframe src="http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe.html"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame A
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame A navigated by Frame B
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame B navigating Frame A
+
+<script type="text/javascript">
+
+window.open("file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html", "framea");
+
+</script>
+
+</body>
+</html>
+
+
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+base test frame
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+navigated by window.open()
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset='utf-8'></head>
+<body>
+<b>Sub Frame 2</b><br/>
+<script type='application/javascript'>
+ // make sure to set document.domain to same domain as frame 1
+ window.onload = function() {
+ document.domain = "mochi.test";
+ // let Frame 1 know that we are ready to run the test
+ window.parent.parent.frames[0].postMessage({start: "startTest"}, "*");
+ };
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body onload="checkResults()">
+<b>Sub Frame 2 Navigated</b><br/>
+
+<script type='application/javascript'>
+ function checkResults() {
+ // query the uri of the loadingPrincipal and the TriggeringPrincipal and pass
+ // that information on to the parent for verification.
+ var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+ var triggeringPrincipalURI = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipalURI = channel.loadInfo.loadingPrincipal.asciiSpec;
+ var referrerURI = document.referrer;
+ window.parent.parent.postMessage({triggeringPrincipalURI,
+ loadingPrincipalURI,
+ referrerURI}, "*");
+ }
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body onload="checkResults()">
+<b>SubFrame Same-Origin Navigated</b><br/>
+
+<script type='application/javascript'>
+ function checkResults() {
+ // query the uri of the loadingPrincipal and the TriggeringPrincipal and pass
+ // that information on to the parent for verification.
+ var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+ var triggeringPrincipalURI = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipalURI = channel.loadInfo.loadingPrincipal.asciiSpec;
+
+ window.parent.postMessage({triggeringPrincipalURI,
+ loadingPrincipalURI}, "*");
+ }
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+http
+</body>
+</html>
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 @@
+<html>
+ <body>Frame 0</body>
+</html>
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 @@
+<html>
+ <body>Frame 1</body>
+</html>
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 @@
+<html>
+ <body>Frame 2</body>
+</html>
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 @@
+<html>
+ <body>Frame 3</body>
+</html>
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 @@
+<html>
+ <body onload="setTimeout('window.history.go(-1)', 1000);">
+ window.history.go(-1);
+ </body>
+</html>
diff --git a/docshell/test/navigation/iframe.html b/docshell/test/navigation/iframe.html
new file mode 100644
index 0000000000..4685fea7b7
--- /dev/null
+++ b/docshell/test/navigation/iframe.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+<script>
+var src = window.location.hash.substring(1);
+document.write('<iframe src="' + src + '"></iframe>');
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/iframe_static.html b/docshell/test/navigation/iframe_static.html
new file mode 100644
index 0000000000..1bdd1437c1
--- /dev/null
+++ b/docshell/test/navigation/iframe_static.html
@@ -0,0 +1,8 @@
+<html>
+ <body>
+ Nested Frame
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/mochitest.ini b/docshell/test/navigation/mochitest.ini
new file mode 100644
index 0000000000..9e5be6a625
--- /dev/null
+++ b/docshell/test/navigation/mochitest.ini
@@ -0,0 +1,122 @@
+[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_scrollRestoration.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
+
+[test_aboutblank_change_process.html]
+[test_bug13871.html]
+[test_bug270414.html]
+[test_bug278916.html]
+[test_bug279495.html]
+[test_bug344861.html]
+skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
+[test_bug386782.html]
+[test_bug430624.html]
+[test_bug430723.html]
+skip-if = (!debug && (os == 'mac' || os == 'win')) # Bug 874423
+[test_bug1364364.html]
+skip-if = (os == "android") || fission # Bug 1560378, bug 1666449
+[test_bug1375833.html]
+[test_bug1536471.html]
+support-files = file_bug1536471.html
+[test_child.html]
+[test_docshell_gotoindex.html]
+support-files = file_docshell_gotoindex.html
+[test_grandchild.html]
+[test_load_history_entry.html]
+[test_not-opener.html]
+[test_opener.html]
+[test_popup-navigates-children.html]
+[test_reserved.html]
+skip-if =
+ (debug && e10s) # bug 1263213
+ (os == 'mac' && os_version == '10.14') # Bug 1548821
+[test_performance_navigation.html]
+skip-if = fission # bug 1666602
+[test_sessionhistory.html]
+skip-if = verify && (os == 'mac') && debug && webrender # 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_session_history_entry_cleanup.html]
+[test_fragment_handling_during_load.html]
+[test_nested_frames.html]
+[test_shiftReload_and_pushState.html]
+[test_scrollRestoration.html]
+skip-if = sessionHistoryInParent # It relies on the bfcache
+[test_bug1609475.html]
+[test_bug1300461.html]
+fail-if = sessionHistoryInParent
+[test_bug1326251.html]
+skip-if = toolkit == 'android' || sessionHistoryInParent # It relies on the bfcache
+[test_bug1379762.html]
+skip-if = sessionHistoryInParent # It relies on the bfcache
+[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]
+[test_triggeringprincipal_frame_nav.html]
+[test_triggeringprincipal_frame_same_origin_nav.html]
+[test_triggeringprincipal_window_open.html]
+[test_triggeringprincipal_parent_iframe_window_open.html]
+[test_triggeringprincipal_iframe_iframe_window_open.html]
+[test_contentpolicy_block_window.html]
+[test_rate_limit_location_change.html]
diff --git a/docshell/test/navigation/navigate.html b/docshell/test/navigation/navigate.html
new file mode 100644
index 0000000000..fe191f1d93
--- /dev/null
+++ b/docshell/test/navigation/navigate.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="NavigationUtils.js"></script>
+ <script>
+ async function navigate() {
+ var args = window.location.hash.substring(1).split(",");
+ var target = args[0];
+ var mechanism = args[1];
+
+ switch (mechanism) {
+ case "location":
+ // eslint-disable-next-line no-eval
+ navigateByLocation(eval(target));
+ break;
+ case "open":
+ navigateByOpen(target);
+ break;
+ case "form":
+ navigateByForm(target);
+ break;
+ case "hyperlink":
+ await navigateByHyperlink(target);
+ break;
+ }
+ }
+ </script>
+</head>
+<body onload="navigate();">
+<script>
+var args = window.location.hash.substring(1).split(",");
+var target = args[0];
+var mechanism = args[1];
+document.write("target=" + target + " mechanism=" + mechanism);
+</script>
+</body>
+</html>
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 @@
+<html><body>This is a popup</body></html>
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 @@
+<html><body>This frame was navigated.</body></html>
diff --git a/docshell/test/navigation/open.html b/docshell/test/navigation/open.html
new file mode 100644
index 0000000000..97eb9b76e1
--- /dev/null
+++ b/docshell/test/navigation/open.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<script>
+var target = window.location.hash.substring(1);
+document.write("target=" + target);
+window.open("navigation_target_popup_url.html", target, "width=10,height=10");
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+This document contains a frame.
+<div><iframe src="blank.html"></iframe></div>
+<script>
+frames[0].name = window.name + "_child0";
+window.onload = function() {
+ opener.postMessage("ready", "*");
+};
+</script>
+</body>
+</html>
+
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 @@
+<html><head>
+<script> window.addEventListener("pageshow", function() { opener.nextTest(); }); </script>
+</head><body>
+<div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:red">
+<p>This is a very tall red box.</p>
+</div></body></html>
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..bde79bd21c
--- /dev/null
+++ b/docshell/test/navigation/test_aboutblank_change_process.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+<script>
+// Open a window and navigate it from http://example.net to about:blank
+// With fission, we should switch processes and about:blank should load in
+// the same process as this test page.
+// This is a crash test.
+add_task(async function test_aboutblank_change_process() {
+ let exampleLoaded = new Promise(resolve => {
+ function onMessage(event) {
+ if (event.data == "body-loaded") {
+ window.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }
+ window.addEventListener("message", onMessage);
+ });
+ let win = window.open();
+ win.location = "http://example.net/tests/docshell/test/navigation/file_tell_opener.html";
+ await exampleLoaded;
+
+ win.location = "about:blank";
+
+ // A crash happens somewhere here when about:blank does not go via
+ // DocumentChannel with fission enabled
+
+ // Wait for about:blank to load in this process
+ await SimpleTest.promiseWaitForCondition(() => {
+ try {
+ return win.location.href == "about:blank";
+ } catch (e) {
+ // While the `win` still has example.net page loaded, `win.location` will
+ // be a cross origin object and querying win.location.href will throw a
+ // SecurityError. Return false as long as this is the case.
+ return false;
+ }
+ })
+
+ ok(true, "We did not crash");
+ win.close();
+});
+</script>
diff --git a/docshell/test/navigation/test_bug1300461.html b/docshell/test/navigation/test_bug1300461.html
new file mode 100644
index 0000000000..b0b7d70c9e
--- /dev/null
+++ b/docshell/test/navigation/test_bug1300461.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1300461</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1300461">Mozilla Bug 1300461</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ let chromeScript = null;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ function doSend(message, fn) {
+ try {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: true, value: fn()});
+ } catch(_) {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: false});
+ }
+ }
+
+ // eslint-disable-next-line no-undef
+ addMessageListener("requestedIndex", () => {
+ doSend("requestedIndex", () => {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let shistory = win.gBrowser.selectedBrowser.browsingContext.sessionHistory;
+ return shistory.requestedIndex;
+ })
+ });
+ });
+ }
+
+ async function getSHRequestedIndex() {
+ let p = chromeScript.promiseOneMessage("requestedIndex");
+ chromeScript.sendAsyncMessage("requestedIndex");
+ let result = await p;
+ ok(result.success, "Got requested index from parent");
+ return result.value;
+ }
+
+ var testCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug1300461.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ if (chromeScript) {
+ chromeScript.destroy();
+ }
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1326251.html b/docshell/test/navigation/test_bug1326251.html
new file mode 100644
index 0000000000..a04431a2c4
--- /dev/null
+++ b/docshell/test/navigation/test_bug1326251.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1326251</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1326251">Mozilla Bug 1326251</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ let chromeScript = null;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ function doSend(message, fn) {
+ try {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: true, value: fn()});
+ } catch(_) {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: false});
+ }
+ }
+
+ // eslint-disable-next-line no-undef
+ addMessageListener("evictAllContentViewers", _ => {
+ doSend("evictAllContentViewers", () => {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let shistory = win.gBrowser.selectedBrowser.browsingContext.sessionHistory;
+ return shistory.evictAllContentViewers();
+ })
+ });
+ });
+ }
+
+ async function evictAllContentViewers() {
+ let p = chromeScript.promiseOneMessage("evictAllContentViewers");
+ chromeScript.sendAsyncMessage("evictAllContentViewers");
+ let result = await p;
+ ok(result.success, "Called evictAllContentViewers from parent");
+ }
+
+ var testCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug1326251.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ if (chromeScript) {
+ chromeScript.destroy();
+ }
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1364364.html b/docshell/test/navigation/test_bug1364364.html
new file mode 100644
index 0000000000..b237e5339f
--- /dev/null
+++ b/docshell/test/navigation/test_bug1364364.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1364364
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1364364</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1364364 **/
+ let testWin, testDoc;
+ async function test() {
+ SimpleTest.waitForExplicitFinish();
+ testWin = window.open("file_bug1364364-1.html");
+ await waitForLoad(testWin);
+ testDoc = testWin.document;
+
+ // file_bug1364364-1.html will load a few dynamic iframes and then navigate
+ // top browsing context to file_bug1364364-2.html, which will postMessage
+ // back.
+ }
+
+ function waitForLoad(win) {
+ return new Promise(r => win.addEventListener("load", r, { once: true}));
+ }
+
+ window.addEventListener("message", async function(msg) {
+ if (msg.data == "navigation-done") {
+ is(testWin.history.length, 6, "check history.length");
+
+ // Modify a document in bfcache should cause the cache being dropped tho
+ // RemoveFromBFCacheAsync.
+ testDoc.querySelector("#content").textContent = "modified";
+ await new Promise(r => setTimeout(r, 0));
+
+ is(testWin.history.length, 2, "check history.length after bfcache dropped");
+ testWin.close();
+ SimpleTest.finish();
+ }
+ });
+
+ </script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1364364">Mozilla Bug 1364364</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1375833.html b/docshell/test/navigation/test_bug1375833.html
new file mode 100644
index 0000000000..49b6646ee3
--- /dev/null
+++ b/docshell/test/navigation/test_bug1375833.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375833
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1375833</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ /**
+ * Test for Bug 1375833. It tests for 2 things in a normal reload -
+ * 1. Static frame history should not be dropped.
+ * 2. In a reload, docshell would parse the reloaded root document and
+ * genearate new child docshells, and then use the child offset
+ */
+
+ let testWin = window.open("file_bug1375833.html");
+ let count = 0;
+ let webNav, shistory;
+ let frameDocShellId;
+ let chromeScript = null;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ function doSend(message, fn) {
+ try {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: true, value: fn()});
+ } catch(_) {
+ // eslint-disable-next-line no-undef
+ sendAsyncMessage(message, {success: false});
+ }
+ }
+
+ // eslint-disable-next-line no-undef
+ addMessageListener("test1", index => {
+ doSend("test1", () => {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let sessionHistory = win.gBrowser.selectedBrowser.browsingContext.sessionHistory;
+ let entry = sessionHistory.getEntryAtIndex(sessionHistory.index);
+ let frameEntry = entry.GetChildAt(0);
+ return String(frameEntry.docshellID);
+ })
+ });
+ });
+ }
+
+ window.addEventListener("message", async e => {
+ switch (count++) {
+ case 0:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+
+ webNav = SpecialPowers.wrap(testWin)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation);
+ shistory = webNav.sessionHistory;
+ is(shistory.count, 2, "check history length");
+ is(shistory.index, 1, "check history index");
+
+ frameDocShellId = String(getFrameDocShell().historyID);
+ ok(frameDocShellId, "sanity check for docshell ID");
+
+ testWin.location.reload();
+ break;
+ case 1:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 3, "check history index");
+
+ let newFrameDocShellId = String(getFrameDocShell().historyID);
+ ok(newFrameDocShellId, "sanity check for docshell ID");
+ is(newFrameDocShellId, frameDocShellId, "check docshell ID remains after reload");
+
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let entry = shistory.legacySHistory.getEntryAtIndex(shistory.index);
+ let frameEntry = entry.GetChildAt(0);
+ is(String(frameEntry.docshellID), frameDocShellId, "check newly added shentry uses the same docshell ID");
+ } else {
+ let p = chromeScript.promiseOneMessage("test1");
+ chromeScript.sendAsyncMessage("test1", shistory.index);
+ let result = await p;
+ ok(result.success, "legacySHistory worked around ok");
+ is(result.value, frameDocShellId, "check newly added shentry uses the same docshell ID");
+ }
+
+ webNav.goBack();
+ break;
+ case 2:
+ ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 2, "check history index");
+
+ webNav.goBack();
+ break;
+ case 3:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 1, "check history index");
+
+ webNav.goBack();
+ break;
+ case 4:
+ ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 0, "check history index");
+
+ if (chromeScript) {
+ chromeScript.destroy();
+ }
+ testWin.close();
+ SimpleTest.finish();
+ }
+ });
+
+ function getFrameDocShell() {
+ return SpecialPowers.wrap(testWin.window[0]).docShell;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375833">Mozilla Bug 1375833</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1379762.html b/docshell/test/navigation/test_bug1379762.html
new file mode 100644
index 0000000000..03afb20cad
--- /dev/null
+++ b/docshell/test/navigation/test_bug1379762.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1379762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1379762">Mozilla Bug 1379762</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ /**
+ * - This page opens new window
+ * - new window sends 'init' msg
+ * - onload() in new window sends 'increment_loadCount' msg
+ * - onpageshow() in new window sends 'increment_testCount' msg
+ * - This page sends 'forward_back' msg
+ * - onpageshow() in new window 'increment_testCount'
+ * - This page sends 'finish_test' msg
+ * - onpageshow() in new window sends 'finished' msg
+ */
+ var testCount = 0; // Used by the test files.
+ var loadCount = 0;
+ var goneBack = false;
+ var bc = new BroadcastChannel("bug1379762");
+ bc.onmessage = (messageEvent) => {
+ let message = messageEvent.data;
+ if (message == "init") {
+ is(testCount, 0, "new window should only be loaded once; otherwise the loadCount variable makes no sense");
+ } else if (message == "increment_loadCount") {
+ loadCount++;
+ is(loadCount, 1, "Should only get one load")
+ } else if (message == 'increment_testCount') {
+ testCount++;
+ if (testCount == 1) {
+ bc.postMessage("forward_back");
+ goneBack = true;
+ } else if (testCount == 2) {
+ ok(goneBack, "We had a chance to navigate backwards and forwards in the new window to test BFCache");
+ bc.postMessage("finish_test");
+ }
+ } else if (message == "finished") {
+ bc.close();
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ window.open("file_bug1379762-1.html", "", "width=360,height=480,noopener");
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug13871.html b/docshell/test/navigation/test_bug13871.html
new file mode 100644
index 0000000000..c3f54776aa
--- /dev/null
+++ b/docshell/test/navigation/test_bug13871.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+async function runTest() {
+ navigateByLocation(window0.frames[0]);
+ navigateByOpen("window1_child0");
+ navigateByForm("window2_child0");
+ await navigateByHyperlink("window3_child0");
+
+ await waitForFinishedFrames(4);
+
+ isInaccessible(window0.frames[0], "Should not be able to navigate off-domain frame by setting location.");
+ isInaccessible(window1.frames[0], "Should not be able to navigate off-domain frame by calling window.open.");
+ isInaccessible(window2.frames[0], "Should not be able to navigate off-domain frame by submitting form.");
+ isInaccessible(window3.frames[0], "Should not be able to navigate off-domain frame by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+}
+
+// Because our open()'d windows are cross-origin, we can't wait for onload.
+// We instead wait for a postMessage from parent.html.
+var windows = new Map();
+addEventListener("message", function windowLoaded(evt) {
+ // Because window.open spins the event loop in order to open new windows,
+ // we might receive the "ready" message before we call waitForLoad.
+ // In that case, windows won't contain evt.source and we just note that the
+ // window is ready. Otherwise, windows contains the "resolve" function for
+ // that window's promise and we just have to call it.
+ if (windows.has(evt.source)) {
+ windows.get(evt.source)();
+ } else {
+ windows.set(evt.source, true);
+ }
+});
+
+var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window0", "width=10,height=10");
+var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window1", "width=10,height=10");
+var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window2", "width=10,height=10");
+var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window3", "width=10,height=10");
+
+function waitForLoad(w) {
+ return new Promise(function(resolve, reject) {
+ // If we already got the "ready" message, resolve immediately.
+ if (windows.has(w)) {
+ resolve();
+ } else {
+ windows.set(w, resolve);
+ }
+ });
+}
+
+Promise.all([ waitForLoad(window0),
+ waitForLoad(window1),
+ waitForLoad(window2),
+ waitForLoad(window3) ])
+ .then(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=13871">Mozilla Bug 13871</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<html>
+ <head>
+ <script>
+ let pass = false;
+ let initialLoad = false;
+ var bc = new BroadcastChannel("bug145971");
+ function checkNavigationTypeEquals2() {
+ if (performance.navigation.type == 2) {
+ pass = true;
+ }
+ testDone();
+ }
+
+ function testDone() {
+ bc.postMessage({result: pass});
+ bc.close();
+ window.close();
+ }
+
+ function test() {
+ window.onpageshow = checkNavigationTypeEquals2;
+ window.location.href = 'goback.html';
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0);">
+ Testing bug 145971.
+ </body>
+</html>
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 @@
+
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1536471
+ -->
+<head>
+ <title>Test for Bug 1536471</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript">
+
+ let testWin;
+ async function test() {
+ // Open a new tab and load a document with an iframe inside
+ testWin = window.open("file_bug1536471.html");
+ await waitForLoad();
+ var iframe = testWin.document.getElementById("staticFrame");
+ is(testWin.history.length, 1, "Checking the number of session history entries when there is only one iframe");
+
+ // Navigate the iframe to different pages
+ await loadUriInFrame(iframe, "frame1.html");
+ is(testWin.history.length, 2, "Checking the number of session history entries after having navigated a single iframe 1 time");
+ await loadUriInFrame(iframe, "frame2.html");
+ is(testWin.history.length, 3, "Checking the number of session history entries after having navigated a single iframe 2 times");
+ await loadUriInFrame(iframe, "frame3.html");
+ is(testWin.history.length, 4, "Checking the number of session history entries after having navigated a single iframe 3 times");
+
+ // Reload the top document
+ testWin.location.reload(true);
+ await waitForLoad();
+ is(testWin.history.length, 1, "Checking the number of session history entries after reloading the top document");
+
+ testWin.close();
+ SimpleTest.finish();
+ }
+
+ async function waitForLoad() {
+ await new Promise(resolve => {
+ window.bodyOnLoad = function() {
+ setTimeout(resolve, 0);
+ window.bodyOnLoad = undefined;
+ };
+ });
+ }
+
+ async function iframeOnload(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", () => {
+ setTimeout(resolve, 0);
+ }, {once: true});
+ });
+ }
+
+ async function loadUriInFrame(frame, uri) {
+ let onloadPromise = iframeOnload(frame);
+ frame.src = uri;
+ await onloadPromise;
+ }
+ </script>
+</head>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1536471">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<body onload="test()">
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1609475</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1609475">Mozilla Bug 1609475</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug1609475.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug270414.html b/docshell/test/navigation/test_bug270414.html
new file mode 100644
index 0000000000..317eabb54e
--- /dev/null
+++ b/docshell/test/navigation/test_bug270414.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+/* eslint-disable no-useless-concat */
+/* global window0:true, window1:true, window2:true, window3:true */
+var headerHTML = "<html><head>" +
+ "<script src='/tests/SimpleTest/EventUtils.js'><\/script>" +
+ "<script src='NavigationUtils.js'><\/script>" +
+ "</head><body>";
+var footerHTML = "</body></html>";
+
+function testChild0() {
+ if (!window.window0) {
+ window0 = window.open("", "window0", "width=10,height=10");
+ window0.document.open();
+ window0.document.write(headerHTML);
+ window0.document.write("<script>navigateByLocation(opener.frames[0])<\/script>");
+ window0.document.write(footerHTML);
+ window0.document.close();
+ }
+}
+
+function testChild1() {
+ if (!window.window1) {
+ window1 = window.open("", "window1", "width=10,height=10");
+ window1.document.open();
+ window1.document.write(headerHTML);
+ window1.document.write("<script>navigateByOpen('child1');<\/script>");
+ window1.document.write(footerHTML);
+ window1.document.close();
+ }
+}
+
+function testChild2() {
+ if (!window.window2) {
+ window2 = window.open("", "window2", "width=10,height=10");
+ window2.document.open();
+ window2.document.write(headerHTML);
+ window2.document.write("<script>navigateByForm('child2');<\/script>");
+ window2.document.write(footerHTML);
+ window2.document.close();
+ }
+}
+
+function testChild3() {
+ if (!window.window3) {
+ window3 = window.open("", "window3", "width=10,height=10");
+ window3.document.open();
+ window3.document.write(headerHTML);
+ window3.document.write("<script>(async function() { await navigateByHyperlink('child3'); })();<\/script>");
+ window3.document.write(footerHTML);
+ window3.document.close();
+ }
+}
+
+add_task(async function() {
+ await waitForFinishedFrames(4);
+
+ await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+});
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=270414">Mozilla Bug 270414</a>
+<div id="frames">
+<iframe onload="testChild0();" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild1();" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild2();" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild3();" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+<script>
+window.onload = async function() {
+ document.getElementById("link0").href = target_url;
+ sendMouseEvent({type: "click"}, "link0");
+
+ await waitForFinishedFrames(1);
+
+ var array_of_frames = await getFramesByName("window0");
+ is(array_of_frames.length, 1, "Should only open one window using a fancy hyperlink.");
+
+ for (var i = 0; i < array_of_frames.length; ++i)
+ array_of_frames[i].close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=278916">Mozilla Bug 278916</a>
+<div id="links">
+<a id="link0" target="window0" onclick="window.open('', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug279495.html b/docshell/test/navigation/test_bug279495.html
new file mode 100644
index 0000000000..fdbdbf6b9f
--- /dev/null
+++ b/docshell/test/navigation/test_bug279495.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+<script>
+window.onload = async function() {
+ document.getElementById("link0").href = target_url;
+ document.getElementById("link1").href = target_url;
+
+ sendMouseEvent({type: "click"}, "link0");
+ sendMouseEvent({type: "click"}, "link1");
+
+ await waitForFinishedFrames(2);
+ await countAndClose("window0", 1);
+ await countAndClose("window1", 1);
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+
+async function countAndClose(name, expected_count) {
+ var array_of_frames = await getFramesByName(name);
+ is(array_of_frames.length, expected_count,
+ "Should only open " + expected_count +
+ " window(s) with name " + name + " using a fancy hyperlink.");
+}
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=279495">Mozilla Bug 279495</a>
+<div id="links">
+<a id="link0" target="window0" onclick="window.open('blank.html', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a>
+<a id="link1" target="window1" onclick="window.open('http://test1.example.org:80/tests/docshell/test/navigation/blank.html', 'window1', 'width=10,height=10');">This is a fancy hyperlink</a>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug344861.html b/docshell/test/navigation/test_bug344861.html
new file mode 100644
index 0000000000..76967b7b17
--- /dev/null
+++ b/docshell/test/navigation/test_bug344861.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=344861
+-->
+<head>
+ <title>Test for Bug 344861</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=344861">Mozilla Bug 344861</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 344861 **/
+SimpleTest.waitForExplicitFinish();
+
+var newwindow = window.open("/", "testwindow", "width=200,height=200");
+newwindow.onload = function() {
+ is(newwindow.innerHeight, 200, "window.open has correct height dimensions");
+ is(newwindow.innerWidth, 200, "window.open has correct width dimensions");
+ SimpleTest.finish();
+ newwindow.close();
+};
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/docshell/test/navigation/test_bug386782.html b/docshell/test/navigation/test_bug386782.html
new file mode 100644
index 0000000000..daa2ced74b
--- /dev/null
+++ b/docshell/test/navigation/test_bug386782.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386782
+-->
+<head>
+ <title>Test for Bug 386782</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <script>
+
+ // This tests if we can load a document whose root is in designMode,
+ // edit it, navigate to a new page, navigate back, still edit, and still
+ // undo/redo. Note that this is different from the case where the
+ // designMode document is in a frame inside the window, as this means
+ // the editable region is not in the root docshell (a less complicated case).
+
+ var gTests = [
+ {
+ // <html><body><p>designModeDocument</p></body></html>
+ url: "file_bug386782_designmode.html",
+ name: "designModeNavigate",
+ onload(doc) { doc.designMode = "on"; },
+ expectedBodyBeforeEdit: "<p>designModeDocument</p>",
+ expectedBodyAfterEdit: "<p>EDITED designModeDocument</p>",
+ expectedBodyAfterSecondEdit: "<p>EDITED TWICE designModeDocument</p>",
+ },
+ {
+ // <html><body contentEditable="true"><p>contentEditable</p></body></html>
+ url: "file_bug386782_contenteditable.html",
+ name: "contentEditableNavigate",
+ expectedBodyBeforeEdit: "<p>contentEditable</p>",
+ expectedBodyAfterEdit: "EDITED <br><p>contentEditable</p>",
+ expectedBodyAfterSecondEdit: "EDITED TWICE <br><p>contentEditable</p>",
+ },
+ ];
+
+ var gTestNum = -1;
+ var gTest = null;
+
+ window.onload = goNext;
+
+ function goNext() {
+ gTestNum++;
+ if (gTestNum >= gTests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ gTest = gTests[gTestNum];
+ gTest.window = window.open(gTest.url, gTest.name, "width=500,height=500");
+ window.onmessage = function(e) {
+ is(e.data.persisted, false, "Initial load cannot be persisted");
+ window.onmessage = null;
+ if ("onload" in gTest) {
+ gTest.onload(gTest.window.document);
+ }
+ SimpleTest.waitForFocus(beginTest, gTest.window);
+ };
+ }
+
+ function beginTest() {
+ gTest.window.document.body.focus();
+
+ // WARNING: If the following test fails, give the setTimeout() in the onload()
+ // a bit longer; the doc hasn't had enough time to setup its editor.
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Is doc setup yet");
+ sendString("EDITED ", gTest.window);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Editing failed.");
+
+ gTest.window.location = "about:blank";
+ let cond = () => gTest.window.location.href == "about:blank";
+ SimpleTest.waitForCondition(cond, () => {
+ SimpleTest.waitForFocus(goBack, gTest.window, true);
+ }, "about:blank never loaded");
+ }
+
+ function goBack() {
+ window.onmessage = function(e) {
+ window.onmessage = null;
+ // Skip the test if the page is not loaded from the bf-cache when going back.
+ if (e.data.persisted) {
+ checkStillEditable();
+ } else {
+ gTest.window.close();
+ goNext();
+ }
+ };
+ gTest.window.history.back();
+ }
+
+ function checkStillEditable() {
+ // Check that the contents are correct.
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Edited contents still correct?");
+
+ // Check that we can undo/redo and the contents are correct.
+ gTest.window.document.execCommand("undo", false, null);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Can we undo?");
+
+ gTest.window.document.execCommand("redo", false, null);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Can we redo?");
+
+ // Check that we can still edit the page.
+ gTest.window.document.body.focus();
+ sendString("TWICE ", gTest.window);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterSecondEdit, "Can we still edit?");
+
+ gTest.window.close();
+ goNext();
+ }
+
+ </script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386782">Mozilla Bug 386782</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 386782 **/
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug430624.html b/docshell/test/navigation/test_bug430624.html
new file mode 100644
index 0000000000..fbdc5d2677
--- /dev/null
+++ b/docshell/test/navigation/test_bug430624.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430624
+-->
+<head>
+ <title>Test for Bug 430624</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430624">Mozilla Bug 430624</a>
+<p id="display"></p>
+
+
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 430624 **/
+
+function onLoad() {
+ window.frames[0].frameElement.onload = onReload;
+ // eslint-disable-next-line no-self-assign
+ window.frames[0].frameElement.srcdoc = window.frames[0].frameElement.srcdoc;
+}
+
+function onReload() {
+ var iframe = window.frames[0].frameElement;
+ SimpleTest.waitForFocus(doTest, iframe.contentWindow);
+ iframe.contentDocument.body.focus();
+}
+
+function doTest() {
+ var bodyElement = window.frames[0].frameElement.contentDocument.body;
+ bodyElement.focus();
+ sendString("Still ", window.frames[0].frameElement.contentWindow);
+
+ is(bodyElement.innerHTML, "Still contentEditable", "Check we're contentEditable after reload");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+
+<iframe onload="onLoad()" srcdoc="<body contenteditable>contentEditable</body>"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/navigation/test_bug430723.html b/docshell/test/navigation/test_bug430723.html
new file mode 100644
index 0000000000..c2ba4b41c3
--- /dev/null
+++ b/docshell/test/navigation/test_bug430723.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430723
+-->
+<head>
+ <title>Test for Bug 430723</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430723">Mozilla Bug 430723</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// <![CDATA[
+
+/** Test for Bug 430723 **/
+
+var BASE_URI = "http://mochi.test:8888/tests/docshell/test/navigation/";
+var gTallRedBoxURI = BASE_URI + "redbox_bug430723.html";
+var gTallBlueBoxURI = BASE_URI + "bluebox_bug430723.html";
+
+window.onload = runTest;
+
+var testWindow;
+var testNum = 0;
+
+var smoothScrollPref = "general.smoothScroll";
+function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [[smoothScrollPref, false]]}, function() {
+ testWindow = window.open(gTallRedBoxURI, "testWindow", "width=300,height=300,location=yes,scrollbars=yes");
+ });
+}
+
+var nextTest = function() {
+ testNum++;
+ switch (testNum) {
+ case 1: setTimeout(step1, 0); break;
+ case 2: setTimeout(step2, 0); break;
+ case 3: setTimeout(step3, 0); break;
+ }
+};
+
+var step1 = function() {
+ window.is(String(testWindow.location), gTallRedBoxURI, "Ensure red page loaded.");
+
+ // Navigate down and up.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure the scrollpane is at the top before we start scrolling.");
+ testWindow.addEventListener("scroll", function() {
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure we can scroll down.");
+ SimpleTest.executeSoon(step1_2);
+ }, {capture: true, once: true});
+ sendKey("DOWN", testWindow);
+
+ function step1_2() {
+ testWindow.addEventListener("scroll", function() {
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure we can scroll up, back to the top.");
+
+ // Nav to blue box page. This should fire step2.
+ testWindow.location = gTallBlueBoxURI;
+ }, {capture: true, once: true});
+ sendKey("UP", testWindow);
+ }
+};
+
+
+var step2 = function() {
+ window.is(String(testWindow.location), gTallBlueBoxURI, "Ensure blue page loaded.");
+
+ // Scroll around a bit.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page2: Ensure the scrollpane is at the top before we start scrolling.");
+
+ var scrollTest = function() {
+ if (++count < 2) {
+ SimpleTest.executeSoon(function() { sendKey("DOWN", testWindow); });
+ } else {
+ testWindow.removeEventListener("scroll", scrollTest, true);
+
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page2: Ensure we could scroll.");
+
+ // Navigate backwards. This should fire step3.
+ testWindow.history.back();
+ }
+ };
+
+ var count = 0;
+ testWindow.addEventListener("scroll", scrollTest, true);
+ sendKey("DOWN", testWindow);
+};
+
+var step3 = function() {
+ window.is(String(testWindow.location), gTallRedBoxURI,
+ "Ensure red page restored from history.");
+
+ // Check we can still scroll with the keys.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1Again: Ensure scroll pane at top before we scroll.");
+ testWindow.addEventListener("scroll", function() {
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page2Again: Ensure we can still scroll.");
+
+ testWindow.close();
+ window.SimpleTest.finish();
+ }, {capture: true, once: true});
+ sendKey("DOWN", testWindow);
+};
+
+SimpleTest.waitForExplicitFinish();
+
+// ]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_child.html b/docshell/test/navigation/test_child.html
new file mode 100644
index 0000000000..7b621b4b78
--- /dev/null
+++ b/docshell/test/navigation/test_child.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(frames[0]);
+ navigateByOpen("child1");
+ navigateByForm("child2");
+ await navigateByHyperlink("child3");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0], "Should be able to navigate off-domain child by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate off-domain child by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate off-domain child by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate off-domain child by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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..b7f463a57e
--- /dev/null
+++ b/docshell/test/navigation/test_contentpolicy_block_window.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1329288
+-->
+<head>
+ <title>Test for Bug 1329288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1329288">Mozilla Bug 1329288</a>
+
+
+<!-- have a testlink which we can use for the test to open a new window -->
+<a href="http://test1.example.org/tests/docshell/test/navigation/file_contentpolicy_block_window.html"
+ target="_blank"
+ id="testlink">This is a link</a>
+
+<script class="testbody" type="text/javascript">
+/*
+ * Description of the test:
+ * The test tries to open a new window and makes sure that a registered contentPolicy
+ * gets called with the right (a non null) 'context' for the TYPE_DOCUMENT load.
+ */
+
+// Services.jsm is disabled as ChromeUtils appears to be out of scope
+/* eslint-disable mozilla/use-services */
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+
+// Content policy / factory implementation for the test
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
+var policyName = "@mozilla.org/testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ // eslint-disable-next-line mozilla/use-chromeutils-generateqi
+ QueryInterface(iid) {
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ let contentType = loadInfo.externalContentPolicyType;
+ let context = loadInfo.loadingContext;
+
+ if (SpecialPowers.wrap(contentLocation).spec !== document.getElementById("testlink").href) {
+ // not the URI we are looking for, allow the load
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ is(contentType, Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ "needs to be type document load");
+ ok(context, "context is not allowed to be null");
+ ok(context.name.endsWith("test_contentpolicy_block_window.html"),
+ "context should be the current window");
+
+ // remove the policy and finish test.
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+
+ setTimeout(function() {
+ // Component must be unregistered delayed, otherwise other content
+ // policy will not be removed from the category correctly
+ componentManager.unregisterFactory(policyID, policy);
+ }, 0);
+
+ SimpleTest.finish();
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ },
+
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+};
+
+policy = SpecialPowers.wrapCallbackObject(policy);
+componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
+
+SimpleTest.waitForExplicitFinish();
+
+// now everything is set up, let's start the test
+document.getElementById("testlink").click();
+
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1684310</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function test() {
+ /*
+ * This test is for nsIWebNavigation.gotoIndex.
+ *
+ * The test
+ * - opens a new window
+ * - loads a page there
+ * - loads another page
+ * - navigates to some fragments in the page
+ * - goes back to one of the fragments
+ * - tries to go back to the initial page.
+ */
+ window.open("file_docshell_gotoindex.html");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 508537</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=508537">Mozilla Bug 508537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug508537_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for fragment navigation during load</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978408">Mozilla Bug 978408</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_fragment_handling_during_load.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_grandchild.html b/docshell/test/navigation/test_grandchild.html
new file mode 100644
index 0000000000..1f78ca974c
--- /dev/null
+++ b/docshell/test/navigation/test_grandchild.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 200px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(frames[0].frames[0]);
+ navigateByOpen("child1_child0");
+ navigateByForm("child2_child0");
+ await navigateByHyperlink("child3_child0");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0].frames[0], "Should be able to navigate off-domain grandchild by setting location.");
+ await isNavigated(frames[1].frames[0], "Should be able to navigate off-domain grandchild by calling window.open.");
+ await isNavigated(frames[2].frames[0], "Should be able to navigate off-domain grandchild by submitting form.");
+ await isNavigated(frames[3].frames[0], "Should be able to navigate off-domain grandchild by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <script type="application/javascript">
+ /*
+ * Perform the following steps.
+ * 1) Go to file_load_history_entry_page_with_two_links.html, which contains two links, 'link1' and 'link2'
+ * 2) Click on 'link1' to be taken to file_load_history_entry_page_with_two_links.html#1
+ * 3) Click on 'link2' to be taken to file_load_history_entry_page_with_two_links.html#2
+ * 4) Go to file_load_history_entry_page_with_one_link.html
+ * 5) Push state to go to file_load_history_entry_page_with_one_link.html#1
+ *
+ * After each step
+ * - Check the number of session history entries
+ * - Reload the document and do the above again
+ * - Navigate back and check the correct history index
+ * - Navigate forward and check the correct history index and location
+ */
+ async function test() {
+ let testWin;
+ var promise;
+ var previousLocation;
+ var numSHEntries = 0;
+
+ // Step 1. Open a new tab and load a document with two links inside
+ // Now we are at file_load_history_entry_page_with_two_links.html
+ numSHEntries++;
+ promise = waitForLoad();
+ testWin = window.open("file_load_history_entry_page_with_two_links.html");
+ await promise;
+
+ let shistory = SpecialPowers.wrap(testWin)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+
+ // Step 2. Navigate the document by clicking on the 1st link
+ // Now we are at file_load_history_entry_page_with_two_links.html#1
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ await clickLink(testWin, "link1");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // Step 3. Navigate the document by clicking the 2nd link
+ // Now we are file_load_history_entry_page_with_two_links.html#2
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ await clickLink(testWin, "link2");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // Step 4. Navigate the document to a different page
+ // Now we are at file_load_history_entry_page_with_one_link.html
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ promise = waitForLoad();
+ testWin.location = "file_load_history_entry_page_with_one_link.html";
+ await promise;
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation,
+ true /* isCrossDocumentLoad */, false /* hashChangeExpected */);
+
+ // Step 5. Push some state
+ // Now we are at file_load_history_entry_page_with_one_link.html#1
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ testWin.history.pushState({foo: "bar"}, "", "#1");
+ is(testWin.history.length, numSHEntries, "Session history's length is correct after pushing state");
+ is(shistory.index, numSHEntries - 1 /* we haven't switched to new history entry yet*/,
+ "Session history's index is correct after pushing state");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // We are done with the test
+ testWin.close();
+ SimpleTest.finish();
+ }
+
+ /*
+ * @prevLocation
+ * if undefined, it is because there is no page to go back to
+ *
+ * @isCrossDocumentLoad
+ * did we just open a different document
+ * @hashChangeExpected
+ * Would we get a hash change event if we navigated backwards and forwards in history?
+ * This is framed with respect to the previous step, e.g. in the previous step was the
+ * hash different from the location we have navigated to just before calling this function?
+ * When we navigate forwards or backwards, we need to wait for this event
+ * because clickLink() also waits for hashchange event and
+ * if this function gets called before clickLink(), sometimes hashchange
+ * events from this function will leak to clickLink.
+ */
+ async function doAfterEachTest(testWin, shistory, expectedNumSHEntries, prevLocation,
+ isCrossDocumentLoad = false, hashChangeExpected = true) {
+ var initialLocation = testWin.location.href;
+ var initialSHIndex = shistory.index;
+ var promise;
+ is(testWin.history.length, expectedNumSHEntries, "Session history's length is correct");
+
+ // Reload the document
+ promise = waitForLoad();
+ testWin.location.reload(true);
+ await promise;
+ is(testWin.history.length, expectedNumSHEntries, "Session history's length is correct after reloading");
+
+ if (prevLocation == undefined) {
+ return;
+ }
+
+ var hashChangePromise;
+ if (hashChangeExpected) {
+ hashChangePromise = new Promise(resolve => {
+ testWin.addEventListener("hashchange", resolve, {once: true});
+ });
+ }
+ // Navigate backwards
+ if (isCrossDocumentLoad) {
+ // Current page must have been a cross document load, so we just need to wait for
+ // document load to complete after we navigate the history back
+ // because popstate event will not be fired in this case
+ promise = waitForLoad();
+ } else {
+ promise = waitForPopstate(testWin);
+ }
+ testWin.history.back();
+ await promise;
+ if (hashChangeExpected) {
+ await hashChangePromise;
+ }
+ is(testWin.location.href, prevLocation, "Window location is correct after navigating back in history");
+ is(shistory.index, initialSHIndex - 1, "Session history's index is correct after navigating back in history");
+
+ // Navigate forwards
+ if (isCrossDocumentLoad) {
+ promise = waitForLoad();
+ } else {
+ promise = waitForPopstate(testWin);
+ }
+ if (hashChangeExpected) {
+ hashChangePromise = new Promise(resolve => {
+ testWin.addEventListener("hashchange", resolve, {once: true});
+ });
+ }
+ testWin.history.forward();
+ await promise;
+ if (hashChangeExpected) {
+ await hashChangePromise;
+ }
+ is(testWin.location.href, initialLocation, "Window location is correct after navigating forward in history");
+ is(shistory.index, initialSHIndex, "Session history's index is correct after navigating forward in history");
+ }
+
+ async function waitForLoad() {
+ return new Promise(resolve => {
+ window.bodyOnLoad = function() {
+ setTimeout(resolve, 0);
+ window.bodyOnLoad = undefined;
+ };
+ });
+ }
+
+ async function waitForPopstate(win) {
+ return new Promise(resolve => {
+ win.addEventListener("popstate", (e) => {
+ setTimeout(resolve, 0);
+ }, {once: true});
+ });
+ }
+
+ async function clickLink(win, id) {
+ var link = win.document.getElementById(id);
+ let clickPromise = new Promise(resolve => {
+ win.addEventListener("hashchange", resolve, {once: true});
+ });
+ link.click();
+ await clickPromise;
+ }
+
+ </script>
+</head>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1539482">Bug 1539482</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<body onload="test()">
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1090918</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1090918">Mozilla Bug 1090918</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_nested_frames.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close()
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_not-opener.html b/docshell/test/navigation/test_not-opener.html
new file mode 100644
index 0000000000..aa215e4052
--- /dev/null
+++ b/docshell/test/navigation/test_not-opener.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ // navigateByLocation(window0); // Don't have a handle to the window.
+ navigateByOpen("window1");
+ navigateByForm("window2");
+ await navigateByHyperlink("window3");
+
+ await waitForFinishedFrames(6);
+
+ is((await getFramesByName("window1")).length, 2, "Should not be able to navigate popup's popup by calling window.open.");
+ is((await getFramesByName("window2")).length, 2, "Should not be able to navigate popup's popup by submitting form.");
+ is((await getFramesByName("window3")).length, 2, "Should not be able to navigate popup's popup by targeted hyperlink.");
+
+ // opener0.close();
+ opener1.close();
+ opener2.close();
+ opener3.close();
+
+ info("here")
+ await cleanupWindows();
+ info("there")
+ SimpleTest.finish();
+};
+
+// opener0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window0", "_blank", "width=10,height=10");
+let opener1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window1", "_blank", "width=10,height=10");
+let opener2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window2", "_blank", "width=10,height=10");
+let opener3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window3", "_blank", "width=10,height=10");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_opener.html b/docshell/test/navigation/test_opener.html
new file mode 100644
index 0000000000..89f1c32187
--- /dev/null
+++ b/docshell/test/navigation/test_opener.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(window0);
+ navigateByOpen("window1");
+ navigateByForm("window2");
+ await navigateByHyperlink("window3");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(window0, "Should be able to navigate popup by setting location.");
+ await isNavigated(window1, "Should be able to navigate popup by calling window.open.");
+ await isNavigated(window2, "Should be able to navigate popup by submitting form.");
+ await isNavigated(window3, "Should be able to navigate popup by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+
+ SimpleTest.finish();
+};
+
+var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window0", "width=10,height=10");
+var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window1", "width=10,height=10");
+var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window2", "width=10,height=10");
+var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window3", "width=10,height=10");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_performance_navigation.html b/docshell/test/navigation/test_performance_navigation.html
new file mode 100644
index 0000000000..55b4081a0d
--- /dev/null
+++ b/docshell/test/navigation/test_performance_navigation.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=145971
+-->
+<head>
+ <title>Test for Bug 145971</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=145971">Mozilla Bug 145971</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var testWindow;
+var bc = new BroadcastChannel("bug145971");
+bc.onmessage = function(msgEvent) {
+ var result = msgEvent.data.result;
+ if (result == undefined) {
+ info("Got unexpected message from BroadcastChannel");
+ return;
+ }
+ ok(result, "Bug 145971: Navigation type does not equal 2 when restoring document from session history.");
+ SimpleTest.finish();
+};
+
+function runTest() {
+ window.open("test_bug145971.html", "", "width=360,height=480,noopener");
+}
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+
+let window0 = null;
+let window1 = null;
+let window2 = null;
+let window3 = null;
+
+function testChild0() {
+ if (!window.window0)
+ window0 = window.open("navigate.html#opener.frames[0],location", "window0", "width=10,height=10");
+}
+
+function testChild1() {
+ if (!window.window1)
+ window1 = window.open("navigate.html#child1,open", "window1", "width=10,height=10");
+}
+
+function testChild2() {
+ if (!window.window2)
+ window2 = window.open("navigate.html#child2,form", "window2", "width=10,height=10");
+}
+
+function testChild3() {
+ if (!window.window3)
+ window3 = window.open("navigate.html#child3,hyperlink", "window3", "width=10,height=10");
+}
+
+window.onload = async function() {
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+
+</script>
+</head>
+<body>
+<div id="frames">
+<iframe onload="testChild0()" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild1()" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild2()" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild3()" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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..3ca002a9c1
--- /dev/null
+++ b/docshell/test/navigation/test_rate_limit_location_change.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1314912</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1314912 **/
+
+ const RATE_LIMIT_COUNT = 90;
+ const RATE_LIMIT_TIME_SPAN = 3;
+
+ async function setup() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ["dom.navigation.locationChangeRateLimit.count", RATE_LIMIT_COUNT],
+ ["dom.navigation.locationChangeRateLimit.timespan", RATE_LIMIT_TIME_SPAN]]});
+ }
+
+ let inc = 0;
+
+ const rateLimitedFunctions = (win) => ({
+ "history.replaceState": () => win.history.replaceState(null, "test", `${win.location.href}#${inc++}`),
+ "history.pushState": () => win.history.pushState(null, "test", `${win.location.href}#${inc++}`),
+ "history.back": () => win.history.back(),
+ "history.forward": () => win.history.forward(),
+ "history.go": () => win.history.go(-1),
+ "location.hash": () => win.location.hash = inc++,
+ "location.host": () => win.location.host = win.location.host + "",
+ "location.hostname": () => win.location.hostname = win.location.hostname + "",
+ "location.pathname": () => win.location.pathname = win.location.pathname + "",
+ "location.port": () => win.location.port = win.location.port + "",
+ "location.protocol": () => win.location.protocol = win.location.protocol + "",
+ "location.search": () => win.location.search = win.location.search + "",
+ });
+
+ async function test() {
+ await setup();
+
+ // Open new window and wait for it to load
+ let win = window.open("blank.html");
+ await new Promise((resolve) => SimpleTest.waitForFocus(resolve, win))
+
+ // Execute the history and location functions
+ Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => {
+ // Reset the rate limit for the next run.
+ info("Reset rate limit.");
+ SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit();
+
+ info(`Calling ${name} ${RATE_LIMIT_COUNT} times to reach the rate limit.`);
+ for(let i = 0; i< RATE_LIMIT_COUNT; i++) {
+ fn.call(this);
+ }
+ // Next calls should throw because we're above the rate limit
+ for(let i = 0; i < 5; i++) {
+ SimpleTest.doesThrow(() => fn.call(this), `Call #${RATE_LIMIT_COUNT + i + 1} to ${name} should throw.`);
+ }
+ })
+
+ // We didn't reset the rate limit after the last loop iteration above.
+ // Wait for the rate limit timer to expire.
+ SimpleTest.requestFlakyTimeout("Waiting to trigger rate limit reset.");
+ await new Promise((resolve) => setTimeout(resolve, 5000));
+
+ // Calls should be allowed again.
+ Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => {
+ let didThrow = false;
+ try {
+ fn.call(this);
+ } catch(error) {
+ didThrow = true;
+ }
+ is(didThrow, false, `Call to ${name} must not throw.`)
+ });
+
+ // Cleanup
+ win.close();
+ SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit();
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="setTimeout(test, 0);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1314912">Mozilla Bug 1314912</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 200px; }
+ </style>
+<script>
+if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 2);
+}
+
+async function testTop() {
+ let window0 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#top,location", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window0, "Should be able to navigate off-domain top by setting location.");
+ window0.close();
+ await cleanupWindows();
+
+ let window1 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,open", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window1, "Should be able to navigate off-domain top by calling window.open.");
+ window1.close();
+ await cleanupWindows();
+
+ let window2 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,form", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window2, "Should be able to navigate off-domain top by submitting form.");
+ window2.close();
+ await cleanupWindows();
+
+ let window3 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,hyperlink", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window3, "Should be able to navigate off-domain top by targeted hyperlink.");
+ window3.close();
+ await cleanupWindows();
+
+ await testParent();
+}
+
+async function testParent() {
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent,location"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by setting location.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,open"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by calling window.open.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,form"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by submitting form.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by targeted hyperlink.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = "";
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await testTop();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_scrollRestoration.html b/docshell/test/navigation/test_scrollRestoration.html
new file mode 100644
index 0000000000..4b8d1b213c
--- /dev/null
+++ b/docshell/test/navigation/test_scrollRestoration.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1155730</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155730">Mozilla Bug 1155730</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_scrollRestoration.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ async function case3WaitForBlank() {
+ let loaded = SimpleTest.promiseWaitForCondition(() => {
+ return testWindow.location.href == "about:blank";
+ });
+ await loaded;
+ SpecialPowers.wrap(testWindow).history.back();
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 534178</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534178">Mozilla Bug 534178</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug534178.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 462076</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="nextTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462076">Mozilla Bug 462076</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ var testFiles =
+ [ "file_bug462076_1.html", // Dynamic frames before onload
+ "file_bug462076_2.html", // Dynamic frames when handling onload
+ "file_bug462076_3.html", // Dynamic frames after onload
+ ];
+ var testCount = 0; // Used by the test files.
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function nextTest_() {
+ if (testFiles.length) {
+ testCount = 0;
+ let nextFile = testFiles.shift();
+ info("Running " + nextFile);
+ testWindow = window.open(nextFile, "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ function nextTest() {
+ setTimeout(nextTest_, 0);
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Session history + document.write</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_document_write_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1003100</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1003100">Mozilla Bug 1003100</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_shiftReload_and_pushState.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+window.onload = async function() {
+ document.getElementById("active").innerHTML =
+ '<iframe src="navigate.html#parent.frames[0],location"></iframe>' +
+ '<iframe src="navigate.html#child1,open"></iframe>' +
+ '<iframe src="navigate.html#child2,form"></iframe>' +
+ '<iframe src="navigate.html#child3,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(4);
+
+ await isNavigated(frames[0], "Should be able to navigate sibling with on-domain parent by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate sibling with on-domain parent by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate sibling with on-domain parent by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate sibling with on-domain parent by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<div id="active"></div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+window.onload = async function() {
+ document.getElementById("active").innerHTML =
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent.frames[0],location"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child1,open"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child2,form"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child3,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(4);
+
+ isBlank(frames[0], "Should not be able to navigate off-domain sibling by setting location.");
+ isBlank(frames[1], "Should not be able to navigate off-domain sibling by calling window.open.");
+ isBlank(frames[2], "Should not be able to navigate off-domain sibling by submitting form.");
+ isBlank(frames[3], "Should not be able to navigate off-domain sibling by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="blank.html"></iframe>
+<iframe name="child1" src="blank.html"></iframe>
+<iframe name="child2" src="blank.html"></iframe>
+<iframe name="child3" src="blank.html"></iframe>
+</div>
+<div id="active"></div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for static and dynamic frames and forward-back </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ var testCount = 0; // Used by the test files.
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_static_and_dynamic_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+ </script>
+</pre>
+</body>
+</html>
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..a580995e11
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1181370 - Test triggeringPrincipal for iframe navigations</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe1"></iframe>
+<iframe style="width:100%;" id="testframe2"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ *
+ * +------------------------------------+
+ * | +----------+ +--------------+ |
+ * | | Frame 1 | | Frame 2 | |
+ * | +----------+ | | |
+ * | | +----------+ | |
+ * | | | Subframe | | |
+ * | | +----------+ | |
+ * | +--------------+ |
+ * +------------------------------------+
+ *
+ * Frame1: test1.mochi.test
+ * Frame2: test2.mochi.test
+ * Subframe: test2.mochi.test
+ *
+ * (*) Frame1 and Subframe set their document.domain to mochi.test
+ * (*) Frame1 navigates the Subframe
+ * (*) TriggeringPrincipal for the Subframe navigation should be
+ * ==> test1.mochi.test
+ * (*) LoadingPrincipal for the Subframe navigation should be
+ * ==> test2.mochi.test
+ */
+
+const BASEURL1 = "http://test1.mochi.test:8888/tests/docshell/test/navigation/";
+const BASEURL2 = "http://test2.mochi.test:8888/tests/docshell/test/navigation/";
+const TRIGGERINGPRINCIPALURI = BASEURL1 + "file_triggeringprincipal_frame_1.html";
+const LOADINGPRINCIPALURI = BASEURL2 + "file_triggeringprincipal_frame_2.html";
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage);
+
+function receiveMessage(event) {
+ is(event.data.triggeringPrincipalURI, TRIGGERINGPRINCIPALURI,
+ "TriggeringPrincipal should be the navigating iframe (Frame 1)");
+ is(event.data.loadingPrincipalURI, LOADINGPRINCIPALURI,
+ "LoadingPrincipal should be the enclosing iframe (Frame 2)");
+ is(event.data.referrerURI, TRIGGERINGPRINCIPALURI,
+ "Referrer and TriggeringPrincipal should be identical (Frame 1)");
+
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+var frame1 = document.getElementById("testframe1");
+frame1.src = BASEURL1 + "file_triggeringprincipal_frame_1.html";
+
+var frame2 = document.getElementById("testframe2");
+frame2.src = BASEURL2 + "file_triggeringprincipal_frame_2.html";
+
+</script>
+</body>
+</html>
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..b9790cd0fa
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1639195 - Test triggeringPrincipal for iframe same-origin navigations</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe" src="http://example.com/"></iframe>
+
+<script type="text/javascript">
+/* We load an third-party iframe which then gets navigated by the iframe's
+ * parent by calling iframe.setAttribute("src", same-origin url) later in the
+ * test. We then verify the TriggeringPrincipal and LoadingPrincipal of the
+ * navigated iframe.
+ *
+ * +------------------------------------------+
+ * | |
+ * | +------------------+ |
+ * | | testframe | |
+ * | +------------------+ |
+ * | |
+ * | iframe.setAttribute("src", |
+ * | same-origin url) |
+ * | |
+ * +------------------------------------------+
+ */
+
+var testframe = document.getElementById("testframe");
+
+window.addEventListener("message", receiveMessage);
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html";
+
+const LOADING_PRINCIPAL_URI = TRIGGERING_PRINCIPAL_URI;
+
+function receiveMessage(event) {
+ is(event.data.triggeringPrincipalURI.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal should be the parent iframe");
+ is(event.data.loadingPrincipalURI.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "LoadingPrincipal should be the parent iframe");
+
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ testframe.removeEventListener("load", performNavigation);
+ testframe.setAttribute("src", "http://example.com/tests/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html");
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+testframe.addEventListener("load", performNavigation);
+</script>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<iframe name="framea" id="framea" src="file_triggeringprincipal_iframe_iframe_window_open_frame_a.html"></iframe>
+<iframe name="frameb" id="frameb"></iframe>
+
+<script type="text/javascript">
+
+/* We load an iframe (Frame A) which then gets navigated by another iframe (Frame B)
+ * by calling window.open("http://", "Frame A") later in the test. We then verify the
+ * TriggeringPrincipal and LoadingPrincipal of the navigated iframe (Frame A).
+ *
+ * +---------------------------------------+
+ * | Parent |
+ * | |
+ * | +----------------------------+ |
+ * | | Frame A | |
+ * | | | |
+ * | | | |
+ * | +----------------------------+ |
+ * | |
+ * | +----------------------------+ |
+ * | | Frame B | |
+ * | | | |
+ * | | win.open("http://", "A") | |
+ * | +----------------------------+ |
+ * | |
+ * +---------------------------------------+
+ *
+ * Sequence of the test:
+ * [1] load Frame A
+ * [2] load Frame B which navigates A
+ * [3] load navigated Frame A and check triggeringPrincipal and loadingPrincipal
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html";
+
+const LOADING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html";
+
+var frameA = document.getElementById("framea");
+
+function checkResults() {
+ frameA.removeEventListener("load", checkResults);
+
+ var channel = SpecialPowers.wrap(frameA.contentWindow).docShell.currentDocumentChannel;
+ var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+
+ is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for targeted window.open() should be the iframe triggering the load");
+
+ is(frameA.contentDocument.referrer, TRIGGERING_PRINCIPAL_URI,
+ "Referrer for targeted window.open() should be the principal of the iframe triggering the load");
+
+ is(loadingPrincipal.split("?")[0], LOADING_PRINCIPAL_URI,
+ "LoadingPrincipal for targeted window.open() should be the containing document");
+
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ frameA.removeEventListener("load", performNavigation);
+ frameA.addEventListener("load", checkResults);
+
+ // load Frame B which then navigates Frame A
+ var frameB = document.getElementById("frameb");
+ frameB.src = "file_triggeringprincipal_iframe_iframe_window_open_frame_b.html";
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+frameA.addEventListener("load", performNavigation);
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<iframe name="testframe" id="testframe" src="file_triggeringprincipal_iframe_iframe_window_open_base.html"></iframe>
+
+<script type="text/javascript">
+
+/* We load an iframe which then gets navigated by the iframe's parent by calling
+ * window.open("http://", iframe) later in the test. We then verify the
+ * TriggeringPrincipal and LoadingPrincipal of the navigated iframe.
+ *
+ * +------------------------------------------+
+ * | |
+ * | +------------------+ |
+ * | | testframe | |
+ * | +------------------+ |
+ * | |
+ * | window.open("http://", "testframe"); |
+ * | |
+ * +------------------------------------------+
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html";
+
+const LOADING_PRINCIPAL_URI = TRIGGERING_PRINCIPAL_URI;
+
+var testframe = document.getElementById("testframe");
+
+function checkResults() {
+ testframe.removeEventListener("load", checkResults);
+
+ var channel = SpecialPowers.wrap(testframe.contentWindow).docShell.currentDocumentChannel;
+ var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec.split("?")[0];
+ var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec.split("?")[0];
+
+ is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for targeted window.open() should be the principal of the document");
+
+ is(testframe.contentDocument.referrer.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "Referrer for targeted window.open() should be the principal of the document");
+
+ is(loadingPrincipal, LOADING_PRINCIPAL_URI,
+ "LoadingPrincipal for targeted window.open() should be the <iframe>.ownerDocument");
+
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ testframe.removeEventListener("load", performNavigation);
+ testframe.addEventListener("load", checkResults);
+ window.open("file_triggeringprincipal_parent_iframe_window_open_nav.html", "testframe");
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+testframe.addEventListener("load", performNavigation);
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<script type="text/javascript">
+
+/* We call window.open() using different URIs and make sure the triggeringPrincipal
+ * loadingPrincipal are correct.
+ * Test1: window.open(http:)
+ * Test2: window.open(javascript:)
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_window_open.html";
+
+SimpleTest.waitForExplicitFinish();
+
+const NUM_TESTS = 2;
+var test_counter = 0;
+
+function checkFinish() {
+ test_counter++;
+ if (test_counter === NUM_TESTS) {
+ SimpleTest.finish();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Test 1: window.open(http:)
+var httpWin = window.open("file_triggeringprincipal_window_open.html", "_blank", "width=10,height=10");
+httpWin.onload = function() {
+ var httpChannel = SpecialPowers.wrap(httpWin).docShell.currentDocumentChannel;
+ var httpTriggeringPrincipal = httpChannel.loadInfo.triggeringPrincipal.asciiSpec;
+ var httpLoadingPrincipal = httpChannel.loadInfo.loadingPrincipal;
+
+ is(httpTriggeringPrincipal.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for window.open(http:) should be the principal of the document");
+
+ is(httpWin.document.referrer.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "Referrer for window.open(http:) should be the principal of the document");
+
+ is(httpLoadingPrincipal, null,
+ "LoadingPrincipal for window.open(http:) should be null");
+
+ httpWin.close();
+ checkFinish();
+};
+
+// ----------------------------------------------------------------------------
+// Test 2: window.open(javascript:)
+var jsWin = window.open("javascript:'<html><body>js</body></html>';", "_blank", "width=10,height=10");
+jsWin.onload = function() {
+ var jsChannel = SpecialPowers.wrap(jsWin).docShell.currentDocumentChannel;
+ var jsTriggeringPrincipal = jsChannel.loadInfo.triggeringPrincipal.asciiSpec;
+ var jsLoadingPrincipal = jsChannel.loadInfo.loadingPrincipal;
+
+ is(jsTriggeringPrincipal.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for window.open(javascript:) should be the principal of the document");
+
+ is(jsWin.document.referrer, "",
+ "Referrer for window.open(javascript:) should be empty");
+
+ is(jsLoadingPrincipal, null,
+ "LoadingPrincipal for window.open(javascript:) should be null");
+
+ jsWin.close();
+ checkFinish();
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/unit/head_docshell.js b/docshell/test/unit/head_docshell.js
new file mode 100644
index 0000000000..1b6595b8b8
--- /dev/null
+++ b/docshell/test/unit/head_docshell.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonTestUtils: "resource://testing-common/AddonTestUtils.jsm",
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+ SearchUtils: "resource://gre/modules/SearchUtils.jsm",
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+var dirSvc = Services.dirSvc;
+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);
+
+ Services.prefs.setBoolPref("browser.search.modernConfig", true);
+ 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();
+}
+
+async function addTestEngines() {
+ // 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 Services.search.addPolicyEngine({
+ description: "urifixup search engine",
+ chrome_settings_overrides: {
+ search_provider: {
+ name: kSearchEngineID,
+ search_url: kSearchEngineURL,
+ },
+ },
+ });
+ await Services.search.addPolicyEngine({
+ description: "urifixup private search engine",
+ chrome_settings_overrides: {
+ search_provider: {
+ name: kPrivateSearchEngineID,
+ search_url: kPrivateSearchEngineURL,
+ },
+ },
+ });
+ await Services.search.addPolicyEngine({
+ description: "urifixup POST search engine",
+ chrome_settings_overrides: {
+ search_provider: {
+ name: kPostSearchEngineID,
+ search_url: kPostSearchEngineURL,
+ search_url_post_params: kPostSearchEngineData,
+ },
+ },
+ });
+}
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_info.js b/docshell/test/unit/test_URIFixup_info.js
new file mode 100644
index 0000000000..f2dc18f54e
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_info.js
@@ -0,0 +1,932 @@
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+const kForceDNSLookup = "browser.fixup.dns_first_for_single_words";
+
+// 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_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,
+ },
+ {
+ 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: "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",
+ alternateURI: "https://www.http.com//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,
+ },
+];
+
+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)
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID)
+ );
+});
+
+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();
+ gSingleWordDNSLookup = true;
+ await do_single_test_run();
+ gSingleWordDNSLookup = false;
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kPostSearchEngineID)
+ );
+ await do_single_test_run();
+});
+
+async function do_single_test_run() {
+ Services.prefs.setBoolPref(kForceDNSLookup, gSingleWordDNSLookup);
+
+ 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") +
+ ")"
+ );
+
+ 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 makeAlternativeURI =
+ flags & Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI;
+ 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"
+ );
+ Assert.equal(
+ URIInfo.fixupChangedProtocol,
+ expectProtocolChange,
+ "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..05a7b2e0b3
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_search.js
@@ -0,0 +1,142 @@
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+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: "gobbledygook:user:pass@example.com:8080/this/is/a/test.html",
+ fixed:
+ "http://gobbledygook: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)
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID)
+ );
+});
+
+// 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_browsing_context_structured_clone.js b/docshell/test/unit/test_browsing_context_structured_clone.js
new file mode 100644
index 0000000000..d06f7aecf6
--- /dev/null
+++ b/docshell/test/unit/test_browsing_context_structured_clone.js
@@ -0,0 +1,68 @@
+"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({ 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/xpcshell.ini b/docshell/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..9052267bfa
--- /dev/null
+++ b/docshell/test/unit/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+head = head_docshell.js
+
+[test_bug442584.js]
+[test_browsing_context_structured_clone.js]
+[test_URIFixup.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'
+[test_pb_notification.js]
+# Bug 751575: unrelated JS changes cause timeouts on random platforms
+skip-if = true
+[test_privacy_transition.js]
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..282fa2b30b
--- /dev/null
+++ b/docshell/test/unit_ipc/test_pb_notification_ipc.js
@@ -0,0 +1,17 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ var notifications = 0;
+ var obs = {
+ observe(aSubject, aTopic, aData) {
+ Assert.equal(aTopic, "last-pb-context-exited");
+ notifications++;
+ },
+ };
+ Services.os.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