tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 39a8a4171bb2248c50f158f0d144474e21924db9
parent f61b5e1399343fb32bfc6c0a44236d69483de02c
Author: Henri Sivonen <hsivonen@hsivonen.fi>
Date:   Mon, 24 Nov 2025 05:32:05 +0000

Bug 543435 - Make initial about:blank not get overwritten by an async about:blank load. r=sessionstore-reviewers,sfoster,timhuang,credential-management-reviewers,issammani,webidl,extension-reviewers,tabbrowser-reviewers,migration-reviewers,home-newtab-reviewers,hsivonen,cookie-reviewers,fxview-reviewers,firefox-desktop-core-reviewers ,dao,valentin,mossop,smaug,geckoview-reviewers,Jamie,mtigley,thecount,media-playback-reviewers,padenot,dom-worker-reviewers,jesup,jdescottes,asuth,robwu,karlt,profiler-reviewers,kpatenio,devtools-reviewers,tcampbell,nchevobbe

Co-authored-by: Vincent Hilla <vhilla@mozilla.com>
Signed-off-by: Vincent Hilla <vhilla@mozilla.com>

Differential Revision: https://phabricator.services.mozilla.com/D155376

Diffstat:
Maccessible/generic/DocAccessible.cpp | 10++++++----
Maccessible/tests/browser/events/browser.toml | 2++
Aaccessible/tests/browser/events/browser_initial_blank_load.js | 17+++++++++++++++++
Mbrowser/base/content/test/favicons/browser_title_flicker.js | 3+++
Mbrowser/base/content/test/performance/head.js | 5++++-
Mbrowser/base/content/test/protectionsUI/head.js | 6++++++
Mbrowser/components/BrowserGlue.sys.mjs | 4+++-
Mbrowser/components/backup/tests/browser/browser_settings_restore_from_backup.js | 16++++++++++++++++
Mbrowser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js | 11+++++++++++
Mbrowser/components/extensions/parent/ext-devtools-panels.js | 14++++++++++----
Mbrowser/components/extensions/parent/ext-tabs.js | 5++++-
Mbrowser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js | 6+++++-
Mbrowser/components/extensions/test/browser/browser_ext_windows_create_tabId.js | 5+++--
Mbrowser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs | 21+++++++++++----------
Mbrowser/components/firefoxview/tests/browser/browser_firefoxview_tab.js | 2+-
Mbrowser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js | 2+-
Mbrowser/components/ipprotection/GuardianClient.sys.mjs | 7++++++-
Mbrowser/components/migration/tests/browser/browser_extension_migration.js | 2+-
Mbrowser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js | 4++--
Mbrowser/components/preferences/tests/head.js | 4+++-
Mbrowser/components/profiles/tests/browser/browser_preferences.js | 4+++-
Mbrowser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js | 24++++++------------------
Mbrowser/components/resistfingerprinting/test/browser/head.js | 7+------
Mbrowser/components/sessionstore/test/browser_687710_2.js | 8++++++++
Mbrowser/components/sessionstore/test/browser_history_persist.js | 24++++++++++++++++++++----
Mbrowser/components/sessionstore/test/browser_restore_redirect.js | 12++++++++++++
Mbrowser/components/tabbrowser/content/tabbrowser.js | 57++++++++++++++++++++++++++++++++++++---------------------
Mbrowser/components/tabbrowser/test/browser/tabs/browser_adoptTab_failure.js | 4+++-
Mbrowser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_blank_page.js | 7++++---
Mbrowser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_new_window.js | 7+++++++
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tab_groups_keyboard_focus.js | 18++++++++++--------
Mbrowser/components/tabbrowser/test/browser/tabs/common_link_in_tab_title_and_url_prefilled.js | 14+++++++++-----
Mbrowser/extensions/newtab/test/browser/head.js | 11+++++------
Mdevtools/client/framework/test/browser_destroying_iframes.js | 3++-
Mdevtools/client/framework/toolbox.js | 3++-
Mdevtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js | 5++++-
Mdevtools/client/inspector/test/browser_inspector_reload_invalid_iframe.js | 4++--
Mdevtools/client/inspector/test/browser_inspector_reload_missing-iframe-node.js | 4++--
Mdevtools/client/jsonview/test/browser_jsonview_content_type.js | 8+++++---
Mdevtools/client/jsonview/test/browser_jsonview_numbers.js | 2+-
Mdevtools/client/jsonview/test/head.js | 2+-
Mdevtools/server/actors/resources/parent-process-document-event.js | 2+-
Mdevtools/server/actors/targets/window-global.js | 2+-
Mdevtools/server/actors/watcher/browsing-context-helpers.sys.mjs | 49+++++++++++++++++++++++++++++--------------------
Mdevtools/server/connectors/js-process-actor/target-watchers/window-global.sys.mjs | 28++++++++++++++++++++++++++--
Mdevtools/shared/dom-helpers.js | 7++++++-
Mdocshell/base/BrowsingContext.cpp | 2++
Mdocshell/base/CanonicalBrowsingContext.cpp | 6++++++
Mdocshell/base/nsDocShell.cpp | 496+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mdocshell/base/nsDocShell.h | 46+++++++++++++++++++++++++++++++++++++---------
Mdocshell/base/nsDocShellLoadState.cpp | 11+++++++++--
Mdocshell/base/nsDocShellLoadState.h | 12++++++++++++
Mdocshell/base/nsDocShellTreeOwner.cpp | 6------
Mdocshell/base/nsIDocumentViewer.idl | 4++++
Mdocshell/test/browser/browser.toml | 2++
Mdocshell/test/browser/browser_browsingContext-webProgress.js | 18+++++-------------
Mdocshell/test/browser/browser_browsing_context_discarded.js | 10+++++++++-
Mdocshell/test/browser/browser_bug388121-2.js | 6------
Mdocshell/test/browser/browser_bug670318.js | 258++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdocshell/test/browser/browser_isInitialDocument.js | 47+++++++++++++++++++++++++++--------------------
Adocshell/test/browser/browser_system_principal_initial_document.js | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdocshell/test/browser/browser_tab_touch_events.js | 3+--
Mdocshell/test/browser/file_bug670318.html | 3+--
Mdocshell/test/chrome/test_bug608669.xhtml | 2+-
Mdocshell/test/mochitest/mochitest.toml | 2++
Mdocshell/test/mochitest/test_iframe_srcdoc_to_remote.html | 2+-
Adocshell/test/mochitest/test_initial_blank_doc_principal.html | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdocshell/test/navigation/test_recursive_frames.html | 1+
Mdom/animation/test/document-timeline/test_document-timeline.html | 2+-
Mdom/base/DocGroup.h | 12------------
Mdom/base/Document.cpp | 79++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mdom/base/Document.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdom/base/crashtests/1370072.html | 34+++++++++++++++++++++++-----------
Mdom/base/domerr.msg | 1+
Mdom/base/nsFrameLoader.cpp | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mdom/base/nsGlobalWindowInner.cpp | 5+++++
Mdom/base/nsGlobalWindowInner.h | 4++++
Mdom/base/nsGlobalWindowOuter.cpp | 34+++++++++++++++++++++++++---------
Mdom/base/nsObjectLoadingContent.cpp | 2+-
Mdom/base/nsPIDOMWindow.h | 4++--
Mdom/base/test/browser_bug1703472.js | 5+++--
Mdom/base/test/browser_multiple_popups.js | 6+++++-
Mdom/base/test/file_bug426646-2.html | 6+++---
Mdom/base/test/file_focus_shadow_dom.html | 99+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdom/base/test/file_window_close.html | 2+-
Mdom/base/test/fullscreen/test_fullscreen-api-race.html | 8++++----
Mdom/base/test/head.js | 19+++++++++++++------
Mdom/base/test/test_bug1126851.html | 21++++++++++++++++-----
Mdom/base/test/test_navigator_cookieEnabled.html | 18++++++++++++++++++
Mdom/base/test/test_suppressed_microtasks.html | 70++++++++++++++++++++++++++++++++++------------------------------------
Mdom/base/test/useractivation/test_useractivation_transient_consuming.html | 1+
Mdom/canvas/crashtests/1441613.html | 4++--
Mdom/chrome-webidl/WindowGlobalActors.webidl | 9++++++---
Mdom/clients/manager/ClientOpenWindowUtils.cpp | 7++++++-
Mdom/events/test/test_bug1539497.html | 8+++-----
Mdom/events/test/test_legacy_touch_api.html | 2+-
Mdom/html/HTMLFormElement.cpp | 46++++++++++++++++++++++------------------------
Mdom/html/nsHTMLContentSink.cpp | 820-------------------------------------------------------------------------------
Mdom/html/nsHTMLDocument.cpp | 32+++++++++++++-------------------
Mdom/ipc/BrowserChild.cpp | 17+++++++++++++----
Mdom/ipc/BrowserChild.h | 4+++-
Mdom/ipc/ContentChild.cpp | 45++++++++++++++++++++++++++++++++++-----------
Mdom/ipc/ContentParent.cpp | 9++++++---
Mdom/ipc/DOMTypes.ipdlh | 2++
Mdom/ipc/PWindowGlobal.ipdl | 3+++
Mdom/ipc/WindowGlobalActor.cpp | 6++++++
Mdom/ipc/WindowGlobalActor.h | 2++
Mdom/ipc/WindowGlobalChild.cpp | 2++
Mdom/ipc/WindowGlobalParent.cpp | 2++
Mdom/ipc/WindowGlobalParent.h | 10++++++++++
Mdom/ipc/WindowGlobalTypes.ipdlh | 1+
Mdom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js | 1-
Mdom/jsurl/nsJSProtocolHandler.cpp | 2++
Mdom/media/test/test_suspend_media_by_inactive_docshell.html | 34+++++++++++++++++++++++-----------
Mdom/media/webspeech/synth/test/test_bfcache.html | 6++----
Mdom/navigation/Navigation.cpp | 4+---
Mdom/serviceworkers/test/performance/test_update.html | 9+++++----
Mdom/svg/crashtests/1329093-2.html | 8++++----
Mdom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js | 127+++++++++++++++++++++++++++++++------------------------------------------------
Mdom/tests/browser/browser_alert_from_about_blank.js | 14++++++++++++++
Mdom/tests/mochitest/bugs/test_bug346659.html | 8++++----
Mdom/tests/mochitest/chrome/test_resize_move_windows.xhtml | 4+++-
Mdom/tests/mochitest/dom-level0/test_location.html | 4++--
Mdom/tests/mochitest/general/mochitest.toml | 2+-
Mdom/tests/mochitest/general/test_bug631440.html | 9++++-----
Mdom/tests/mochitest/general/test_resizeby.html | 57++++++++++++++++++++++++++-------------------------------
Mdom/webidl/Document.webidl | 25+++++++++++++++++++++++++
Mdom/xhr/XMLHttpRequestMainThread.cpp | 39++++++++++++++++++++++++++++++++++++---
Mdom/xhr/XMLHttpRequestMainThread.h | 4++++
Mdom/xhr/tests/test_nestedSyncXHR.html | 1+
Mgfx/layers/apz/test/mochitest/apz_test_native_event_utils.js | 3+++
Mgfx/tests/browser/browser_windowless_troubleshoot_crash.js | 29+++++++----------------------
Mjs/xpconnect/tests/chrome/test_discardSystemSource.xhtml | 2+-
Mjs/xpconnect/tests/unit/xpcshell.toml | 2++
Mlayout/base/PresShell.cpp | 12+++++++++---
Mlayout/base/crashtests/1453342.html | 2+-
Mlayout/base/nsDocumentViewer.cpp | 41++++++++++++++++++++++++++++++-----------
Mlayout/build/nsContentDLF.cpp | 2++
Mlayout/style/crashtests/1384824-1.html | 26+++++++++++++-------------
Mlayout/style/crashtests/1384824-2.html | 32++++++++++++++++----------------
Mlayout/style/test/test_media_query_list.html | 3+++
Mlayout/svg/tests/test_context_properties_allowed_domains.html | 4++++
Mlayout/tools/reftest/api.js | 4++--
Mlayout/tools/reftest/reftest.sys.mjs | 11+++++++++--
Mmobile/android/geckoview/src/androidTest/assets/www/forms.html | 11+++++------
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt | 3++-
Mmobile/shared/components/extensions/ext-tabs.js | 5++---
Mnetwerk/ipc/DocumentLoadListener.cpp | 2+-
Mparser/html/nsHtml5Parser.cpp | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mparser/html/nsHtml5Parser.h | 26+++++++++++++++++++++++++-
Mparser/html/nsHtml5StreamParser.h | 8+++++++-
Mparser/htmlparser/nsIParser.h | 6++++++
Mparser/htmlparser/nsParser.cpp | 2++
Mparser/htmlparser/nsParser.h | 5+++++
Mparser/prototype/PrototypeDocumentParser.h | 2++
Mremote/marionette/actors/MarionetteEventsChild.sys.mjs | 6+++++-
Mremote/marionette/navigate.sys.mjs | 9++++++---
Mremote/shared/Navigate.sys.mjs | 23+++++++----------------
Mremote/shared/listeners/ParentWebProgressListener.sys.mjs | 7++++---
Mremote/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs | 24+++++++++++++++++++++++-
Mremote/shared/test/xpcshell/test_Navigate.js | 124++++++++++++++++---------------------------------------------------------------
Mremote/webdriver-bidi/modules/windowglobal/_configuration.sys.mjs | 2+-
Mtesting/mochitest/BrowserTestUtils/BrowserTestUtils.sys.mjs | 18++++++++++++------
Mtesting/mochitest/tests/SimpleTest/EventUtils.js | 7++++++-
Mtesting/mochitest/tests/SimpleTest/SimpleTest.js | 12++++++++----
Mtesting/modules/XPCShellContentUtils.sys.mjs | 8++++++--
Mtesting/web-platform/meta/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js.ini | 15++++++++++++++-
Mtesting/web-platform/meta/ai/summarizer/summarizer-from-detached-iframe.tentative.https.window.js.ini | 15++++++++++++++-
Mtesting/web-platform/meta/ai/writer/writer-from-detached-iframe.tentative.https.window.js.ini | 15++++++++++++++-
Dtesting/web-platform/meta/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-iframe-background-about-blank.tentative.html.ini | 2--
Mtesting/web-platform/meta/custom-elements/registries/Element-customElementRegistry-exceptions.html.ini | 3---
Dtesting/web-platform/meta/fetch/metadata/generated/element-frame.https.sub.html.ini | 5-----
Dtesting/web-platform/meta/fetch/metadata/generated/element-iframe.https.sub.html.ini | 5-----
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/010.html.ini | 3---
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html.ini | 8++------
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js.ini | 34----------------------------------
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html.ini | 14--------------
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini | 12++++++++++++
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini | 12++++++++++++
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html.ini | 43-------------------------------------------
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html.ini | 17-----------------
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html.ini | 15---------------
Dtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html.ini | 6------
Mtesting/web-platform/meta/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html.ini | 17++++++++++++++++-
Mtesting/web-platform/meta/html/browsers/windows/browsing-context.html.ini | 5+----
Atesting/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js.ini | 4++++
Dtesting/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js.ini | 4----
Atesting/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-link-click-fragment.html.ini | 3+++
Dtesting/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html.ini | 7-------
Mtesting/web-platform/meta/navigation-api/navigation-methods/navigate-from-initial-about-blank.html.ini | 3+--
Dtesting/web-platform/meta/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html.ini | 3---
Dtesting/web-platform/meta/selection/getSelection.html.ini | 8--------
Dtesting/web-platform/meta/shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms/test-003.html.ini | 5-----
Dtesting/web-platform/meta/webdriver/tests/bidi/script/add_preload_script/execution_order_tentative.py.ini | 24------------------------
Mtesting/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini | 3+++
Mtesting/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js | 12++++++------
Dtesting/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html | 17-----------------
Atesting/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html | 31+++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html | 2+-
Mtesting/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html | 20++++++++++++++++++++
Mtesting/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html | 5+++++
Mtesting/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html | 43+++++++++++++++++++++++++++++++++++--------
Atesting/web-platform/tests/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html | 26++++++++++++++++++++++++++
Mtesting/web-platform/tests/html/browsers/windows/clear-window-name.https.html | 8++++++++
Atesting/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js | 29+++++++++++++++++++++++++++++
Atesting/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js | 29+++++++++++++++++++++++++++++
Dtesting/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js | 34----------------------------------
Mtesting/web-platform/tests/old-tests/submission/Microsoft/history/history_000.htm | 4+++-
Mtesting/web-platform/tests/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py | 12++++++++++++
Mtoolkit/components/aboutprocesses/tests/browser/browser_aboutprocesses_shortcut.js | 3+++
Mtoolkit/components/antitracking/AntiTrackingUtils.cpp | 2+-
Mtoolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py | 22++++++++++++++++------
Mtoolkit/components/browser/nsWebBrowser.cpp | 28+++++++++++++---------------
Mtoolkit/components/browser/nsWebBrowser.h | 5++++-
Mtoolkit/components/cookiebanners/test/browser/browser_cookieinjector.js | 9+++++++++
Mtoolkit/components/extensions/ExtensionContent.sys.mjs | 11++++++-----
Mtoolkit/components/extensions/ExtensionParent.sys.mjs | 3+++
Mtoolkit/components/extensions/WebExtensionPolicy.cpp | 7++++---
Mtoolkit/components/extensions/parent/ext-downloads.js | 6------
Mtoolkit/components/extensions/parent/ext-identity.js | 2+-
Mtoolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html | 1+
Mtoolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html | 2+-
Mtoolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html | 1-
Mtoolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js | 5+++--
Mtoolkit/components/passwordmgr/test/browser/browser_basicAuth_switchTab.js | 6++++++
Mtoolkit/components/resistfingerprinting/tests/browser/browser_fingerprintingWebCompat.js | 6+++++-
Mtoolkit/components/thumbnails/BackgroundPageThumbs.sys.mjs | 3++-
Mtoolkit/components/windowwatcher/nsIOpenWindowInfo.idl | 42++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/windowwatcher/nsIWindowWatcher.idl | 4++--
Mtoolkit/components/windowwatcher/nsOpenWindowInfo.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtoolkit/components/windowwatcher/nsOpenWindowInfo.h | 8+++++++-
Mtoolkit/components/windowwatcher/nsWindowWatcher.cpp | 111++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mtoolkit/content/tests/browser/browser_bug1572798.js | 3+++
Mtoolkit/content/tests/browser/browser_cancel_starting_autoscrolling_requested_by_background_tab.js | 10++++++++--
Mtoolkit/content/tests/widgets/test_videocontrols_standalone.html | 54+++++++++++++++++++++++++++++-------------------------
Mtoolkit/modules/HiddenFrame.sys.mjs | 2--
Mtoolkit/modules/SubDialog.sys.mjs | 4+++-
Mtools/profiler/tests/browser/browser_test_feature_js_sourceindex.js | 5+++++
Muriloader/base/nsDocLoader.h | 6+++---
Mwidget/nsIBaseWindow.idl | 31+------------------------------
Mwidget/tests/test_alwaysontop_focus.xhtml | 2+-
Mxpfe/appshell/AppWindow.cpp | 16+++++-----------
Mxpfe/appshell/AppWindow.h | 4+++-
Mxpfe/appshell/nsAppShellService.cpp | 84++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mxpfe/appshell/nsChromeTreeOwner.cpp | 8--------
Mxpfe/appshell/nsContentTreeOwner.cpp | 10----------
246 files changed, 2947 insertions(+), 2430 deletions(-)

diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp @@ -533,7 +533,8 @@ void DocAccessible::Init() { // this failed. The DocAccessible was subsequently created due to a layout // notification. if (mDocumentNode->GetReadyStateEnum() == - dom::Document::READYSTATE_COMPLETE) { + dom::Document::READYSTATE_COMPLETE && + !mDocumentNode->IsUncommittedInitialDocument()) { mLoadState |= eDOMLoaded; // If this happened due to reasons 1 or 2, it isn't *necessary* to fire a // doc load complete event. If it happened due to reason 3, we need to fire @@ -543,9 +544,10 @@ void DocAccessible::Init() { // harm even if it isn't necessary. We set mLoadEventType here and it will // be fired in ProcessLoad as usual. mLoadEventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE; - } else if (mDocumentNode->IsInitialDocument()) { - // The initial about:blank document will never finish loading, so we can - // immediately mark it loaded to avoid waiting for its load. + } else if (mDocumentNode->IsUncommittedInitialDocument()) { + // The initial about:blank always has its readyState as "complete" + // even if it didn't fire a load event yet. We cannot know whether + // it will load, so mark it loaded to avoid waiting for it. mLoadState |= eDOMLoaded; } diff --git a/accessible/tests/browser/events/browser.toml b/accessible/tests/browser/events/browser.toml @@ -17,6 +17,8 @@ prefs = [ ["browser_focus_document.js"] +["browser_initial_blank_load.js"] + ["browser_menu_and_alerts.js"] ["browser_tab_name_change.js"] diff --git a/accessible/tests/browser/events/browser_initial_blank_load.js b/accessible/tests/browser/events/browser_initial_blank_load.js @@ -0,0 +1,17 @@ +"use strict"; + +addAccessibleTask( + "we get a doc load complete event for the initial about:blank", + async function (browser, docAcc) { + let loaded = waitForEvent( + EVENT_DOCUMENT_LOAD_COMPLETE, + evt => evt.accessible.parent.parent == docAcc + ); + await invokeContentTask(browser, [], () => { + content.iframe = content.document.createElement("iframe"); + content.iframe.src = "about:blank"; + content.document.body.append(content.iframe); + }); + await loaded; + } +); diff --git a/browser/base/content/test/favicons/browser_title_flicker.js b/browser/base/content/test/favicons/browser_title_flicker.js @@ -52,6 +52,7 @@ add_task(async () => { await waitForAttributeChange(tab, "label"); ok(tab.hasAttribute("busy"), "Should have seen the busy attribute"); let label = tab.textLabel; + ok(label.textContent.includes("file_with_slow_favicon.html")); let bounds = label.getBoundingClientRect(); await waitForAttributeChange(tab, "busy"); @@ -160,6 +161,8 @@ add_task(async () => { await waitForAttributeChange(tab, "label"); ok(tab.hasAttribute("busy"), "Should have seen the busy attribute"); + let label = tab.textLabel; + ok(label.textContent.includes("file_with_slow_favicon.html")); let newBounds = tab.getBoundingClientRect(); is( bounds.width, diff --git a/browser/base/content/test/performance/head.js b/browser/base/content/test/performance/head.js @@ -489,7 +489,10 @@ async function recordFrames(testPromise, win = window) { win.addEventListener("MozAfterPaint", afterPaintListener); // If the test is using an existing window, capture a frame immediately. - if (win.document.readyState == "complete") { + if ( + win.document.readyState == "complete" && + win.location.href != "about:blank" + ) { afterPaintListener(); } diff --git a/browser/base/content/test/protectionsUI/head.js b/browser/base/content/test/protectionsUI/head.js @@ -59,6 +59,12 @@ async function openProtectionsPanel(toast, win = window) { "shown" ); + // nsXULTooltipListener will fail silently if no drag service is available + Assert.ok( + !SpecialPowers.isHeadless, + "openProtectionsPanel cannot be used in headless mode." + ); + // Move out than move over the shield icon to trigger the hover event in // order to fetch tracker count. EventUtils.synthesizeMouseAtCenter( diff --git a/browser/components/BrowserGlue.sys.mjs b/browser/components/BrowserGlue.sys.mjs @@ -678,9 +678,11 @@ BrowserGlue.prototype = { if (makeWindowPrivate) { browserWindowFeatures += ",private"; } + + // We use a null URI such that the window stays on the initial uncommitted about:blank let win = Services.ww.openWindow( null, - "about:blank", + null, null, browserWindowFeatures, null diff --git a/browser/components/backup/tests/browser/browser_settings_restore_from_backup.js b/browser/components/backup/tests/browser/browser_settings_restore_from_backup.js @@ -41,11 +41,21 @@ add_setup(async () => { }); }); +async function waitInitialRequestStateSettled() { + // restore-from-backup.mjs is rendered quite late during the load. + // Bug 543435 caused a timing change such that withNewTab resolves right after + // that component dispatches BackupUI:InitWidget. So BackupUIParent hasn't yet + // received RequestState. If the RequestState / StateUpdate happens during the test + // that can mess things up, so wait a tick. See bug 2001583 + await new Promise(res => setTimeout(res)); +} + /** * Tests for when the user specifies an invalid backup file to restore. */ add_task(async function test_backup_failure() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); const mockBackupFilePath = await IOUtils.createUniqueFile( TEST_PROFILE_PATH, "backup.html" @@ -101,6 +111,7 @@ add_task(async function test_backup_failure() { */ add_task(async function test_restore_from_backup() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); // Info about our mock backup const date = new Date().getTime(); const deviceName = "test-device"; @@ -256,6 +267,7 @@ add_task(async function test_restore_from_backup() { */ add_task(async function test_restore_uses_matching_initial_folder() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); const mockBackupFilePath = await IOUtils.createUniqueFile( TEST_PROFILE_PATH, "backup.html" @@ -312,6 +324,7 @@ add_task(async function test_restore_uses_matching_initial_folder() { */ add_task(async function test_restore_in_progress() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); let sandbox = sinon.createSandbox(); let bs = getAndMaybeInitBackupService(); @@ -456,6 +469,7 @@ add_task( await BrowserTestUtils.withNewTab( "about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); let sandbox = sinon.createSandbox(); let settings = browser.contentDocument.querySelector("backup-settings"); await settings.updateComplete; @@ -539,6 +553,7 @@ add_task( */ add_task(async function test_restore_backup_file_info_display() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); let settings = browser.contentDocument.querySelector("backup-settings"); await settings.updateComplete; @@ -633,6 +648,7 @@ function assertNonEmbeddedSupportLink(link, linkName) { */ add_task(async function test_support_links_non_embedded() { await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); let settings = browser.contentDocument.querySelector("backup-settings"); await settings.updateComplete; diff --git a/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js b/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js @@ -556,6 +556,15 @@ add_task(async function test_default_location_selected() { await SpecialPowers.popPrefEnv(); }); +async function waitInitialRequestStateSettled() { + // restore-from-backup.mjs is rendered quite late during the load. + // Bug 543435 caused a timing change such that withNewTab resolves right after + // that component dispatches BackupUI:InitWidget. So BackupUIParent hasn't yet + // received RequestState. If the RequestState / StateUpdate happens during the test + // that can mess things up, so wait a tick. See bug 2001583 + await new Promise(res => setTimeout(res)); +} + /** * Tests that the persistent data for embedded components is set when a user picks a file * and is flushed once backup is enabled. @@ -566,6 +575,7 @@ add_task(async function test_embedded_component_persistent_data_filepicker() { }); await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); const mockCustomParentDir = await IOUtils.createUniqueDirectory( PathUtils.tempDir, "our-dummy-folder" @@ -651,6 +661,7 @@ add_task( await BrowserTestUtils.withNewTab( "about:preferences#sync", async browser => { + await waitInitialRequestStateSettled(); const mockCustomParentDir = await IOUtils.createUniqueDirectory( PathUtils.tempDir, "our-dummy-folder" diff --git a/browser/components/extensions/parent/ext-devtools-panels.js b/browser/components/extensions/parent/ext-devtools-panels.js @@ -16,8 +16,6 @@ ChromeUtils.defineESModuleGetters(this, { var { watchExtensionProxyContextLoad } = ExtensionParent; -var { promiseDocumentLoaded } = ExtensionUtils; - const WEBEXT_PANELS_URL = "chrome://browser/content/webext-panels.xhtml"; class BaseDevToolsPanel { @@ -489,9 +487,17 @@ class ParentDevToolsInspectorSidebar extends BaseDevToolsPanel { // Wait the webext-panel.xhtml page to have been loaded in the // inspector sidebar panel. - promiseDocumentLoaded(containerEl.contentDocument).then(() => { + const onLoaded = () => { this.createBrowserElement(containerEl.contentWindow); - }); + }; + // ExtensionUtils.promiseDocumentLoaded would attach a load listener to the + // container window, which will be replaced during the load (Bug 1955324). + const doc = containerEl.contentDocument; + if (doc.readyState == "complete" && doc.location.href != "about:blank") { + onLoaded(); + } else { + containerEl.addEventListener("load", onLoaded, { once: true }); + } } onExtensionPageUnmount() { diff --git a/browser/components/extensions/parent/ext-tabs.js b/browser/components/extensions/parent/ext-tabs.js @@ -866,11 +866,14 @@ this.tabs = class extends ExtensionAPIPersistent { if ( createProperties.url && - createProperties.url !== window.BROWSER_NEW_TAB_URL + createProperties.url !== window.BROWSER_NEW_TAB_URL && + !createProperties.url.startsWith("about:blank") ) { // We can't wait for a location change event for about:newtab, // since it may be pre-rendered, in which case its initial // location change event has already fired. + // The same goes for about:blank, since the initial blank document + // is loaded synchronously. // Mark the tab as initializing, so that operations like // `executeScript` wait until the requested URL is loaded in diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js @@ -248,7 +248,11 @@ add_task( let onNewTabOpened = new Promise(resolve => win.gBrowser.addTabsProgressListener({ onStateChange(browser, webProgress, request, stateFlags) { - if (stateFlags & Ci.nsIWebProgressListener.STATE_START) { + if ( + stateFlags & Ci.nsIWebProgressListener.STATE_START && + request.QueryInterface(Ci.nsIChannel).originalURI.spec !== + "about:blank" + ) { win.gBrowser.removeTabsProgressListener(this); resolve(win.gBrowser.getTabForBrowser(browser)); } diff --git a/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js @@ -11,8 +11,9 @@ function assertNoLeaksInTabTracker() { for (const [tabId, nativeTab] of tabTracker._tabIds) { if (!nativeTab.ownerGlobal) { - ok( - false, + // Disable this check due to bug 1987344, 1806361 + //ok(false, + info( `A tab with tabId ${tabId} has been leaked in the tabTracker ("${nativeTab.title}")` ); } diff --git a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs @@ -72,6 +72,9 @@ async function openFirefoxViewTab(win) { : TestUtils.topicObserved("firefoxview-entered"); if (!fxviewTab?.selected) { + testScope.info( + "Navigating to about:firefoxview by clicking firefox-view-button" + ); await BrowserTestUtils.synthesizeMouseAtCenter( "#firefox-view-button", { type: "mousedown" }, @@ -90,16 +93,14 @@ async function openFirefoxViewTab(win) { testScope.info( "openFirefoxViewTab, waiting for complete readyState, visible and firefoxview-entered" ); - await Promise.all([ - TestUtils.waitForCondition(() => { - const document = fxviewTab.linkedBrowser.contentDocument; - return ( - document.readyState == "complete" && - document.visibilityState == "visible" - ); - }), - enteredPromise, - ]); + await enteredPromise; + await TestUtils.waitForCondition(() => { + const document = fxviewTab.linkedBrowser.contentDocument; + return ( + document.readyState == "complete" && document.visibilityState == "visible" + ); + }); + testScope.info("openFirefoxViewTab, ready resolved"); return fxviewTab; } diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js @@ -265,7 +265,7 @@ add_task(async function testFxViewNotMultiselect() { win.FirefoxViewHandler.tab.selected, "Firefox View tab is selected" ); - let tab2 = await add_new_tab("https://www.mozilla.org"); + let tab2 = await add_new_tab("https://www.mozilla.org/"); let fxViewBtn = win.document.getElementById("firefox-view-button"); info("We multi-select a visible tab with ctrl key down"); diff --git a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -requestLongerTimeout(2); +requestLongerTimeout(3); ChromeUtils.defineESModuleGetters(globalThis, { SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", diff --git a/browser/components/ipprotection/GuardianClient.sys.mjs b/browser/components/ipprotection/GuardianClient.sys.mjs @@ -107,10 +107,15 @@ export class GuardianClient { }); const finalEndpoint = waitUntilURL(browser, url => { const urlObj = new URL(url); + if (url === "about:blank") { + return false; + } if (!allowedOrigins.includes(urlObj.origin)) { browser.stop(); browser.remove(); - throw new Error(`URL origin ${urlObj.origin} is not allowed.`); + throw new Error( + `URL ${url} with origin ${urlObj.origin} is not allowed.` + ); } if ( finalizerURLs.some( diff --git a/browser/components/migration/tests/browser/browser_extension_migration.js b/browser/components/migration/tests/browser/browser_extension_migration.js @@ -119,7 +119,7 @@ async function assertSupportLink(link, url, message) { Assert.stringMatches(link.textContent, message); Assert.stringMatches(link.href, url); if (message && url) { - link.href = "about:blank"; + link.href = "data:text/html,"; let linkOpened = BrowserTestUtils.waitForNewTab(gBrowser, link.href); EventUtils.synthesizeMouseAtCenter(link, {}, link.ownerGlobal); let tab = await linkOpened; diff --git a/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js b/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js @@ -80,10 +80,10 @@ add_task(async function () { const TAB_SHENTRY = { url: TAB_URL, triggeringPrincipal_base64 }; const TAB_STATE = { entries: [TAB_SHENTRY], formdata: TAB_FORMDATA }; - let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab( + let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:blank" - )); + ); // Fake a post-crash tab SessionStore.setTabState(tab, JSON.stringify(TAB_STATE)); diff --git a/browser/components/preferences/tests/head.js b/browser/components/preferences/tests/head.js @@ -127,7 +127,9 @@ async function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) { if (!newTabBrowser.contentWindow) { await BrowserTestUtils.waitForEvent(newTabBrowser, "Initialized", true); - await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load"); + if (newTabBrowser.contentDocument.readyState != "complete") { + await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load"); + } await finalPrefPaneLoaded; } diff --git a/browser/components/profiles/tests/browser/browser_preferences.js b/browser/components/profiles/tests/browser/browser_preferences.js @@ -24,7 +24,9 @@ async function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) { if (!newTabBrowser.contentWindow) { await BrowserTestUtils.waitForEvent(newTabBrowser, "Initialized", true); - await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load"); + if (newTabBrowser.contentDocument.readyState != "complete") { + await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load"); + } await finalPrefPaneLoaded; } diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js @@ -4,24 +4,12 @@ */ async function test_dialog_window() { - let diagWin; - - await new Promise(resolve => { - // Open a dialog window which is not rounded size. - diagWin = window.openDialog( - "about:blank", - null, - "innerWidth=250,innerHeight=350" - ); - - diagWin.addEventListener( - "load", - function () { - resolve(); - }, - { once: true } - ); - }); + // Open a dialog window which is not rounded size. + let diagWin = window.openDialog( + "about:blank", + null, + "innerWidth=250,innerHeight=350" + ); is(diagWin.innerWidth, 250, "The dialog window doesn't have a rounded size."); is( diff --git a/browser/components/resistfingerprinting/test/browser/head.js b/browser/components/resistfingerprinting/test/browser/head.js @@ -352,12 +352,7 @@ async function calcPopUpWindowChromeUISize() { tab.linkedBrowser, [], async function () { - let win; - - await new Promise(resolve => { - win = content.open("about:blank", "", "width=1000,height=1000"); - win.onload = () => resolve(); - }); + let win = content.open("about:blank", "", "width=1000,height=1000"); let res = { chromeWidth: win.outerWidth - win.innerWidth, diff --git a/browser/components/sessionstore/test/browser_687710_2.js b/browser/components/sessionstore/test/browser_687710_2.js @@ -37,6 +37,14 @@ var state = { add_task(async function test() { let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + + // addTab sends a message to the child to load about:blank, which sends a + // message to the parent to add the SH entry. + // promiseTabState modifies the SH syncronously, so ensure it is settled. + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); + await promiseTabState(tab, state); function compareEntries(i, j, history) { diff --git a/browser/components/sessionstore/test/browser_history_persist.js b/browser/components/sessionstore/test/browser_history_persist.js @@ -27,10 +27,18 @@ add_task(async function check_history_not_persisted() { // Open a new tab to restore into. tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); browser = tab.linkedBrowser; - await promiseTabState(tab, state); let sessionHistory = browser.browsingContext.sessionHistory; + // addTab sends a message to the child to load about:blank, which sends a + // message to the parent to add the SH entry. + // promiseTabState modifies the SH syncronously, so ensure it is settled. + await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" }); + is(sessionHistory.count, 1, "Should have initial entry"); + + info("New about:blank loaded, restoring state"); + await promiseTabState(tab, state); + is(sessionHistory.count, 1, "Should be a single history entry"); is( sessionHistory.getEntryAtIndex(0).URI.spec, @@ -40,7 +48,7 @@ add_task(async function check_history_not_persisted() { // Load a new URL into the tab, it should replace the about:blank history entry BrowserTestUtils.startLoadingURIString(browser, "about:robots"); - await promiseBrowserLoaded(browser, false, "about:robots"); + await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:robots" }); sessionHistory = browser.browsingContext.sessionHistory; @@ -75,10 +83,18 @@ add_task(async function check_history_default_persisted() { // Open a new tab to restore into. tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); browser = tab.linkedBrowser; - await promiseTabState(tab, state); let sessionHistory = browser.browsingContext.sessionHistory; + // addTab sends a message to the child to load about:blank, which sends a + // message to the parent to add the SH entry. + // promiseTabState modifies the SH syncronously, so ensure it is settled. + await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" }); + is(sessionHistory.count, 1, "Should have initial entry"); + + info("New about:blank loaded, restoring state"); + await promiseTabState(tab, state); + is(sessionHistory.count, 1, "Should be a single history entry"); is( sessionHistory.getEntryAtIndex(0).URI.spec, @@ -86,7 +102,7 @@ add_task(async function check_history_default_persisted() { "Should be the right URL" ); - // Load a new URL into the tab, it should replace the about:blank history entry + // Load a new URL into the tab, it should NOT replace the about:blank history entry BrowserTestUtils.startLoadingURIString(browser, "about:robots"); await promiseBrowserLoaded(browser, false, "about:robots"); diff --git a/browser/components/sessionstore/test/browser_restore_redirect.js b/browser/components/sessionstore/test/browser_restore_redirect.js @@ -16,6 +16,12 @@ add_task(async function check_http_redirect() { // Open a new tab to restore into. let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); let browser = tab.linkedBrowser; + + // addTab sends a message to the child to load about:blank, which sends a + // message to the parent to add the SH entry. + // promiseTabState modifies the SH syncronously, so ensure it is settled. + await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" }); + await promiseTabState(tab, state); info("Restored tab"); @@ -47,6 +53,12 @@ add_task(async function check_js_redirect() { // Open a new tab to restore into. let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); let browser = tab.linkedBrowser; + + // addTab sends a message to the child to load about:blank, which sends a + // message to the parent to add the SH entry. + // promiseTabState modifies the SH syncronously, so ensure it is settled. + await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" }); + let loadPromise = BrowserTestUtils.browserLoaded(browser, true, url => url.endsWith("restore_redirect_target.html") ); diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -2588,6 +2588,7 @@ // of those notifications can cause code to run that inspects our // state, so it is important that the tab element is fully // initialized by this point. + // AppendChild will cause a synchronous about:blank load. this.tabpanels.appendChild(panel); } @@ -3117,7 +3118,7 @@ fromExternal, forceAllowDataURI, isCaptivePortalTab, - skipLoad, + skipLoad: skipLoad || uriIsAboutBlank, referrerInfo, charset, postData, @@ -3718,6 +3719,16 @@ return t; } + /** + * + * @param {object} options + * @param {nsIPrincipal} [options.originPrincipal] + * If uriString is given, uri might inherit principals, and no preloaded browser is used, + * this is the origin principal to be inherited by the initial about:blank. + * @param {nsIPrincipal} [options.originStoragePrincipal] + * If uriString is given, uri might inherit principals, and no preloaded browser is used, + * this is the origin storage principal to be inherited by the initial about:blank. + */ _createBrowserForTab( tab, { @@ -3849,21 +3860,28 @@ textDirectiveUserActivation, } ) { - if ( - !usingPreloadedContent && - originPrincipal && - originStoragePrincipal && - uriString - ) { - let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler; - // Unless we know for sure we're not inheriting principals, - // force the about:blank viewer to have the right principal: - if (!uri || doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) { - browser.createAboutBlankDocumentViewer( - originPrincipal, - originStoragePrincipal - ); + const shouldInheritSecurityContext = (() => { + if ( + !usingPreloadedContent && + originPrincipal && + originStoragePrincipal && + uriString + ) { + let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler; + // Unless we know for sure we're not inheriting principals, + // force the about:blank viewer to have the right principal: + if (!uri || doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) { + return true; + } } + return false; + })(); + + if (shouldInheritSecurityContext) { + browser.createAboutBlankDocumentViewer( + originPrincipal, + originStoragePrincipal + ); } // If we didn't swap docShells with a preloaded browser @@ -3889,6 +3907,8 @@ } else if (!triggeringPrincipal.isSystemPrincipal) { // XXX this code must be reviewed and changed when bug 1616353 // lands. + // The purpose of LOAD_FLAGS_FIRST_LOAD is to close a new + // tab if it turns out to be a download. loadFlags |= LOAD_FLAGS_FIRST_LOAD; } if (!allowInheritPrincipal) { @@ -6879,16 +6899,11 @@ // new tab must have the same usercontextid as the old one params.userContextId = aTab.getAttribute("usercontextid"); } + params.skipLoad = true; let newTab = this.addWebTab("about:blank", params); - let newBrowser = this.getBrowserForTab(newTab); aTab.container.tabDragAndDrop.finishAnimateTabMove(); - if (!createLazyBrowser) { - // Stop the about:blank load. - newBrowser.stop(); - } - if (!this.swapBrowsersAndCloseOther(newTab, aTab)) { // Swapping wasn't permitted. Bail out. this.removeTab(newTab); diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_adoptTab_failure.js b/browser/components/tabbrowser/test/browser/tabs/browser_adoptTab_failure.js @@ -37,7 +37,9 @@ add_task(async function test_replaceTabsWithWindow() { const windowOpenedPromise = BrowserTestUtils.waitForNewWindow(); const win2 = gBrowser.replaceTabsWithWindow(selectedTab); - await BrowserTestUtils.waitForEvent(win2, "DOMContentLoaded"); + // BrowserTestUtils.waitForEvent will resolve the next tick, by which point + // we'll already have adopted nonAdoptableTab + await new Promise(res => win2.addEventListener("DOMContentLoaded", res)); const gBrowser2 = win2.gBrowser; makeAdoptTabFailOnceFor(gBrowser2, nonAdoptableTab); await windowOpenedPromise; diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_blank_page.js b/browser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_blank_page.js @@ -107,9 +107,10 @@ add_task(async function by_script() { tab: BLANK_TITLE, urlbar: UrlbarTestUtils.trimURL(BLANK_URL), }, - async actionWhileLoading(onTabLoaded) { - info("Wait until loading the link target"); - await onTabLoaded; + async actionWhileLoading(_onTabLoaded) { + // window.open("about:blank") will cause a synchronous load that cannot + // be catched by listeners attached afterwards + info("Skip waiting for link target to load"); }, finalState: { tab: BLANK_TITLE, diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_new_window.js b/browser/components/tabbrowser/test/browser/tabs/browser_link_in_tab_title_and_url_prefilled_new_window.js @@ -56,5 +56,12 @@ add_task(async function blank_page__by_script() { await doTestWithNewWindow({ link: "blank-page--by-script", expectedSetURICalled: false, + async actionWhileLoading(_onTabLoaded) { + // window.open("about:blank") will cause a synchronous load that cannot + // be caught by listeners attached afterwards + info("Skip waiting for link target to load"); + // catch a possible window unloaded while waiting for load error + _onTabLoaded.catch(() => {}); + }, }); }); diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_tab_groups_keyboard_focus.js b/browser/components/tabbrowser/test/browser/tabs/browser_tab_groups_keyboard_focus.js @@ -45,10 +45,11 @@ function synthesizeKeyForKeyboardMovement(element, keyName) { } add_task(async function test_TabGroupKeyboardFocus() { - const tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab3 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab4 = BrowserTestUtils.addTab(gBrowser, "about:blank"); + const URL = "about:blank"; + const tab1 = BrowserTestUtils.addTab(gBrowser, URL); + const tab2 = BrowserTestUtils.addTab(gBrowser, URL); + const tab3 = BrowserTestUtils.addTab(gBrowser, URL); + const tab4 = BrowserTestUtils.addTab(gBrowser, URL); const tabGroup = gBrowser.addTabGroup([tab2, tab3], { insertBefore: tab2 }); @@ -183,10 +184,11 @@ add_task(async function test_TabGroupKeyboardFocus() { }); add_task(async function test_TabGroupKeyboardMovement() { - const tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab3 = BrowserTestUtils.addTab(gBrowser, "about:blank"); - const tab4 = BrowserTestUtils.addTab(gBrowser, "about:blank"); + const URL = "about:blank"; + const tab1 = BrowserTestUtils.addTab(gBrowser, URL); + const tab2 = BrowserTestUtils.addTab(gBrowser, URL); + const tab3 = BrowserTestUtils.addTab(gBrowser, URL); + const tab4 = BrowserTestUtils.addTab(gBrowser, URL); const tabGroup = gBrowser.addTabGroup([tab2, tab3], { insertBefore: tab2 }); diff --git a/browser/components/tabbrowser/test/browser/tabs/common_link_in_tab_title_and_url_prefilled.js b/browser/components/tabbrowser/test/browser/tabs/common_link_in_tab_title_and_url_prefilled.js @@ -88,7 +88,11 @@ async function doTestInSameWindow({ }); } -async function doTestWithNewWindow({ link, expectedSetURICalled }) { +async function doTestWithNewWindow({ + link, + expectedSetURICalled, + actionWhileLoading = async p => await p, +}) { await SpecialPowers.pushPrefEnv({ set: [["browser.link.open_newwindow", 2]], }); @@ -121,10 +125,10 @@ async function doTestWithNewWindow({ link, expectedSetURICalled }) { isSetURIWhileLoading = true; } }); - await BrowserTestUtils.browserLoaded( - win.gBrowser.selectedBrowser, - false, - href || (() => true) + await actionWhileLoading( + BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser, { + wantLoad: href || (() => true), + }) ); sandbox.restore(); diff --git a/browser/extensions/newtab/test/browser/head.js b/browser/extensions/newtab/test/browser/head.js @@ -71,12 +71,11 @@ async function clearHistoryAndBookmarks() { * not necessarily have had all its javascript/render logic executed. */ async function waitForPreloaded(browser) { - let readyState = await ContentTask.spawn( - browser, - null, - () => content.document.readyState - ); - if (readyState !== "complete") { + let [readyState, location] = await ContentTask.spawn(browser, null, () => [ + content.document.readyState, + content.document.location.href, + ]); + if (readyState !== "complete" || location === "about:blank") { await BrowserTestUtils.browserLoaded(browser); } } diff --git a/devtools/client/framework/test/browser_destroying_iframes.js b/devtools/client/framework/test/browser_destroying_iframes.js @@ -19,8 +19,9 @@ add_task(async function () { for (let i = 0; i < 10; i++) { await SpecialPowers.spawn(browser, [], async function () { const iframe = content.document.createElement("iframe"); + const loaded = new Promise(res => (iframe.onload = res)); content.document.body.appendChild(iframe); - await new Promise(res => (iframe.onload = res)); + await loaded; iframe.remove(); }); } diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js @@ -2905,7 +2905,8 @@ Toolbox.prototype = { // on the DOM node every time because this won't work // if the (xul chrome) iframe is loaded in a content docshell. if (iframe.contentWindow) { - DOMHelpers.onceDOMReady(iframe.contentWindow, onLoad); + const loadingUrl = definition.url || "about:blank"; + DOMHelpers.onceDOMReady(iframe.contentWindow, onLoad, loadingUrl); } else { const callback = () => { iframe.removeEventListener("DOMContentLoaded", callback); diff --git a/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js b/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js @@ -70,7 +70,10 @@ async function testSetExtensionPageSidebarPanel(panelDoc, expectedURL) { const iframeWindow = panelDoc.querySelector(selector).contentWindow; await TestUtils.waitForCondition(() => { - return iframeWindow.document.readyState === "complete"; + return ( + iframeWindow.document.readyState === "complete" && + iframeWindow.location.href != "about:blank" + ); }, "Wait for the extension page iframe to complete to load"); is( diff --git a/devtools/client/inspector/test/browser_inspector_reload_invalid_iframe.js b/devtools/client/inspector/test/browser_inspector_reload_invalid_iframe.js @@ -20,10 +20,9 @@ add_task(async function () { // Create an iframe element with the same id "fake-iframe". const iframe = content.document.createElement("iframe"); - content.document.body.appendChild(iframe); iframe.setAttribute("id", "fake-iframe"); - iframe.contentWindow.addEventListener("load", () => { + iframe.addEventListener("load", () => { // Create a div element and append it to the iframe const div = content.document.createElement("div"); div.id = "in-frame"; @@ -34,6 +33,7 @@ add_task(async function () { frameContent.appendChild(div); resolve(); }); + content.document.body.appendChild(iframe); }); }); diff --git a/devtools/client/inspector/test/browser_inspector_reload_missing-iframe-node.js b/devtools/client/inspector/test/browser_inspector_reload_missing-iframe-node.js @@ -17,9 +17,8 @@ add_task(async function () { await ContentTask.spawn(gBrowser.selectedBrowser, null, async function () { await new Promise(resolve => { const iframe = content.document.createElement("iframe"); - content.document.body.appendChild(iframe); - iframe.contentWindow.addEventListener("load", () => { + iframe.addEventListener("load", () => { // Create a div element and append it to the iframe const div = content.document.createElement("div"); div.id = "in-frame"; @@ -30,6 +29,7 @@ add_task(async function () { frameContent.appendChild(div); resolve(); }); + content.document.body.appendChild(iframe); }); }); ok( diff --git a/devtools/client/jsonview/test/browser_jsonview_content_type.js b/devtools/client/jsonview/test/browser_jsonview_content_type.js @@ -20,8 +20,9 @@ const contentTypes = { invalid: [ "text/json", "text/hal+json", - "application/jsona", - "application/whatever+jsona", + // Disabled, bug 1862935 + //"application/jsona", + //"application/whatever+jsona", ], }; @@ -90,7 +91,8 @@ function testType(isValid, type, params = "") { const count = await getElementCount(".jsonPanelBox .treeTable .treeRow"); is(count, 3, "There must be expected number of rows"); }, - function () { + function (err) { + is(err, "Error: The JSON Viewer did not load."); ok( !isValid, "The JSON Viewer should only not load for invalid content types." diff --git a/devtools/client/jsonview/test/browser_jsonview_numbers.js b/devtools/client/jsonview/test/browser_jsonview_numbers.js @@ -11,7 +11,7 @@ add_task(async function () { "big": 1516340399466235648, "precise": 3.141592653589793238462643383279, "exp": 1e2 - }` + }`.replaceAll("\n", "") // ensure equality with actually loaded URI ); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { diff --git a/devtools/client/jsonview/test/head.js b/devtools/client/jsonview/test/head.js @@ -51,7 +51,7 @@ async function addJsonViewTab( ) { info("Adding a new JSON tab with URL: '" + url + "'"); const tabAdded = BrowserTestUtils.waitForNewTab(gBrowser, url); - const tabLoaded = addTab(url); + const tabLoaded = addTab(url, { waitForLoad: true }); // The `tabAdded` promise resolves when the JSON Viewer starts loading. // This is usually what we want, however, it never resolves for unrecognized diff --git a/devtools/server/actors/resources/parent-process-document-event.js b/devtools/server/actors/resources/parent-process-document-event.js @@ -99,7 +99,7 @@ class ParentProcessDocumentEventWatcher { // Ignore if we are still on the initial document, // as that's the navigation from it (about:blank) to the actual first location. // The target isn't created yet. - if (browsingContext.currentWindowGlobal.isInitialDocument) { + if (browsingContext.currentWindowGlobal.isUncommittedInitialDocument) { return; } diff --git a/devtools/server/actors/targets/window-global.js b/devtools/server/actors/targets/window-global.js @@ -1176,7 +1176,7 @@ class WindowGlobalTargetActor extends BaseTargetActor { docShell.QueryInterface(Ci.nsIWebNavigation); // don't include transient about:blank documents - if (docShell.document.isInitialDocument) { + if (docShell.document.isUncommittedInitialDocument) { return false; } diff --git a/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs b/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs @@ -68,7 +68,7 @@ export function getAddonIdForWindowGlobal(windowGlobal) { * is disabled. In case of client side target switching, the top browsing context * is debugged via a target actor that is being instantiated manually by the frontend. * And this target actor isn't created, nor managed by the watcher actor. - * @param {boolean} options.acceptInitialDocument + * @param {boolean} options.acceptUncommitedInitialDocument * By default, we ignore initial about:blank documents/WindowGlobals. * But some code cares about all the WindowGlobals, this flag allows to also accept them. * (Used by _validateWindowGlobal) @@ -236,34 +236,43 @@ function isPopupToDebug(browsingContext, sessionContext) { * @param {object} options * Optional arguments passed via a dictionary. * See `isBrowsingContextPartOfContext` jsdoc. + * @param {boolean} options.acceptUncommitedInitialDocument + * By default, we ignore initial uncommitted about:blank documents/WindowGlobals + * as they might be transient while the initial load is ongoing. + * But some code cares about all the WindowGlobals. */ function _validateWindowGlobal( windowGlobal, sessionContext, - { acceptInitialDocument } + { acceptUncommitedInitialDocument } ) { - // By default, before loading the actual document (even an about:blank document), - // we do load immediately "the initial about:blank document". - // This is expected by the spec. Typically when creating a new BrowsingContext/DocShell/iframe, - // we would have such transient initial document. - // `Document.isInitialDocument` helps identify this transient document, which - // we want to ignore as it would instantiate a very short lived target which + // When creating a new DocShell and before loading anything, we immediately + // create the initial about:blank. This is expected by the spec. + // Occasionally, multiple such transient empty documents may be created. + // These documents start out in the uncommitted state, but if a navigation to + // `about:blank` occurs, they can become permanent by being committed to, and + // a load event will be fired. + // + // `Document.isUncommittedInitialDocument` helps identify these transient documents, + // which we want to ignore as they would instantiate very short lived targets which // confuses many tests and triggers race conditions by spamming many targets. // - // We also ignore some other transient empty documents created while using `window.open()` - // When using this API with cross process loads, we may create up to three documents/WindowGlobals. - // We get a first initial about:blank document, and a second document created - // for moving the document in the right principal. - // The third document will be the actual document we expect to debug. - // The second document is an implementation artifact which ideally wouldn't exist - // and isn't expected by the spec. + // For example, when using `window.open()` with cross-process loads, we may create four + // such documents/WindowGlobals. We first get an initial about:blank document, + // a second one when moving to the right principal, a third one when moving to the correct + // process, and the fourth will is the actual document we expect to debug. + // The second and third documents are implementation artifacts that ideally + // wouldn't exist and aren't expected by the spec. These are implementation + // details that may have already changed or could change in the future. + // // Note that `window.print` and print preview are using `window.open` and are going through this. // - // WindowGlobalParent will have `isInitialDocument` attribute, while we have to go through the Document for WindowGlobalChild. - const isInitialDocument = - windowGlobal.isInitialDocument || - windowGlobal.browsingContext.window?.document.isInitialDocument; - if (isInitialDocument && !acceptInitialDocument) { + // WindowGlobalParent will have `isUncommittedInitialDocument` attribute, while we have to go + // through the Document for WindowGlobalChild. + const isUncommittedInitialDocument = + windowGlobal.isUncommittedInitialDocument || + windowGlobal.browsingContext.window?.document.isUncommittedInitialDocument; + if (isUncommittedInitialDocument && !acceptUncommitedInitialDocument) { return false; } diff --git a/devtools/server/connectors/js-process-actor/target-watchers/window-global.sys.mjs b/devtools/server/connectors/js-process-actor/target-watchers/window-global.sys.mjs @@ -111,11 +111,11 @@ function createTargetsForWatcher(watcherDataObject, isProcessActorStartup) { // // We want to avoid creating transient targets for initial about blank when a new WindowGlobal // just get created as it will most likely navigate away just after and confuse the frontend with short lived target. - const acceptInitialDocument = !isProcessActorStartup; + const acceptUncommitedInitialDocument = !isProcessActorStartup; if ( lazy.isWindowGlobalPartOfContext(windowGlobalChild, sessionContext, { - acceptInitialDocument, + acceptUncommitedInitialDocument, }) ) { createWindowGlobalTargetActor(watcherDataObject, windowGlobalChild); @@ -479,6 +479,16 @@ function observe(subject, topic) { topic == "content-document-global-created" || topic == "chrome-document-global-created" ) { + if (subject.isUncommittedInitialDocument) { + // If this is the initial document, it might be a short-lived transient one, and + // onWindowGlobalCreated will ignore such documents. If we receive a load + // event, the document has been committed to, and we know the initial document + // will persist. In that case, we need to call onWindowGlobalCreated again. + subject.addEventListener("DOMContentLoaded", handleEvent, { + capture: true, + once: true, + }); + } onWindowGlobalCreated(subject); } else if (topic == "inner-window-destroyed") { const innerWindowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; @@ -575,6 +585,20 @@ function handleEvent({ type, persisted, target }) { // if we navigate back to it, the next DOMWindowCreated won't create a new target for it. onWindowGlobalDestroyed(target.defaultView.windowGlobalChild.innerWindowId); } + + if (type == "DOMContentLoaded") { + if (!target.isInitialDocument) { + return; + } + + // This is similar to initial-document-element-inserted. onWindowGlobalCreated likely + // ignored the earlier call for this document because it was the uncommitted initial one. Now + // that we got a load event we know that the document is not transient but the destination of a + // load. Its state will have changed and onWindowGlobalCreated won't skip it anymore. + onWindowGlobalCreated(target.defaultView, { + ignoreIfExisting: true, + }); + } } /** diff --git a/devtools/shared/dom-helpers.js b/devtools/shared/dom-helpers.js @@ -38,10 +38,15 @@ exports.DOMHelpers = { Services.tm.dispatchToMainThread(callback); } }; + // The initial document is special in that, while uncommitted, its readyState + // will already be "complete" even though the document is still loading. + // It is either transient and will be replaced by a different document, + // or it will be committed to and a load event will be fired for it. if ( (win.document.readyState == "complete" || win.document.readyState == "interactive") && - win.location.href == targetURL + win.location.href == targetURL && + !win.document.isUncommittedInitialDocument ) { Services.tm.dispatchToMainThread(callback); } else { diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp @@ -385,6 +385,8 @@ already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached( MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType); fields.Get<IDX_OpenerId>() = aOpener->Id(); fields.Get<IDX_HadOriginalOpener>() = true; + fields.Get<IDX_MessageManagerGroup>() = + aOpener->Top()->GetMessageManagerGroup(); if (aType == Type::Chrome && !aParent) { // See SetOpener for why we do this inheritance. diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp @@ -67,6 +67,8 @@ #include "nsIXPConnect.h" #include "nsImportModule.h" #include "UnitTransforms.h" +#include "nsIOpenWindowInfo.h" +#include "nsOpenWindowInfo.h" using namespace mozilla::ipc; @@ -2301,6 +2303,10 @@ nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() { nsCOMPtr<nsIPrincipal> initialPrincipal = NullPrincipal::Create(target->OriginAttributesRef()); + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + openWindowInfo->mPrincipalToInheritForAboutBlank = initialPrincipal; + openWindowInfo->mPartitionedPrincipalToInheritForAboutBlank = + initialPrincipal; WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp @@ -190,6 +190,7 @@ #include "IHistory.h" #include "IUrlClassifierUITelemetry.h" +#include "nsAboutProtocolUtils.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsBrowserStatusFilter.h" @@ -200,6 +201,7 @@ #include "nsContentSecurityUtils.h" #include "nsContentUtils.h" #include "nsCURILoader.h" +#include "nsDocElementCreatedNotificationRunner.h" #include "nsDocShellCID.h" #include "nsDocShellEditorData.h" #include "nsDocShellEnumerator.h" @@ -253,6 +255,8 @@ #include "nsDocShellTelemetryUtils.h" +#include "nsIOpenWindowInfo.h" + #ifdef MOZ_PLACES # include "mozilla/places/nsFaviconService.h" # include "mozIPlacesPendingOperation.h" @@ -273,6 +277,7 @@ using mozilla::ipc::Endpoint; #define REFRESH_REDIRECT_TIMER 15000 static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); #define LOGCHARSETMENU(args) \ MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args) @@ -281,7 +286,6 @@ static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); unsigned long nsDocShell::gNumberOfDocShells = 0; static uint64_t gDocshellIDCounter = 0; -static mozilla::LazyLogModule gDocShellLog("nsDocShell"); static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( "DocShellAndDOMWindowLeak"); #endif @@ -369,6 +373,7 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, mSavingOldViewer(false), mInvisible(false), mHasLoadedNonBlankURI(false), + mHasStartedLoadingOtherThanInitialBlankURI(false), mBlankTiming(false), mTitleValidForCurrentURI(false), mWillChangeProcess(false), @@ -435,12 +440,30 @@ nsDocShell::~nsDocShell() { #endif } -bool nsDocShell::Initialize() { +nsresult nsDocShell::InitWindow(nsIWidget* aParentWidget, int32_t aX, + int32_t aY, int32_t aWidth, int32_t aHeight, + nsIOpenWindowInfo* aOpenWindowInfo, + mozilla::dom::WindowGlobalChild* aWindowActor) { + SetParentWidget(aParentWidget); + SetPositionAndSize(aX, aY, aWidth, aHeight, 0); + NS_ENSURE_TRUE(Initialize(aOpenWindowInfo, aWindowActor), NS_ERROR_FAILURE); + + return NS_OK; +} + +bool nsDocShell::Initialize(nsIOpenWindowInfo* aOpenWindowInfo, + mozilla::dom::WindowGlobalChild* aWindowActor) { if (mInitialized) { // We've already been initialized. + MOZ_ASSERT(!aOpenWindowInfo, + "Tried to reinitialize with override principal"); + MOZ_ASSERT(!aWindowActor, "Tried to reinitialize with a window actor"); return true; } + MOZ_ASSERT(aOpenWindowInfo, + "Must have openwindowinfo if not already initialized."); + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); @@ -451,13 +474,16 @@ bool nsDocShell::Initialize() { Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", mDisableMetaRefreshWhenInactive); + bool succeeded = + NS_SUCCEEDED(CreateInitialDocumentViewer(aOpenWindowInfo, aWindowActor)); + 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; + return succeeded; } /* static */ @@ -610,8 +636,7 @@ nsDocShell::GetInterface(const nsIID& aIID, void** aSink) { aIID.Equals(NS_GET_IID(nsIDOMWindow))) && NS_SUCCEEDED(EnsureScriptEnvironment())) { return mScriptGlobal->QueryInterface(aIID, aSink); - } else if (aIID.Equals(NS_GET_IID(Document)) && - NS_SUCCEEDED(EnsureDocumentViewer())) { + } else if (aIID.Equals(NS_GET_IID(Document)) && VerifyDocumentViewer()) { RefPtr<Document> doc = mDocumentViewer->GetDocument(); doc.forget(aSink); return *aSink ? NS_OK : NS_NOINTERFACE; @@ -1582,15 +1607,18 @@ bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, mLastOpenedURI = aURI; #endif - if (!NS_IsAboutBlank(mCurrentURI)) { + if (!NS_IsAboutBlankAllowQueryAndFragment(mCurrentURI)) { mHasLoadedNonBlankURI = true; } - // Don't fire onLocationChange when creating a subframe's initial about:blank + // Don't fire onLocationChange when creating a the initial about:blank // document, as this can happen when it's not safe for us to run script. - if (aIsInitialAboutBlank && !mHasLoadedNonBlankURI && - !mBrowsingContext->IsTop()) { - MOZ_ASSERT(!aRequest && aLocationFlags == 0); + // Note that if this initial about:blank isn't immediately navigated + // away from, the onLocationChange will be fired as part of committing + // to keeping the initial about:blank as the actual first initial + // navigation destination. + if (aIsInitialAboutBlank) { + MOZ_ASSERT(!mHasLoadedNonBlankURI && !aRequest && aLocationFlags == 0); return false; } @@ -1828,6 +1856,10 @@ nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) { return NS_OK; } +bool nsDocShell::HasStartedLoadingOtherThanInitialBlankURI() { + return mHasStartedLoadingOtherThanInitialBlankURI; +} + NS_IMETHODIMP nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) { NS_ENSURE_ARG_POINTER(aUseRemoteTabs); @@ -2452,7 +2484,17 @@ void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) { mInitialClientSource->DocShellExecutionReady(this); // Next, check to see if the parent is controlled. + MaybeInheritController(mInitialClientSource.get(), principal); +} + +void nsDocShell::MaybeInheritController( + mozilla::dom::ClientSource* aClientSource, nsIPrincipal* aPrincipal) { nsCOMPtr<nsIDocShell> parent = GetInProcessParentDocshell(); + if (!parent) { + if (RefPtr<BrowsingContext> opener = mBrowsingContext->GetOpener()) { + parent = opener->GetDocShell(); + } + } nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; nsPIDOMWindowInner* parentInner = parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; @@ -2467,11 +2509,11 @@ void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) { // is not permitted to control for some reason. Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController()); if (controller.isNothing() || - !ServiceWorkerAllowedToControlWindow(principal, uri)) { + !ServiceWorkerAllowedToControlWindow(aPrincipal, uri)) { return; } - mInitialClientSource->InheritController(controller.ref()); + aClientSource->InheritController(controller.ref()); } Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const { @@ -3004,7 +3046,7 @@ nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() { } Document* nsDocShell::GetDocument() { - NS_ENSURE_SUCCESS(EnsureDocumentViewer(), nullptr); + NS_ENSURE_TRUE(VerifyDocumentViewer(), nullptr); return mDocumentViewer->GetDocument(); } @@ -3926,6 +3968,12 @@ nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI, loadState->SetLoadingSessionHistoryInfo( MakeUnique<LoadingSessionHistoryInfo>(*mLoadingEntry)); } + + // Prevent initial about:blank handling, as it's likely irrelevant and + // keeps us from needing to change GeckoView / NavigationDelegateTest. + // It also makes the load more consistent with non-about-blank cases. + loadState->ProhibitInitialAboutBlankHandling(); + return InternalLoad(loadState); } @@ -4015,6 +4063,10 @@ nsresult nsDocShell::ReloadNavigable( bool okToUnload = true; MOZ_TRY(viewer->PermitUnload(&okToUnload)); + if (mIsBeingDestroyed) { + // unload handler destroyed this docshell. + return NS_ERROR_NOT_AVAILABLE; + } if (!okToUnload) { return NS_OK; } @@ -4254,7 +4306,7 @@ nsDocShell::Stop(uint32_t aStopFlags) { nsresult nsDocShell::StopInternal( uint32_t aStopFlags, UnsetOngoingNavigation aUnsetOngoingNavigation) { RefPtr kungFuDeathGrip = this; - if (RefPtr<Document> doc = GetDocument(); + if (RefPtr<Document> doc = GetExtantDocument(); aUnsetOngoingNavigation == UnsetOngoingNavigation::Yes && doc && !doc->ShouldIgnoreOpens() && mOngoingNavigation == Some(OngoingNavigation::NavigationID)) { @@ -4324,7 +4376,7 @@ nsresult nsDocShell::StopInternal( NS_IMETHODIMP nsDocShell::GetDocument(Document** aDocument) { NS_ENSURE_ARG_POINTER(aDocument); - NS_ENSURE_SUCCESS(EnsureDocumentViewer(), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(VerifyDocumentViewer(), NS_ERROR_FAILURE); RefPtr<Document> doc = mDocumentViewer->GetDocument(); if (!doc) { @@ -4398,6 +4450,12 @@ nsDocShell::LoadPageAsViewSource(nsIDocShell* aOtherDocShell, loadState->SetOriginalURI(nullptr); loadState->SetResultPrincipalURI(nullptr); + // Initial about:blank handling is probably irrelevant, but newURI shouldn't + // anyway be about:blank. Otherwise we should prohibit initial about blank + // handling. + MOZ_ASSERT(!NS_IsAboutBlankAllowQueryAndFragment(newURI), + "We only expect view-source:// URIs"); + return InternalLoad(loadState, Some(cacheKey)); } @@ -4486,15 +4544,6 @@ bool nsDocShell::FillLoadStateFromCurrentEntry( //***************************************************************************** NS_IMETHODIMP -nsDocShell::InitWindow(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 @@ -6543,36 +6592,47 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, // nsDocShell: Content Viewer Management //***************************************************************************** -nsresult nsDocShell::EnsureDocumentViewer() { +bool nsDocShell::VerifyDocumentViewer() { if (mDocumentViewer) { - return NS_OK; + return true; } if (mIsBeingDestroyed) { - return NS_ERROR_FAILURE; + return false; } + // The viewer should be created during docshell initialization. So unless + // we're being destroyed, there always needs to be a viewer. + MOZ_ASSERT_UNREACHABLE("The content viewer should've been created eagerly."); + return false; +} - nsCOMPtr<nsIPolicyContainer> policyContainerToInheritForAboutBlank; - 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(); - policyContainerToInheritForAboutBlank = - parentElement->GetPolicyContainer(); - } - } +nsresult nsDocShell::CreateInitialDocumentViewer( + nsIOpenWindowInfo* aOpenWindowInfo, + mozilla::dom::WindowGlobalChild* aWindowActor) { + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; } + MOZ_ASSERT(!mDocumentViewer); + MOZ_ASSERT(aOpenWindowInfo, "Why don't we have openwindowinfo?"); + + // Previously, CreateDocumentViewerForActor would've used the actor's + // principal. + MOZ_ASSERT_IF(aWindowActor, + aWindowActor->DocumentPrincipal() == + aOpenWindowInfo->PrincipalToInheritForAboutBlank()); + MOZ_ASSERT_IF( + aWindowActor, + aWindowActor->DocumentPrincipal() == + aOpenWindowInfo->PartitionedPrincipalToInheritForAboutBlank()); nsresult rv = CreateAboutBlankDocumentViewer( - principal, partitionedPrincipal, policyContainerToInheritForAboutBlank, - baseURI, - /* aIsInitialDocument */ true); + aOpenWindowInfo->PrincipalToInheritForAboutBlank(), + aOpenWindowInfo->PartitionedPrincipalToInheritForAboutBlank(), + aOpenWindowInfo->PolicyContainerToInheritForAboutBlank(), + aOpenWindowInfo->BaseUriToInheritForAboutBlank(), + /* aIsInitialDocument */ true, + aOpenWindowInfo->CoepToInheritForAboutBlank(), + /* aTryToSaveOldPresentation */ true, + /* aCheckPermitUnload */ true, aWindowActor); NS_ENSURE_STATE(mDocumentViewer); @@ -6583,7 +6643,7 @@ nsresult nsDocShell::EnsureDocumentViewer() { "succeeded!"); MOZ_ASSERT(doc->IsInitialDocument(), "Document should be initial document"); - // Documents created using EnsureDocumentViewer may be transient + // Documents created using CreateInitialDocumentViewer 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 @@ -6642,8 +6702,14 @@ nsresult nsDocShell::CreateAboutBlankDocumentViewer( if (aPrincipal && !aPrincipal->IsSystemPrincipal() && mItemType != typeChrome) { - MOZ_ASSERT(aPrincipal->OriginAttributesRef() == - mBrowsingContext->OriginAttributesRef()); + if (GetIsTopLevelContentDocShell()) { + // Bug 1948216 tracks having a FPD for top-level initial about:blank + MOZ_ASSERT(aPrincipal->OriginAttributesRef().EqualsIgnoringFPD( + mBrowsingContext->OriginAttributesRef())); + } else { + MOZ_ASSERT(aPrincipal->OriginAttributesRef() == + mBrowsingContext->OriginAttributesRef()); + } } // Make sure timing is created. But first record whether we had it @@ -6663,7 +6729,10 @@ nsresult nsDocShell::CreateAboutBlankDocumentViewer( bool okToUnload; rv = mDocumentViewer->PermitUnload(&okToUnload); - + if (mIsBeingDestroyed) { + // unload handler destroyed this docshell. + return NS_ERROR_NOT_AVAILABLE; + } if (NS_SUCCEEDED(rv) && !okToUnload) { // The user chose not to unload the page, interrupt the load. MaybeResetInitTiming(toBeReset); @@ -6733,7 +6802,7 @@ nsresult nsDocShell::CreateAboutBlankDocumentViewer( partitionedPrincipal = aPartitionedPrincipal; } - // We cannot get the foreign partitioned prinicpal for the initial + // We cannot get the foreign partitioned principal for the initial // about:blank page. So, we change to check if we need to use the // partitioned principal for the service worker here. MaybeCreateInitialClientSource( @@ -6757,9 +6826,21 @@ nsresult nsDocShell::CreateAboutBlankDocumentViewer( policyContainerToInherit->InitFromOther( PolicyContainer::Cast(aPolicyContainer)); blankDoc->SetPolicyContainer(policyContainerToInherit); + nsIContentSecurityPolicy* csp = + PolicyContainer::GetCSP(policyContainerToInherit); + if (!csp) { + csp = new nsCSPContext(); + policyContainerToInherit->SetCSP(csp); + }; + nsresult rv = csp->SetRequestContextWithDocument(blankDoc); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } - blankDoc->SetIsInitialDocument(aIsInitialDocument); + blankDoc->SetInitialStatus( + aIsInitialDocument ? Document::InitialStatus::IsInitialUncommitted + : Document::InitialStatus::NeverInitial); blankDoc->SetEmbedderPolicy(aCOEP); @@ -6800,7 +6881,7 @@ nsresult nsDocShell::CreateAboutBlankDocumentViewer( SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, /* aFireLocationChange */ true, - /* aIsInitialAboutBlank */ true, + /* aIsInitialAboutBlank */ aIsInitialDocument, /* aLocationFlags */ 0); rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; } @@ -6835,36 +6916,6 @@ nsDocShell::CreateAboutBlankDocumentViewer( /* aIsInitialDocument */ false); } -nsresult nsDocShell::CreateDocumentViewerForActor( - WindowGlobalChild* aWindowActor) { - MOZ_ASSERT(aWindowActor); - - // FIXME: WindowGlobalChild should provide the PartitionedPrincipal. - // FIXME: We may want to support non-initial documents here. - nsresult rv = CreateAboutBlankDocumentViewer( - aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(), - /* aPolicyContinaer */ nullptr, - /* aBaseURI */ nullptr, - /* aIsInitialDocument */ true, - /* aCOEP */ Nothing(), - /* aTryToSaveOldPresentation */ true, - /* aCheckPermitUnload */ true, aWindowActor); -#ifdef DEBUG - if (NS_SUCCEEDED(rv)) { - RefPtr<Document> doc(GetDocument()); - MOZ_ASSERT( - doc, - "Should have a document if CreateAboutBlankDocumentViewer succeeded"); - MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(), - "New document should be in the same global as our actor"); - MOZ_ASSERT(doc->IsInitialDocument(), - "New document should be an initial document"); - } -#endif - - return rv; -} - bool nsDocShell::CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest, Document* aNewDocument, @@ -7173,8 +7224,7 @@ nsDocShell::BeginRestore(nsIDocumentViewer* aDocumentViewer, bool aTop) { nsresult rv; if (!aDocumentViewer) { - rv = EnsureDocumentViewer(); - NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(VerifyDocumentViewer(), NS_ERROR_FAILURE); aDocumentViewer = mDocumentViewer; } @@ -8653,6 +8703,15 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, return false; } + if (GetExtantDocument() && + GetExtantDocument()->IsUncommittedInitialDocument()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell::IsSameDocumentNavigation %p false, document is " + "uncommitted initial", + this)); + return false; + } + nsCOMPtr<nsIURI> currentURI = mCurrentURI; nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash); @@ -9760,6 +9819,10 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, &okToUnload); } else { rv = mDocumentViewer->PermitUnload(&okToUnload); + if (mIsBeingDestroyed) { + // unload handler destroyed this docshell. + return NS_ERROR_NOT_AVAILABLE; + } } if (NS_SUCCEEDED(rv) && !okToUnload) { @@ -10065,8 +10128,7 @@ nsIPrincipal* nsDocShell::GetInheritedPrincipal( // 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. - EnsureDocumentViewer(); - if (!mDocumentViewer) { + if (!VerifyDocumentViewer()) { return nullptr; } document = mDocumentViewer->GetDocument(); @@ -10479,11 +10541,39 @@ nsIPrincipal* nsDocShell::GetInheritedPrincipal( } bool nsDocShell::IsAboutBlankLoadOntoInitialAboutBlank( - nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) { - return NS_IsAboutBlankAllowQueryAndFragment(aURI) && aInheritPrincipal && - (aPrincipalToInherit == GetInheritedPrincipal(false)) && - (!mDocumentViewer || !mDocumentViewer->GetDocument() || - mDocumentViewer->GetDocument()->IsInitialDocument()); + nsIURI* aURI, nsIPrincipal* aPrincipalToInherit) { + MOZ_ASSERT(mDocumentViewer); + bool ret = !mHasStartedLoadingOtherThanInitialBlankURI && + mDocumentViewer->GetDocument()->IsUncommittedInitialDocument() && + NS_IsAboutBlankAllowQueryAndFragment(aURI); + if (ret && !aPrincipalToInherit) { + MOZ_ASSERT( + mDocumentViewer->GetDocument()->NodePrincipal()->GetIsNullPrincipal(), + "Load looks like first load but does not want principal inheritance."); + } + return ret; +} + +void nsDocShell::UnsuppressPaintingIfNoNavigationAwayFromAboutBlank( + mozilla::PresShell* aPresShell) { + if (mHasStartedLoadingOtherThanInitialBlankURI || !mDocumentViewer) { + return; + } + Document* doc = mDocumentViewer->GetDocument(); + if (!doc || !doc->IsInitialDocument()) { + return; + } + if (mDocumentViewer->GetPresShell() != aPresShell) { + return; + } + // Our surroundings appear to remain in the same state + // as before posting the runnable. + aPresShell->UnsuppressPainting(); + // The content viewer's mPresShell could have been removed now, see bug + // 378682/421432 + if ((aPresShell = mDocumentViewer->GetPresShell())) { + aPresShell->LoadComplete(); + } } nsresult nsDocShell::PerformTrustedTypesPreNavigationCheck( @@ -10742,9 +10832,13 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState, inheritPrincipal = inheritAttrs && !uri->SchemeIs("data"); } + MOZ_ASSERT_IF(NS_IsAboutBlankAllowQueryAndFragment(uri) && + aLoadState->PrincipalToInherit(), + inheritPrincipal); // See https://bugzilla.mozilla.org/show_bug.cgi?id=1736570 const bool isAboutBlankLoadOntoInitialAboutBlank = - IsAboutBlankLoadOntoInitialAboutBlank(uri, inheritPrincipal, + !aLoadState->IsInitialAboutBlankHandlingProhibited() && + IsAboutBlankLoadOntoInitialAboutBlank(uri, aLoadState->PrincipalToInherit()); // FIXME We still have a ton of codepaths that don't pass through @@ -10780,6 +10874,7 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState, outRequest.forget(aRequest); } + mHasStartedLoadingOtherThanInitialBlankURI = true; return OpenRedirectedChannel(aLoadState); } @@ -10882,28 +10977,11 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState, RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext(); if (isAboutBlankLoadOntoInitialAboutBlank) { - // Match the DocumentChannel case where the default for third-partiness - // differs from the default in LoadInfo construction here. - // toolkit/components/antitracking/test/browser/browser_aboutblank.js - // fails without this. - BrowsingContext* top = mBrowsingContext->Top(); - if (top == mBrowsingContext) { - // If we're at the top, this must be a window.open()ed - // window, and we can't be third-party relative to ourselves. - loadInfo->SetIsThirdPartyContextToTopWindow(false); - } else { - if (Document* topDoc = top->GetDocument()) { - bool thirdParty = false; - (void)topDoc->GetPrincipal()->IsThirdPartyPrincipal( - aLoadState->PrincipalToInherit(), &thirdParty); - loadInfo->SetIsThirdPartyContextToTopWindow(thirdParty); - } else { - // If top is in a different process, we have to be third-party relative - // to it. - loadInfo->SetIsThirdPartyContextToTopWindow(true); - } - } + // Stay on the eagerly created document and adjust it to match what we would + // be loading. + return CompleteInitialAboutBlankLoad(aLoadState, loadInfo); } + mHasStartedLoadingOtherThanInitialBlankURI = true; if (mLoadType != LOAD_ERROR_PAGE && context && context->IsInProcess()) { if (context->HasValidTransientUserGestureActivation()) { @@ -11028,6 +11106,191 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState, return OpenInitializedChannel(channel, uriLoader, openFlags); } +nsresult nsDocShell::CompleteInitialAboutBlankLoad( + nsDocShellLoadState* aLoadState, nsILoadInfo* aLoadInfo) { + nsresult rv; + // Match the DocumentChannel case where the default for third-partiness + // differs from the default in LoadInfo construction here. + // toolkit/components/antitracking/test/browser/browser_aboutblank.js + // fails without this. + BrowsingContext* top = mBrowsingContext->Top(); + if (top == mBrowsingContext) { + // If we're at the top, this must be a window.open()ed + // window, and we can't be third-party relative to ourselves. + aLoadInfo->SetIsThirdPartyContextToTopWindow(false); + } else { + if (Document* topDoc = top->GetDocument()) { + bool thirdParty = false; + (void)topDoc->GetPrincipal()->IsThirdPartyPrincipal( + aLoadState->PrincipalToInherit(), &thirdParty); + aLoadInfo->SetIsThirdPartyContextToTopWindow(thirdParty); + } else { + // If top is in a different process, we have to be third-party relative + // to it. + aLoadInfo->SetIsThirdPartyContextToTopWindow(true); + } + } + + if (!mDocumentViewer) { + MOZ_ASSERT(false, "How did the viewer go away?"); + return NS_ERROR_FAILURE; + } + RefPtr<Document> doc = mDocumentViewer->GetDocument(); + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::DoURILoad sync about:blank onto initial " + "about:blank. Document[%p]\n", + this, doc.get())); + if (!doc) { + MOZ_ASSERT(false, "How did the document go away?"); + return NS_ERROR_FAILURE; + } + + const bool principalMissmatch = + aLoadState->PrincipalToInherit() && + !aLoadState->PrincipalToInherit()->Equals(doc->GetPrincipal()); + MOZ_ASSERT_IF(!aLoadState->PrincipalToInherit(), + doc->GetPrincipal()->GetIsNullPrincipal()); + + // The channel would sandbox aLoadState->PrincipalToInherit(). Even if + // the document already has a null principal, we don't know if it's the right + // sandboxed one. So be safe and clobber. + const uint32_t sandboxFlags = + mBrowsingContext->GetHasLoadedNonInitialDocument() + ? mBrowsingContext->GetSandboxFlags() + : mBrowsingContext->GetInitialSandboxFlags(); + const bool shouldBeSandboxed = sandboxFlags & SANDBOXED_ORIGIN; + MOZ_ASSERT_IF(shouldBeSandboxed, aLoadState->PrincipalToInherit()); + + // Clobber document before completing the synchronous load if it doesn't have + // the right principal (bug 1979032) + if (principalMissmatch || shouldBeSandboxed) { + nsIPrincipal* principal = aLoadState->PrincipalToInherit(); + nsIPrincipal* partitionedPrincipal = + aLoadState->PartitionedPrincipalToInherit(); + if (!partitionedPrincipal) { + partitionedPrincipal = principal; + } + + // This will sandbox the principals as needed + rv = CreateAboutBlankDocumentViewer( + principal, partitionedPrincipal, aLoadState->PolicyContainer(), + doc->GetDocBaseURI(), /* aIsInitialDocument */ true); + NS_ENSURE_SUCCESS(rv, rv); + + doc = mDocumentViewer->GetDocument(); + MOZ_ASSERT(doc); + MOZ_LOG(gDocShellLog, LogLevel::Warning, + ("nsDocShell[%p] sync about:blank principals don't match, create " + "new document. Document[%p] \n", + this, doc.get())); + } + + MOZ_ASSERT(doc->IsInitialDocument(), + "How come the doc is no longer the initial one?"); + + MOZ_ASSERT(doc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE); + MOZ_ASSERT(!mIsLoadingDocument); + + doc->ApplyCspFromLoadInfo(aLoadInfo); + doc->ApplySettingsFromCSP(false); + doc->RecomputeResistFingerprinting(); + + rv = doc->GetWindowContext()->SetIsOriginalFrameSource( + aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC)); + NS_ENSURE_SUCCESS(rv, rv); + + nsPIDOMWindowInner* innerWindow = doc->GetInnerWindow(); + if (innerWindow) { + mozilla::dom::ClientSource* clientSource = + nsGlobalWindowInner::Cast(innerWindow)->GetClientSource(); + // See if we don't have a controller but the parent has gained a + // controller. + if (clientSource && clientSource->GetController().isNothing()) { + MaybeInheritController( + clientSource, + StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker( + this) + ? doc->PartitionedPrincipal() + : doc->GetPrincipal()); + } + } + + // Get the load event fired for the initial about:blank without starting + // a real load from a channel. We still need a channel object even though + // we don't care about reading from the channel. + nsCOMPtr<nsIChannel> aboutBlankChannel; + rv = NS_NewChannelInternal(getter_AddRefs(aboutBlankChannel), + aLoadState->URI(), aLoadInfo, nullptr, mLoadGroup, + nullptr, nsIChannel::LOAD_DOCUMENT_URI); + if (NS_FAILED(rv)) { + return rv; + } + if (!aboutBlankChannel) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!mIsLoadingDocument); + MOZ_ASSERT(!mDocumentRequest); + + // Call OnStartRequest so that nsDocLoader sets mIsLoadingDocument and fire + // state start + OnStartRequest(aboutBlankChannel); + + MOZ_ASSERT(mIsLoadingDocument); + MOZ_ASSERT(mDocumentRequest == aboutBlankChannel); + MOZ_ASSERT(!doc->InitialAboutBlankLoadCompleting()); + + doc->BeginInitialAboutBlankLoadCompleting(aboutBlankChannel); + auto resetLoadCompleting = + MakeScopeExit([&] { doc->EndInitialAboutBlankLoadCompleting(); }); + + mCurrentURI = aLoadState->URI(); + doc->SetDocumentURI(aLoadState->URI()); + + // Normal documents fire the location change at content viewer creation. + // The initial about:blank does not do that at content viewer creation, + // so that the UI isn't bothered about the initial about:blank if there's + // immediate navigation away. However, now that the initial about:blank is + // going to remain in this docshell, we need to let to the UI know about it + // (at least in the top-level case). + FireOnLocationChange(this, aboutBlankChannel, aLoadState->URI(), 0); + + if (SessionHistoryInParent()) { + MoveLoadingToActiveEntry(false, 0, nullptr); + } + + doc->BeginLoad(); + + nsContentUtils::AddScriptRunner( + new nsDocElementCreatedNotificationRunner(doc)); + // When scripts are not blocked (are they ever blocked here?), the runnable + // runs immediately, so let's check if this docshell got destroyed or the + // document got swapped. Unclear if this ever happens; this is a defensive + // check. + if (mIsBeingDestroyed || !mDocumentViewer || + doc != mDocumentViewer->GetDocument()) { + return NS_OK; + } + + // Initialize the presShell here in the window.open() case. + RefPtr<PresShell> presShell = doc->GetPresShell(); + if (presShell && !presShell->DidInitialize()) { + rv = presShell->Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + } + + doc->SetScrollToRef(doc->GetDocumentURI()); + + OnStopRequest(aboutBlankChannel, NS_OK); + + doc->EndLoad(); + // Can't assert any postcondition, because the load event + // handler may have started loading something new in this + // docshell. + + return NS_OK; +} + static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure, const char* aFromRawSegment, uint32_t aToOffset, uint32_t aCount, @@ -13950,6 +14213,11 @@ nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) { } } + // Prohibit initial about:blank handling e.g. for when a cross-process + // iframe loads about:blank and becomes same-process. Conceptually, the + // browsing context isn't new despite the docshell being newly created. + aLoadState->ProhibitInitialAboutBlankHandling(); + self->InternalLoad(aLoadState); if (aLoadState->GetOriginalURIString().isSome()) { diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h @@ -46,10 +46,12 @@ class HTMLEditor; class ObservedDocShell; class ScrollContainerFrame; enum class TaskCategory; +class PresShell; namespace dom { class ClientInfo; class ClientSource; class EventTarget; +class WindowGlobalChild; enum class NavigationHistoryBehavior : uint8_t; struct NavigationAPIMethodTracker; class SessionHistoryInfo; @@ -76,6 +78,7 @@ class nsIURILoader; class nsIWebBrowserFind; class nsIWidget; class nsIReferrerInfo; +class nsIOpenWindowInfo; class nsBrowserStatusFilter; class nsCommandManager; @@ -187,7 +190,13 @@ class nsDocShell final : public nsDocLoader, mozilla::dom::BrowsingContext* aBrowsingContext, uint64_t aContentWindowID = 0); - bool Initialize(); + bool Initialize(nsIOpenWindowInfo* aOpenWindowInfo, + mozilla::dom::WindowGlobalChild* aWindowActor); + + nsresult InitWindow(nsIWidget* aParentWidget, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight, + nsIOpenWindowInfo* aOpenWindowInfo, + mozilla::dom::WindowGlobalChild* aWindowActor); NS_IMETHOD Stop() override { // Need this here because otherwise nsIWebNavigation::Stop @@ -400,7 +409,8 @@ class nsDocShell final : public nsDocLoader, /** * Loads the given URI. See comments on nsDocShellLoadState members for more * information on information used. - * `aCacheKey` gets passed to DoURILoad call. + * + * @param aCacheKey gets passed to DoURILoad call. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult InternalLoad( @@ -414,11 +424,6 @@ class nsDocShell final : public nsDocLoader, void SetWillChangeProcess() { mWillChangeProcess = true; } bool WillChangeProcess() { return mWillChangeProcess; } - // Create a content viewer within this nsDocShell for the given - // `WindowGlobalChild` actor. - nsresult CreateDocumentViewerForActor( - 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 @@ -576,9 +581,16 @@ class nsDocShell final : public nsDocLoader, // Content Viewer Management // - nsresult EnsureDocumentViewer(); + // Assert the document viewer exists or we are being destroyed + // and return true if a viewer exists. + bool VerifyDocumentViewer(); + void DestroyDocumentViewer(); + nsresult CreateInitialDocumentViewer( + nsIOpenWindowInfo* aOpenWindowInfo = nullptr, + mozilla::dom::WindowGlobalChild* aWindowActor = nullptr); + // aPrincipal can be passed in if the caller wants. If null is // passed in, the about:blank principal will end up being used. // aPolicyContainer, if any, will be used for the new about:blank load. @@ -665,9 +677,13 @@ class nsDocShell final : public nsDocLoader, public: bool IsAboutBlankLoadOntoInitialAboutBlank(nsIURI* aURI, - bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit); + void UnsuppressPaintingIfNoNavigationAwayFromAboutBlank( + mozilla::PresShell* aPresShell); + + bool HasStartedLoadingOtherThanInitialBlankURI(); + private: // // URI Load @@ -707,6 +723,9 @@ class nsDocShell final : public nsDocLoader, MOZ_CAN_RUN_SCRIPT nsresult PerformTrustedTypesPreNavigationCheck( nsDocShellLoadState* aLoadState, nsGlobalWindowInner* aWindow) const; + nsresult CompleteInitialAboutBlankLoad(nsDocShellLoadState* aLoadState, + nsILoadInfo* aLoadInfo); + static nsresult AddHeadersToChannel(nsIInputStream* aHeadersData, nsIChannel* aChannel); @@ -951,6 +970,10 @@ class nsDocShell final : public nsDocLoader, // its real document and window are created. void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr); + // Try to inherit the controller from same-origin parent. + void MaybeInheritController(mozilla::dom::ClientSource* aClientSource, + nsIPrincipal* aPrincipal); + // 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 @@ -1420,8 +1443,13 @@ class nsDocShell final : public nsDocLoader, bool mSavingOldViewer : 1; bool mInvisible : 1; + + // There has been an OnStartRequest for a non-about:blank URI bool mHasLoadedNonBlankURI : 1; + // There has been a DoURILoad that wasn't the initial commit to about:blank + bool mHasStartedLoadingOtherThanInitialBlankURI : 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() diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp @@ -118,6 +118,8 @@ nsDocShellLoadState::nsDocShellLoadState( mUnstrippedURI = aLoadState.UnstrippedURI(); mRemoteTypeOverride = aLoadState.RemoteTypeOverride(); mIsCaptivePortalTab = aLoadState.IsCaptivePortalTab(); + mIsInitialAboutBlankHandlingProhibited = + aLoadState.IsInitialAboutBlankHandlingProhibited(); if (aLoadState.NavigationAPIState()) { mNavigationAPIState = MakeRefPtr<nsStructuredCloneContainer>(); @@ -222,7 +224,9 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther) mSchemelessInput(aOther.mSchemelessInput), mForceMediaDocument(aOther.mForceMediaDocument), mHttpsUpgradeTelemetry(aOther.mHttpsUpgradeTelemetry), - mNavigationAPIState(aOther.mNavigationAPIState) { + mNavigationAPIState(aOther.mNavigationAPIState), + mIsInitialAboutBlankHandlingProhibited( + aOther.mIsInitialAboutBlankHandlingProhibited) { MOZ_DIAGNOSTIC_ASSERT( XRE_IsParentProcess(), "Cloning a nsDocShellLoadState with the same load identifier is only " @@ -270,7 +274,8 @@ nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier) mTriggeringRemoteType(XRE_IsContentProcess() ? ContentChild::GetSingleton()->GetRemoteType() : NOT_REMOTE_TYPE), - mSchemelessInput(nsILoadInfo::SchemelessInputTypeUnset) { + mSchemelessInput(nsILoadInfo::SchemelessInputTypeUnset), + mIsInitialAboutBlankHandlingProhibited(false) { MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!"); // For https telemetry we set a flag indicating whether the load is https. @@ -1452,6 +1457,8 @@ DocShellLoadStateInit nsDocShellLoadState::Serialize( loadState.UnstrippedURI() = mUnstrippedURI; loadState.RemoteTypeOverride() = mRemoteTypeOverride; loadState.IsCaptivePortalTab() = mIsCaptivePortalTab; + loadState.IsInitialAboutBlankHandlingProhibited() = + mIsInitialAboutBlankHandlingProhibited; if (mNavigationAPIState) { loadState.NavigationAPIState().emplace(); diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h @@ -468,6 +468,13 @@ class nsDocShellLoadState final { bool GetIsCaptivePortalTab() const; void SetIsCaptivePortalTab(bool aIsCaptivePortalTab); + void ProhibitInitialAboutBlankHandling() { + mIsInitialAboutBlankHandlingProhibited = true; + } + bool IsInitialAboutBlankHandlingProhibited() { + return mIsInitialAboutBlankHandlingProhibited; + } + protected: // Destructor can't be defaulted or inlined, as header doesn't have all type // includes it needs to do so. @@ -751,6 +758,11 @@ class nsDocShellLoadState final { // Whether this is a captive portal tab. bool mIsCaptivePortalTab = false; + + // When this is the initial load and it is loading about:blank, force it + // to take the regular load path. It will replace the previous document + // and not load synchronous. + bool mIsInitialAboutBlankHandlingProhibited; }; #endif /* nsDocShellLoadState_h__ */ diff --git a/docshell/base/nsDocShellTreeOwner.cpp b/docshell/base/nsDocShellTreeOwner.cpp @@ -492,12 +492,6 @@ nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult) { //***************************************************************************** NS_IMETHODIMP -nsDocShellTreeOwner::InitWindow(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) { diff --git a/docshell/base/nsIDocumentViewer.idl b/docshell/base/nsIDocumentViewer.idl @@ -89,6 +89,8 @@ interface nsIDocumentViewer : nsISupports /** * Overload PermitUnload method for C++ consumers with no aPermitUnloadFlags * argument. + * + * ATTENTION: This call may destroy the docshell that owns this viewer. */ %{C++ nsresult PermitUnload(bool* canUnload) { @@ -100,6 +102,8 @@ interface nsIDocumentViewer : nsISupports * Checks if the document wants to prevent unloading by firing beforeunload on * the document. * The result is returned. + * + * ATTENTION: This call may destroy the docshell that owns this viewer. */ boolean permitUnload([optional] in nsIDocumentViewer_PermitUnloadAction aAction); diff --git a/docshell/test/browser/browser.toml b/docshell/test/browser/browser.toml @@ -361,6 +361,8 @@ support-files = ["file_replace_state_during_navigation.html"] ["browser_search_notification.js"] +["browser_system_principal_initial_document.js"] + ["browser_tab_replace_while_loading.js"] skip-if = [ "os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # Bug 1604237 diff --git a/docshell/test/browser/browser_browsingContext-webProgress.js b/docshell/test/browser/browser_browsingContext-webProgress.js @@ -37,19 +37,11 @@ add_task(async function () { const isBfcacheInParentEnabled = SpecialPowers.Services.appinfo.sessionHistoryInParent && SpecialPowers.Services.prefs.getBoolPref("fission.bfcacheInParent"); - if (isBfcacheInParentEnabled) { - isnot( - aboutBlankBrowsingContext, - firstPageBrowsingContext, - "With bfcache in parent, navigations spawn a new BrowsingContext" - ); - } else { - is( - aboutBlankBrowsingContext, - firstPageBrowsingContext, - "Without bfcache in parent, navigations reuse the same BrowsingContext" - ); - } + is( + aboutBlankBrowsingContext, + firstPageBrowsingContext, + "The first navigation away from the initial about:blank reuses the BrowsingContext with or without bfcacheInParent" + ); info("Wait for onLocationChange to be fired"); { diff --git a/docshell/test/browser/browser_browsing_context_discarded.js b/docshell/test/browser/browser_browsing_context_discarded.js @@ -66,8 +66,8 @@ add_task(async function subframe() { [], () => { const iframe = content.document.createElement("iframe"); + iframe.src = "https://example.com/"; content.document.body.appendChild(iframe); - iframe.contentWindow.location = "https://example.com/"; return iframe.browsingContext; } ); @@ -97,6 +97,14 @@ add_task(async function subframe() { add_task(async function replaceToplevel() { const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + // Force load about:blank such that BC::HasLoadedNonInitialDocument is true + // which means it's BFCache eligible and will be replaced + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); + const browsingContext = tab.linkedBrowser.browsingContext; const expected = new Map([[browsingContext, "replace"]]); diff --git a/docshell/test/browser/browser_bug388121-2.js b/docshell/test/browser/browser_bug388121-2.js @@ -9,12 +9,6 @@ function test() { function testLoad() { let wgp = w.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal; - if (wgp == origWgp) { - // Go back to polling - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - setTimeout(testLoad, 10); - return; - } var prin = wgp.documentPrincipal; isnot(prin, null, "Loaded principal must not be null when adding " + uri); isnot( diff --git a/docshell/test/browser/browser_bug670318.js b/docshell/test/browser/browser_bug670318.js @@ -11,138 +11,138 @@ 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.less( - 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; +async function LegacySHTest(browser) { + 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.less( + 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; + }); +} + +async function SHIPTest(browser) { + 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", + () => { + let history = docShell.QueryInterface( + Ci.nsIWebNavigation + ).sessionHistory; + Assert.less( + history.index, + history.count, + "history.index is valid" + ); + resolve(); + }, + { capture: true, once: true } + ); - QueryInterface: ChromeUtils.generateQI([ - "nsISHistoryListener", - "nsISupportsWeakReference", - ]), - }; - - history.legacySHistory.addSHistoryListener(this._testListener); - content.location = URL; - - await testDone.promise; + content.location.reload(); + }); }); - - return; + 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; + // XXX I think this will be notified once |URL|, i.e. file_bug670318.html loads. + // this is probably not desired. + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", + ]), + }; + + history.addSHistoryListener(listener); + BrowserTestUtils.startLoadingURIString(browser, URL); + + await testDone.promise; +} - 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", - () => { - let history = docShell.QueryInterface( - Ci.nsIWebNavigation - ).sessionHistory; - Assert.less( - 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.startLoadingURIString(browser, URL); - - await testDone.promise; - } - ); +add_task(async function test() { + const task = SpecialPowers.Services.appinfo.sessionHistoryInParent + ? SHIPTest + : LegacySHTest; + await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, task); }); diff --git a/docshell/test/browser/browser_isInitialDocument.js b/docshell/test/browser/browser_isInitialDocument.js @@ -21,8 +21,8 @@ add_task(async function new_about_blank_tab() { await BrowserTestUtils.withNewTab("about:blank", async browser => { is( browser.browsingContext.currentWindowGlobal.isInitialDocument, - false, - "After loading an actual, final about:blank in the tab, the field is false" + true, + "After the initial about:blank fires its load event, the field is still true" ); }); }); @@ -35,6 +35,9 @@ add_task(async function iframe_initial_about_blank() { info("Create an iframe without any explicit location"); await SpecialPowers.spawn(browser, [], async () => { const iframe = content.document.createElement("iframe"); + let loadPromise = new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + }); // Add the iframe to the DOM tree in order to be able to have its browsingContext content.document.body.appendChild(iframe); const { browsingContext } = iframe; @@ -52,13 +55,11 @@ add_task(async function iframe_initial_about_blank() { ] ); - await new Promise(resolve => { - iframe.addEventListener("load", resolve, { once: true }); - }); + await loadPromise; is( iframe.contentDocument.isInitialDocument, - false, - "The field is false after having loaded the final about:blank document" + true, + "The field remains true when the iframe stays at about:blank" ); let afterLoadPromise = SpecialPowers.spawnChrome( [browsingContext], @@ -73,7 +74,7 @@ add_task(async function iframe_initial_about_blank() { is(beforeIsInitial, true, "before load is initial in parent"); is(beforeWasInitial, true, "before load was initial in parent"); let [afterIsInitial, afterWasInitial] = await afterLoadPromise; - is(afterIsInitial, false, "after load is not initial in parent"); + is(afterIsInitial, true, "after load is initial in parent"); is(afterWasInitial, true, "after load was initial in parent"); iframe.remove(); }); @@ -104,14 +105,12 @@ add_task(async function iframe_initial_about_blank() { add_task(async function window_open() { async function testWindowOpen({ browser, args, isCrossOrigin, willLoad }) { info(`Open popup with ${JSON.stringify(args)}`); - const onNewTab = BrowserTestUtils.waitForNewTab( - gBrowser, - args[0] || "about:blank" - ); + let url = args[0] || "about:blank"; + const onNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url); await SpecialPowers.spawn( browser, - [args, isCrossOrigin, willLoad], - async (args, crossOrigin, willLoad) => { + [url, args, isCrossOrigin, willLoad], + async (url, args, crossOrigin, willLoad) => { const win = content.window.open(...args); is( win.document.isInitialDocument, @@ -128,7 +127,7 @@ add_task(async function window_open() { // In cross origin, it is harder to watch for new document load, and if // no argument is passed no load will happen. - if (!crossOrigin && willLoad) { + if (!crossOrigin && willLoad && url != "about:blank") { await new Promise(r => win.addEventListener("load", r, { once: true }) ); @@ -149,11 +148,19 @@ add_task(async function window_open() { const windowGlobal = newTab.linkedBrowser.browsingContext.currentWindowGlobal; if (willLoad) { - is( - windowGlobal.isInitialDocument, - false, - "The field is false in the parent process after having loaded the final document" - ); + if (url == "about:blank") { + is( + windowGlobal.isInitialDocument, + true, + "The field is true in the parent process after having loaded about:blank" + ); + } else { + is( + windowGlobal.isInitialDocument, + false, + "The field is false in the parent process after having loaded the final document" + ); + } } else { is( windowGlobal.isInitialDocument, diff --git a/docshell/test/browser/browser_system_principal_initial_document.js b/docshell/test/browser/browser_system_principal_initial_document.js @@ -0,0 +1,126 @@ +const CHROME_URI = "chrome://global/content/aboutSupport.xhtml"; + +// The goal of this test is to check for crashes and document current behavior + +add_task(async function test_transient_about_blank_in_chrome_iframe() { + // open a tab with system principal due to chrome URI + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, CHROME_URI); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [CHROME_URI], async CHROME_URI => { + let bc = content.browsingContext; + let contentPrincipal = content.document.nodePrincipal; + Assert.ok(contentPrincipal.isSystemPrincipal, "tab has system principal"); + Assert.ok(bc.isContent, "tab BC is content"); + + // within a system context, add a new iframe + let iframe = content.document.createElement("iframe"); + iframe.src = CHROME_URI; + content.document.documentElement.appendChild(iframe); + + // iframe will start with some different principal + let ifrBC = iframe.browsingContext; + let aboutBlankPrincipal = iframe.contentDocument.nodePrincipal; + Assert.ok( + iframe.contentDocument.isUncommittedInitialDocument, + "iframe at transient about:blank" + ); + Assert.ok( + !aboutBlankPrincipal.isSystemPrincipal, + "transient about:blank doesn't have system principal" + ); + Assert.ok( + aboutBlankPrincipal.isNullPrincipal, + "transient about:blank starts out with null principal" + ); + Assert.ok(ifrBC.isContent, "iframe BC is content"); + + // test inner window will be replaced + iframe.contentWindow.foo = "bar"; + iframe.contentWindow.addEventListener("load", () => { + Assert.ok(false, "load event never fired on initial iframe inner window"); + }); + + await new Promise(res => iframe.addEventListener("load", res)); + + // after load, iframe has system principal and inner window was replaced + let chromeDocPrincipal = iframe.contentDocument.nodePrincipal; + Assert.ok( + chromeDocPrincipal.isSystemPrincipal, + "after load, iframe has system principal" + ); + Assert.ok(ifrBC.isContent, "iframe BC stays content"); + Assert.equal( + iframe.contentWindow.foo, + undefined, + "iframe inner window replaced" + ); + + iframe.remove(); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_about_blank_iframe_in_chrome_doc() { + // open a tab with system principal due to chrome URI + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, CHROME_URI); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [], async () => { + let bc = content.browsingContext; + let contentPrincipal = content.document.nodePrincipal; + Assert.ok(contentPrincipal.isSystemPrincipal, "tab has system principal"); + Assert.ok(bc.isContent, "tab BC is content"); + + // within a system context, add an about:blank iframe + let iframe = content.document.createElement("iframe"); + content.document.documentElement.appendChild(iframe); + let ifrPrincipal = SpecialPowers.wrap(iframe.contentWindow).document + .nodePrincipal; + Assert.ok( + !ifrPrincipal.isSystemPrincipal, + "about:blank iframe has no system principal" + ); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_open_about_blank_link_from_chrome_doc() { + // open a tab with system principal due to chrome URI + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, CHROME_URI); + let browser = tab.linkedBrowser; + + const linkOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:blank"); + + await SpecialPowers.spawn(browser, [], async () => { + let bc = content.browsingContext; + let contentPrincipal = content.document.nodePrincipal; + Assert.ok(contentPrincipal.isSystemPrincipal, "tab has system principal"); + Assert.ok(bc.isContent, "tab BC is content"); + + // within a system context, add an about:blank link + let link = content.document.createElement("a"); + link.href = "about:blank"; + link.target = "_blank"; + content.document.documentElement.appendChild(link); + link.click(); + }); + + const blanktab = await linkOpened; + + // Check the opened tab from the link is privileged + await SpecialPowers.spawn(blanktab.linkedBrowser, [], async () => { + let bc = content.browsingContext; + let contentPrincipal = content.document.nodePrincipal; + Assert.ok( + contentPrincipal.isSystemPrincipal, + "about:blank has system principal" + ); + Assert.ok(bc.isContent, "about:blank BC is content"); + }); + + BrowserTestUtils.removeTab(blanktab); + BrowserTestUtils.removeTab(tab); +}); diff --git a/docshell/test/browser/browser_tab_touch_events.js b/docshell/test/browser/browser_tab_touch_events.js @@ -53,8 +53,7 @@ async function test_body() { "Newly created frames should use the new touchEventsOverride flag" ); - // Wait for the non-transient about:blank to load. - await ContentTaskUtils.waitForEvent(newFrame, "load"); + // about:blank in the iframe has loaded synchronously newFrameWin = newFrame.contentWindow; bc = newFrameWin.browsingContext; is( diff --git a/docshell/test/browser/file_bug670318.html b/docshell/test/browser/file_bug670318.html @@ -9,10 +9,9 @@ function load() { var count = 0; var iframe = document.createElement("iframe"); + iframe.src = "data:text/html;charset=utf-8,iframe " + (++count); iframe.onload = function() { setTimeout(next, 0); }; document.body.appendChild(iframe); - - setTimeout(next, 0); } </script> </head> diff --git a/docshell/test/chrome/test_bug608669.xhtml b/docshell/test/chrome/test_bug608669.xhtml @@ -51,7 +51,7 @@ function* doTest() { // create a new window var testWin = chromeWindow.open("", "bug 608669", "chrome,width=600,height=600"); testWin.x = "y"; - is(notificationCount, 1, "after created window"); + is(notificationCount, 0, "after created window"); // Try loading in the window testWin.location = "bug608669.xhtml"; diff --git a/docshell/test/mochitest/mochitest.toml b/docshell/test/mochitest/mochitest.toml @@ -280,6 +280,8 @@ skip-if = [ "http3", ] +["test_initial_blank_doc_principal.html"] + ["test_javascript_sandboxed_popup.html"] ["test_load_during_reload.html"] diff --git a/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html @@ -16,9 +16,9 @@ async function test() { // Create an OOP iframe let frame = document.createElement("iframe"); + document.body.appendChild(frame); await new Promise(r => { frame.onload = r; - document.body.appendChild(frame); // eslint-disable-next-line @microsoft/sdl/no-insecure-url frame.contentWindow.location = "http://example.net/tests/docshell/test/dummy_page.html"; }); diff --git a/docshell/test/mochitest/test_initial_blank_doc_principal.html b/docshell/test/mochitest/test_initial_blank_doc_principal.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Tests for the principal of initial about:blank documents</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content"></div> +<pre id="test"></pre> +</body> + +<script> + function waitForEvent(name, target, checkFn = null) { + return new Promise(resolve => { + function listener(event) { + if (!checkFn || checkFn(event)) { + resolve(event); + if (checkFn) { + target.removeEventListener(name, listener); + } + } + } + target.addEventListener(name, listener, { once: !checkFn }); + }); + } + + const testContent = document.getElementById("content"); + + async function createSandboxedIframe(options = {}) { + const { srcdoc = "", waitLoad = true, extraSandbox = "" } = options; + + const ifr = document.createElement("iframe"); + ifr.sandbox = `allow-scripts ${extraSandbox}`; + if (srcdoc) { + ifr.srcdoc = srcdoc; + } + + const loaded = waitLoad ? waitForEvent("load", ifr) : null; + testContent.appendChild(ifr); + if (waitLoad) { + await loaded; + } + + return { ifr, doc: SpecialPowers.wrap(ifr).contentDocument }; + } + + // Tests + + // We want to check that + // - initial about:blank documents load synchronously + // - without failing assertions in nsDocShell::IsAboutBlankLoadOntoInitialAboutBlank + // - while ending up with the right principal + + async function test_sandboxed_iframe() { + // basic case: <iframe sandbox> + const { ifr, doc } = await createSandboxedIframe({ waitLoad: false }); + is(doc.readyState, "complete", "Sandboxed iframe loaded initial document synchronously"); + ok(doc.nodePrincipal.isNullPrincipal, "Sandboxed ifame has null principal"); + ifr.remove(); + } + + async function test_nested_iframes() { + // Iframes nested in an isolated iframe + const { ifr } = await createSandboxedIframe({ + srcdoc: "<iframe id=first></iframe><iframe id=second sandbox>" + }); + + await SpecialPowers.spawn(ifr, [], () => { + const origin = content.document.nodePrincipal.siteOrigin; + const first = content.document.getElementById("first"); + const second = content.document.getElementById("second"); + const firstPrincipal = SpecialPowers.wrap(first).contentDocument.nodePrincipal; + const secondPrincipal = SpecialPowers.wrap(second).contentDocument.nodePrincipal; + ok(secondPrincipal.siteOrigin != origin, "<iframe> is implicitly isolated"); + ok(secondPrincipal.siteOrigin != origin, "<iframe sandbox> is explicitly isolated"); + ok(firstPrincipal.siteOrigin != secondPrincipal.siteOrigin, 'nested iframes are isolated from each other'); + }); + + ifr.remove(); + } + + async function test_nested_iframes_crash() { + // This caused an assertion failure during development + // due to nsFrameLoader::Show being called for the nested frames before ReallyStartLoading + // and so they end up with the wrong principal on the initial document. + const { ifr } = await createSandboxedIframe({ + srcdoc: "<iframe id=first></iframe><iframe id=second sandbox>", + extraSandbox: "allow-same-origin" + }); + ok(true, "did not crash"); + ifr.remove(); + } + + async function test_sandboxed_iframe_opens_window() { + // <iframe sandboxed> does window.open() which inherits the same principal + const { ifr } = await createSandboxedIframe({ extraSandbox: "allow-popups" }); + + await SpecialPowers.spawn(ifr, [], () => { + const origin = content.document.nodePrincipal.siteOrigin; + + const popup = content.open(); + is(popup.document.readyState, "complete", "Popup from sandbox loaded synchronously"); + const popupOrigin = SpecialPowers.wrap(popup.document).nodePrincipal.siteOrigin; + ok(popupOrigin != origin, "Popup from sandboxed iframe is isolated from it."); + + popup.close(); + }); + + ifr.remove(); + } + + // Running + + async function tests() { + SimpleTest.waitForExplicitFinish(); + + + await Promise.all([ + test_sandboxed_iframe(), + test_nested_iframes(), + test_nested_iframes_crash(), + test_sandboxed_iframe_opens_window(), + ]); + + SimpleTest.finish(); + } + + tests(); +</script> +</html> diff --git a/docshell/test/navigation/test_recursive_frames.html b/docshell/test/navigation/test_recursive_frames.html @@ -76,6 +76,7 @@ "http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html", // eslint-disable-next-line @microsoft/sdl/no-insecure-url "http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html", + "about:blank", ], }, { // too many recursive objects diff --git a/dom/animation/test/document-timeline/test_document-timeline.html b/dom/animation/test/document-timeline/test_document-timeline.html @@ -137,7 +137,7 @@ async_test(function(t) { })); }); - if (hiddenIFrame.contentDocument.readyState === 'complete') { + if (hiddenIFrame.contentDocument.readyState === 'complete' && hiddenIFrame.contentDocument.location.href !== "about:blank") { testToRunOnLoad(); } else { hiddenIFrame.addEventListener("load", testToRunOnLoad); diff --git a/dom/base/DocGroup.h b/dom/base/DocGroup.h @@ -43,8 +43,6 @@ class JSExecutionManager; // however, align with web-visible synchronous script access boundaries. class DocGroup final { public: - typedef nsTArray<Document*>::iterator Iterator; - NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DocGroup) NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(DocGroup) @@ -78,16 +76,6 @@ class DocGroup final { // DocGroup::RemoveDocument). void RemoveDocument(Document* aDocument); - // Iterators for iterating over every document within the DocGroup - Iterator begin() { - MOZ_ASSERT(NS_IsMainThread()); - return mDocuments.begin(); - } - Iterator end() { - MOZ_ASSERT(NS_IsMainThread()); - return mDocuments.end(); - } - // Return a pointer that can be continually checked to see if access to this // DocGroup is valid. This pointer should live at least as long as the // DocGroup. diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -1377,6 +1377,7 @@ Document::Document(const char* aContentType, mRenderingSuppressedForViewTransitions(false), mBidiEnabled(false), mMayNeedFontPrefsUpdate(true), + mInitialAboutBlankLoadCompleting(false), mIgnoreDocGroupMismatches(false), mAddedToMemoryReportingAsDataDocument(false), mMayStartLayout(true), @@ -3682,6 +3683,20 @@ static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy, #endif // defined(EARLY_BETA_OR_EARLIER) } +void Document::ApplyCspFromLoadInfo(nsILoadInfo* aLoadInfo) { + // The CSP directives upgrade-insecure-requests as well as + // block-all-mixed-content not only apply to the toplevel document, + // but also to nested documents. The loadInfo of a subdocument + // load already holds the correct flag, so let's just set it here + // on the document. Please note that we set the appropriate preload + // bits just for the sake of completeness here, because the preloader + // does not reach into subdocuments. + mUpgradeInsecureRequests = aLoadInfo->GetUpgradeInsecureRequests(); + mUpgradeInsecurePreloads = mUpgradeInsecureRequests; + mBlockAllMixedContent = aLoadInfo->GetBlockAllMixedContent(); + mBlockAllMixedContentPreloads = mBlockAllMixedContent; +} + nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, nsISupports* aContainer, @@ -3795,17 +3810,7 @@ nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, (void)docShell->GetBrowsingContext()->SetOpenerPolicy(policy); } - // The CSP directives upgrade-insecure-requests as well as - // block-all-mixed-content not only apply to the toplevel document, - // but also to nested documents. The loadInfo of a subdocument - // load already holds the correct flag, so let's just set it here - // on the document. Please note that we set the appropriate preload - // bits just for the sake of completeness here, because the preloader - // does not reach into subdocuments. - mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests(); - mUpgradeInsecurePreloads = mUpgradeInsecureRequests; - mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent(); - mBlockAllMixedContentPreloads = mBlockAllMixedContent; + ApplyCspFromLoadInfo(loadInfo); // HTTPS-Only Mode flags // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all @@ -8608,7 +8613,7 @@ void Document::BeginLoad() { mDidFireDOMContentLoaded = false; BlockDOMContentLoaded(); - if (mScriptLoader) { + if (mScriptLoader && !IsInitialDocument()) { mScriptLoader->BeginDeferringScripts(); } @@ -8836,9 +8841,10 @@ void Document::UnblockDOMContentLoaded() { mDidFireDOMContentLoaded = true; - MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE); + MOZ_ASSERT(IsInitialDocument() || mReadyState == READYSTATE_INTERACTIVE); if (!mSynchronousDOMContentLoaded) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!IsInitialDocument()); nsCOMPtr<nsIRunnable> ev = NewRunnableMethod("Document::DispatchContentLoadedEvents", this, &Document::DispatchContentLoadedEvents); @@ -10474,10 +10480,6 @@ Document* Document::Open(const Optional<nsAString>& /* unused */, mSecurityInfo = callerDoc->GetSecurityInfo(); // Step 16 - // See <https://github.com/whatwg/html/issues/4299>. Since our - // URL may be changing away from about:blank here, we really want to unset - // this flag no matter what, since only about:blank can be an initial - // document. if (IsInitialDocument()) { SetInitialStatus(Document::InitialStatus::IsInitialButExplicitlyOpened); } @@ -12417,8 +12419,10 @@ void Document::BlockOnload() { // -- it's not ours. // If we're already complete there's no need to mess with the loadgroup // either, we're not blocking the load event after all. + // Note that ready state is not reliable for the initial about:blank. if (mOnloadBlockCount == 0 && mScriptGlobalObject && - mReadyState != ReadyState::READYSTATE_COMPLETE) { + (mReadyState != ReadyState::READYSTATE_COMPLETE || + mInitialAboutBlankLoadCompleting)) { if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) { loadGroup->AddRequest(mOnloadBlocker, nullptr); } @@ -14848,7 +14852,8 @@ class UnblockParsingPromiseHandler final : public PromiseNativeHandler { const BlockParsingOptions& aOptions) : mPromise(aPromise) { nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull(); - if (parser && + // Parser blocking is not allowed for about:blank + if (parser && !parser->IsAboutBlankMode() && (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) { parser->BlockParser(); mParser = do_GetWeakReference(parser); @@ -20368,28 +20373,40 @@ nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const { return nullptr; } -void Document::SetIsInitialDocument(bool aIsInitialDocument) { - if (aIsInitialDocument) { - mInitialStatus = InitialStatus::IsInitial; - } else if (mInitialStatus != InitialStatus::NeverInitial) { - mInitialStatus = InitialStatus::WasInitial; +void Document::SetInitialStatus(InitialStatus aStatus) { + mInitialStatus = aStatus; + + if (aStatus == InitialStatus::IsInitialUncommitted) { + // Set readyState to complete silently. + mReadyState = READYSTATE_COMPLETE; + mSetCompleteAfterDOMContentLoaded = false; + mSynchronousDOMContentLoaded = true; + } else if (aStatus == InitialStatus::IsInitialButExplicitlyOpened) { + mSynchronousDOMContentLoaded = false; } // Asynchronously tell the parent process that we are, or are no longer, the // initial document. This happens async. if (auto* wgc = GetWindowGlobalChild()) { - wgc->SendSetIsInitialDocument(aIsInitialDocument); + wgc->SendSetIsInitialDocument(IsInitialDocument()); } } -void Document::SetInitialStatus(InitialStatus aStatus) { - mInitialStatus = aStatus; - - // Asynchronously tell the parent process that we are, or are no longer, the - // initial document. This happens async. +void Document::BeginInitialAboutBlankLoadCompleting(nsIChannel* aChannel) { + MOZ_ASSERT(aChannel); + SetInitialStatus(InitialStatus::IsInitialCommitted); if (auto* wgc = GetWindowGlobalChild()) { - wgc->SendSetIsInitialDocument(aStatus == InitialStatus::IsInitial); + wgc->SendCommitToInitialDocument(); } + mInitialAboutBlankLoadCompleting = true; + mChannel = aChannel; + mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); + + // This is the condition under which we would usually set + // mMaybeServiceWorkerControlled in SetScriptGlobalObject. + MOZ_ASSERT(mDocumentContainer && mScriptGlobalObject, + "Should have document container and script global"); + mMaybeServiceWorkerControlled = true; } // static diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -668,6 +668,8 @@ class Document : public nsINode, } }; + void ApplyCspFromLoadInfo(nsILoadInfo* aLoadInfo); + /** * Let the document know that we're starting to load data into it. * @param aCommand The parser command. Must not be null. @@ -1014,22 +1016,35 @@ class Document : public nsINode, */ void SetBidiEnabled() { mBidiEnabled = true; } + /** + * Whether a document is the initial document in its window, and if so, + * which stage of initialness it is in. + */ enum class InitialStatus : uint8_t { - IsInitial, + // The first stage of an initial document. No navigation has occured, + // we might navigate away or commit to this document by firing load. + IsInitialUncommitted, + // An explicit navigation to `about:blank` occured. Load was fired or will + // be soon. + IsInitialCommitted, + // document.open() was called on the initial document. IsInitialButExplicitlyOpened, - WasInitial, + // This document is not initial NeverInitial, }; /** - * Ask this document whether it's the initial document in its window. + * Ask this document if the "is initial about:blank" flag is set, i.e. + * it is the initial document in its window. + * https://html.spec.whatwg.org/#is-initial-about:blank */ bool IsInitialDocument() const { - return mInitialStatus == InitialStatus::IsInitial; + return mInitialStatus == InitialStatus::IsInitialUncommitted || + mInitialStatus == InitialStatus::IsInitialCommitted; } /** - * Ask this document whether it has ever been a initial document in its + * Ask this document whether it has ever been an initial document in its * window. */ bool IsEverInitialDocument() const { @@ -1037,15 +1052,37 @@ class Document : public nsINode, } /** - * Tell this document that it's the initial document in its window. See - * comments on mIsInitialDocumentInWindow for when this should be called. + * Ask this document whether it is the initial document in its window and + * no navigation to about:blank has yet occured that would cause us to commit + * to it. */ - void SetIsInitialDocument(bool aIsInitialDocument); + bool IsUncommittedInitialDocument() const { + return mInitialStatus == InitialStatus::IsInitialUncommitted; + } InitialStatus GetInitialStatus() const { return mInitialStatus; } + /** + * Tell this document whether it's the initial document in its window. + * Should be called when creating the initial `about:blank`, when committing + * to it, and from `document.open()`. + */ void SetInitialStatus(Document::InitialStatus aStatus); + /** + * Returns true if this is the initial document in its window + * and we are currently committing to it. + */ + bool InitialAboutBlankLoadCompleting() const { + return mInitialAboutBlankLoadCompleting; + } + + void BeginInitialAboutBlankLoadCompleting(nsIChannel* aChannel); + + void EndInitialAboutBlankLoadCompleting() { + mInitialAboutBlankLoadCompleting = false; + } + void SetLoadedAsData(bool aLoadedAsData, bool aConsiderForMemoryReporting); TimeStamp GetLoadingOrRestoredFromBFCacheTimeStamp() const { @@ -4875,6 +4912,13 @@ class Document : public nsINode, // True if we may need to recompute the language prefs for this document. bool mMayNeedFontPrefsUpdate : 1; + // True if we are trying to fire the load event for the initial about:blank. + // Since the initial about:blank is already in READYSTATE_COMPLETE when + // firing the load event, a different indicator is needed. + // IsInitialDocument() isn't a sufficient indicator, because it + // remains set, when navigating back in history. + bool mInitialAboutBlankLoadCompleting : 1; + bool mIgnoreDocGroupMismatches : 1; // True if the document is considered for memory reporting as a @@ -5035,6 +5079,7 @@ class Document : public nsINode, bool mDelayFrameLoaderInitialization : 1; + // True if we should fire load events synchronously bool mSynchronousDOMContentLoaded : 1; // Set to true when the document is possibly controlled by the ServiceWorker. diff --git a/dom/base/crashtests/1370072.html b/dom/base/crashtests/1370072.html @@ -1,18 +1,30 @@ <script> +let nLoads = 0; + function start() { - o1=document.createElement('iframe'); - o1.addEventListener('load',fun0); - document.body.appendChild(o1); + ifr = document.createElement('iframe'); + ifr.addEventListener('load', onLoad); + document.body.appendChild(ifr); } -function fun0() { - o5=o1.contentDocument; - o52=function() {let x=o5.querySelectorAll('*:not([id])');return x[x.length-1]}(); - o1.contentWindow.onresize=fun1; - o1.height='5px'; - o52.clientTop; + +function onLoad() { + if (++nLoads > 3) { + // Limit recursion depth, bug 1971796 + return; + } + doc = ifr.contentDocument; + // el will be body + el = function() { + let elements = doc.querySelectorAll('*:not([id])'); + return elements[elements.length-1] + }(); + ifr.contentWindow.onresize = onResize; + ifr.height = '5px'; + el.clientTop; } -function fun1() { - document.documentElement.appendChild(o1); + +function onResize() { + document.documentElement.appendChild(ifr); } </script> <body onload="start()"></body> diff --git a/dom/base/domerr.msg b/dom/base/domerr.msg @@ -23,6 +23,7 @@ DOM4_MSG_DEF(NamespaceError, "An attempt was made to create or change an object DOM4_MSG_DEF(InvalidAccessError, "A parameter or an operation is not supported by the underlying object", NS_ERROR_DOM_INVALID_ACCESS_ERR) DOM4_MSG_DEF(TypeMismatchError, "The type of an object is incompatible with the expected type of the parameter associated to the object", NS_ERROR_DOM_TYPE_MISMATCH_ERR) DOM4_MSG_DEF(SecurityError, "The operation is insecure.", NS_ERROR_DOM_SECURITY_ERR) +DOM4_MSG_DEF(SecurityError, "Property access denied.", NS_ERROR_DOM_PROP_ACCESS_DENIED) DOM4_MSG_DEF(NetworkError, "A network error occurred.", NS_ERROR_DOM_NETWORK_ERR) DOM4_MSG_DEF(AbortError, "The operation was aborted. ", NS_ERROR_DOM_ABORT_ERR) DOM4_MSG_DEF(URLMismatchError, "The given URL does not match another URL.", NS_ERROR_DOM_URL_MISMATCH_ERR) diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp @@ -110,6 +110,7 @@ #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" #include "nsNetUtil.h" +#include "nsOpenWindowInfo.h" #include "nsPIDOMWindow.h" #include "nsPIWindowRoot.h" #include "nsQueryObject.h" @@ -765,7 +766,8 @@ nsresult nsFrameLoader::ReallyStartLoadingInternal() { // Kick off the load... bool tmpState = mNeedsAsyncDestroy; - mNeedsAsyncDestroy = true; + // Sync destroy should be possible from the sync about:blank load event + mNeedsAsyncDestroy = !NS_IsAboutBlankAllowQueryAndFragment(mURIToLoad); RefPtr<nsDocShell> docShell = GetDocShell(); rv = docShell->LoadURI(loadState, false); @@ -955,6 +957,7 @@ bool nsFrameLoader::Show(nsSubDocumentFrame* aFrame) { return ShowRemoteFrame(aFrame); } const LayoutDeviceIntSize size = aFrame->GetInitialSubdocumentSize(); + nsresult rv = MaybeCreateDocShell(); if (NS_FAILED(rv)) { return false; @@ -985,7 +988,9 @@ bool nsFrameLoader::Show(nsSubDocumentFrame* aFrame) { } RefPtr<nsDocShell> baseWindow = GetDocShell(); - baseWindow->InitWindow(nullptr, 0, 0, size.width, size.height); + MOZ_ASSERT(ds == baseWindow, "How did the docshell change?"); + baseWindow->InitWindow(nullptr, 0, 0, size.width, size.height, nullptr, + nullptr); baseWindow->SetVisibility(true); NS_ENSURE_TRUE(GetDocShell(), false); @@ -2236,12 +2241,6 @@ nsresult nsFrameLoader::MaybeCreateDocShell() { nsGlobalWindowOuter::Cast(newWindow)->AllowScriptsToClose(); } - if (!docShell->Initialize()) { - // Do not call Destroy() here. See bug 472312. - NS_WARNING("Something wrong when creating the docshell for a frameloader!"); - return NS_ERROR_FAILURE; - } - NS_ENSURE_STATE(mOwnerContent); // If we are an in-process browser, we want to set up our session history. @@ -2264,19 +2263,55 @@ nsresult nsFrameLoader::MaybeCreateDocShell() { MOZ_ALWAYS_SUCCEEDS(mPendingBrowsingContext->SetInitialSandboxFlags( mPendingBrowsingContext->GetSandboxFlags())); + // Gather things to inherit into the initial about:blank + + // For HTML [i]frames and objects, perform the inheritance here. (It would + // probably be more proper to hoist this to each call site of + // nsFrameLoader::Create.) + nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); + nsCOMPtr<nsIPrincipal> partitionedPrincipal = doc->PartitionedPrincipal(); + + // We use mOpenWindowInfo so that JS can force a principal onto us + if (mOpenWindowInfo && mOpenWindowInfo->PrincipalToInheritForAboutBlank()) { + principal = mOpenWindowInfo->PrincipalToInheritForAboutBlank(); + partitionedPrincipal = + mOpenWindowInfo->PartitionedPrincipalToInheritForAboutBlank(); + } + + if ((mPendingBrowsingContext->IsContent() || XRE_IsContentProcess()) && + (!principal || principal->IsSystemPrincipal())) { + // Never inherit system principal to a content HTML [i]frame. + principal = NullPrincipal::Create( + mPendingBrowsingContext->OriginAttributesRef(), nullptr); + partitionedPrincipal = principal; + } + + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + openWindowInfo->mPrincipalToInheritForAboutBlank = principal.forget(); + openWindowInfo->mPartitionedPrincipalToInheritForAboutBlank = + partitionedPrincipal.forget(); + openWindowInfo->mPolicyContainerToInheritForAboutBlank = + doc->GetPolicyContainer(); + openWindowInfo->mCoepToInheritForAboutBlank = doc->GetEmbedderPolicy(); + openWindowInfo->mBaseUriToInheritForAboutBlank = mOwnerContent->GetBaseURI(); + if (!docShell->Initialize(openWindowInfo, nullptr)) { + // Do not call Destroy() here. See bug 472312. + NS_WARNING("Something wrong when creating the docshell for a frameloader!"); + return NS_ERROR_FAILURE; + } + ReallyLoadFrameScripts(); - // Previously we would forcibly create the initial about:blank document for - // in-process content frames from a frame script which eagerly loaded in - // every tab. This lead to other frontend components growing dependencies on - // the initial about:blank document being created eagerly. See bug 1471327 - // for details. - // - // We also eagerly create the initial about:blank document for remote loads - // separately when initializing BrowserChild. - if (mIsTopLevelContent && - mPendingBrowsingContext->GetMessageManagerGroup() == u"browsers"_ns) { - (void)mDocShell->GetDocument(); + // Previously, the lazy about:blank creation had the effect of running + // nsGlobalWindowOuter::DispatchDOMWindowCreated, which sets up the message + // manager, after ReallyLoadFrameScripts(). We can't achieve the same by using + // a script blocker while calling `docShell->Initialize()`, because the + // initialization expects to be able to assert that scripts are allowed to + // run. Therefore, let's fix up the message manager setup here. + if (Document* doc = docShell->GetDocument()) { + if (nsPIDOMWindowOuter* window = doc->GetWindow()) { + window->UpdateParentTarget(); + } } return NS_OK; diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp @@ -1837,6 +1837,11 @@ void nsGlobalWindowInner::InitDocumentDependentState(JSContext* aCx) { if (!mWindowGlobalChild) { mWindowGlobalChild = WindowGlobalChild::Create(this); + } else { + // If the window global existed before the window, it must've come from the + // AboutBlankInitializer + MOZ_ASSERT(NS_IsAboutBlankAllowQueryAndFragment(GetDocumentURI()), + "AboutBlankInitializer should only be used with about:blank"); } MOZ_ASSERT(!GetWindowContext()->HasBeenUserGestureActivated(), "WindowContext should always not have user gesture activation at " diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h @@ -1292,6 +1292,10 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, void SetCurrentPasteDataTransfer(mozilla::dom::DataTransfer* aDataTransfer); mozilla::dom::DataTransfer* GetCurrentPasteDataTransfer() const; + mozilla::dom::ClientSource* GetClientSource() const { + return mClientSource.get(); + } + private: RefPtr<mozilla::dom::ContentMediaController> mContentMediaController; diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp @@ -64,6 +64,7 @@ #include "nsIDOMStorageManager.h" #include "nsIDocShellTreeOwner.h" #include "nsIInterfaceRequestorUtils.h" +#include "nsILoadGroup.h" #include "nsIPermissionManager.h" #include "nsIScriptContext.h" #include "nsISecureBrowserUI.h" @@ -2498,11 +2499,10 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, } if (!newInnerWindow->mHasNotifiedGlobalCreated && mDoc) { - // We should probably notify. However if this is the, arguably bad, - // situation when we're creating a temporary non-chrome-about-blank - // document in a chrome docshell, don't notify just yet. Instead wait - // until we have a real chrome doc. - const bool isContentAboutBlankInChromeDocshell = [&] { + // We should probably notify, except if we have the initial about:blank + // in a chrome docshell, defer notification until the first non-initial + // document. + const bool isAboutBlankInChromeDocshell = [&] { if (!mDocShell) { return false; } @@ -2512,10 +2512,10 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, return false; } - return !mDoc->NodePrincipal()->IsSystemPrincipal(); + return mDoc->IsInitialDocument(); }(); - if (!isContentAboutBlankInChromeDocshell) { + if (!isAboutBlankInChromeDocshell) { newInnerWindow->mHasNotifiedGlobalCreated = true; nsContentUtils::AddScriptRunner(NewRunnableMethod( "nsGlobalWindowOuter::DispatchDOMWindowCreated", this, @@ -5900,7 +5900,13 @@ bool nsGlobalWindowOuter::CanClose() { if (viewer) { bool canClose; nsresult rv = viewer->PermitUnload(&canClose); - if (NS_SUCCEEDED(rv) && !canClose) return false; + // PermitUnload can destroy the docshell. + if (!mDocShell || mDocShell->IsBeingDestroyed()) { + return true; + } + if (NS_SUCCEEDED(rv) && !canClose) { + return false; + } } // If we still have to print, we delay the closing until print has happened. @@ -6336,7 +6342,17 @@ Selection* nsGlobalWindowOuter::GetSelectionOuter() { PresShell* presShell = mDocShell->GetPresShell(); if (!presShell) { - return nullptr; + // Force layout of the containing frame. + // layout/reftests/selection/modify-range.html goes + // through here. + EnsureSizeAndPositionUpToDate(); + if (!mDocShell) { + return nullptr; + } + presShell = mDocShell->GetPresShell(); + if (!presShell) { + return nullptr; + } } return presShell->GetCurrentSelection(SelectionType::eNormal); } diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp @@ -1407,7 +1407,7 @@ bool nsObjectLoadingContent::IsAboutBlankLoadOntoInitialAboutBlank( return NS_IsAboutBlank(aURI) && aInheritPrincipal && (!mFrameLoader || !mFrameLoader->GetExistingDocShell() || mFrameLoader->GetExistingDocShell() - ->IsAboutBlankLoadOntoInitialAboutBlank(aURI, aInheritPrincipal, + ->IsAboutBlankLoadOntoInitialAboutBlank(aURI, aPrincipalToInherit)); } diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h @@ -1114,6 +1114,8 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy { already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow(); already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome(); + virtual void UpdateParentTarget() = 0; + protected: // Lazily instantiate an about:blank document if necessary, and if // we have what it takes to do so. @@ -1122,8 +1124,6 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy { void SetChromeEventHandlerInternal( mozilla::dom::EventTarget* aChromeEventHandler); - virtual void UpdateParentTarget() = 0; - // These two variables are special in that they're set to the same // value on both the outer window and the current inner window. Make // sure you keep them in sync! diff --git a/dom/base/test/browser_bug1703472.js b/dom/base/test/browser_bug1703472.js @@ -18,14 +18,15 @@ add_task(async function bug1703472() { await BrowserTestUtils.withNewTab( BASE_URL + "file_bug1703472.html", async function (browser) { - info("Opening popup"); + info("Opening about:blank popup"); let win = await newFocusedWindow(function () { return BrowserTestUtils.synthesizeMouseAtCenter( "#openWindow", {}, browser ); - }); + }, true); + is(Services.focus.focusedWindow, win, "New window should be focused"); info("re-focusing the original window"); { diff --git a/dom/base/test/browser_multiple_popups.js b/dom/base/test/browser_multiple_popups.js @@ -62,7 +62,11 @@ async function withTestPage(popupCount, optionsOrCallback, callback) { let windows = await obs; ok(true, `We had ${popupCount} windows.`); for (let win of windows) { - if (win.document.readyState !== "complete") { + // wait for browser.xhtml being loaded to prevent shutdown hang + if ( + win.document.readyState !== "complete" || + win.location.href == "about:blank" + ) { await BrowserTestUtils.waitForEvent(win, "load"); } await BrowserTestUtils.closeWindow(win); diff --git a/dom/base/test/file_bug426646-2.html b/dom/base/test/file_bug426646-2.html @@ -21,16 +21,16 @@ function doe() { function doe2() { // Add some iframes/docshells. Session history should still work. var ifr1 = document.createElement("iframe"); - document.body.insertBefore(ifr1, document.body.firstChild); ifr1.onload = soon(doe3); + document.body.insertBefore(ifr1, document.body.firstChild); var ifr2 = document.createElement("iframe"); - document.body.insertBefore(ifr2, document.body.firstChild); ifr2.onload = soon(doe3); + document.body.insertBefore(ifr2, document.body.firstChild); var ifr3 = document.createElement("iframe"); - document.body.insertBefore(ifr3, document.body.firstChild); ifr3.onload = soon(doe3); + document.body.insertBefore(ifr3, document.body.firstChild); } var doe3_count = 0; diff --git a/dom/base/test/file_focus_shadow_dom.html b/dom/base/test/file_focus_shadow_dom.html @@ -23,7 +23,12 @@ event.stopPropagation(); } - function testTabbingThroughShadowDOMWithTabIndexes() { + function flushLayout(...iframes) { + document.body.offsetHeight; + iframes.forEach(ifr => ifr.contentDocument?.body.offsetHeight); + } + + async function testTabbingThroughShadowDOMWithTabIndexes() { var anchor = document.createElement("a"); anchor.onfocus = focusLogger; anchor.href = "#"; @@ -63,7 +68,7 @@ input2.onfocus = focusLogger; document.body.appendChild(input2); - document.body.offsetLeft; + flushLayout(shadowIframe); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input, "Should have focused input element. (3)"); @@ -114,7 +119,7 @@ document.body.innerHTML = null; } - function testTabbingThroughSimpleShadowDOM() { + async function testTabbingThroughSimpleShadowDOM() { var anchor = document.createElement("a"); anchor.onfocus = focusLogger; anchor.href = "#"; @@ -146,7 +151,7 @@ input2.onfocus = focusLogger; document.body.appendChild(input2); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM."); @@ -172,7 +177,7 @@ input2.remove(); } - function testTabbingThroughNestedShadowDOM() { + async function testTabbingThroughNestedShadowDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); var host = document.createElement("div"); @@ -200,7 +205,7 @@ var input22 = sr2.getElementById("h22"); input22.onfocus = focusLogger; - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)"); @@ -237,7 +242,7 @@ host.remove(); } - function testTabbingThroughDisplayContentsHost() { + async function testTabbingThroughDisplayContentsHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); var host = document.createElement("div"); @@ -265,7 +270,7 @@ var shadowInput4 = sr1.getElementById("shadowInput2"); shadowInput4.onfocus = focusLogger; - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)"); @@ -297,7 +302,7 @@ host1.remove(); } - function testTabbingThroughLightDOMShadowDOMLightDOM() { + async function testTabbingThroughLightDOMShadowDOMLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); @@ -319,7 +324,7 @@ a.onfocus = focusLogger; document.body.appendChild(p); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, p1, "Should have focused p1."); @@ -345,7 +350,7 @@ p.remove(); } - function testFocusableHost() { + async function testFocusableHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); @@ -369,7 +374,7 @@ a.onfocus = focusLogger; document.body.appendChild(p); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host, "Should have focused host."); @@ -395,7 +400,7 @@ p.remove(); } - function testShiftTabbingThroughFocusableHost() { + async function testShiftTabbingThroughFocusableHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); @@ -420,7 +425,7 @@ input.onfocus = focusLogger; document.body.appendChild(input); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host, "Should have focused host element. (1)"); @@ -454,7 +459,7 @@ input.remove(); } - function testTabbingThroughNestedSlot() { + async function testTabbingThroughNestedSlot() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host0 = document.createElement("div"); @@ -509,7 +514,7 @@ input11.onfocus = focusLogger; host11.appendChild(input11); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)"); @@ -541,7 +546,7 @@ host1.remove(); } - function testTabbingThroughSlotInLightDOM() { + async function testTabbingThroughSlotInLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var input0 = document.createElement("input"); @@ -563,7 +568,7 @@ input2.onfocus = focusLogger; document.body.appendChild(input2); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input0, "Should have focused input element. (1)"); @@ -596,7 +601,7 @@ input2.remove(); } - function testTabbingThroughFocusableSlotInLightDOM() { + async function testTabbingThroughFocusableSlotInLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var slot0 = document.createElement("slot"); @@ -623,7 +628,7 @@ input1.onfocus = focusLogger; document.body.appendChild(input1); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)"); @@ -661,7 +666,7 @@ input1.remove(); } - function testTabbingThroughScrollableShadowDOM() { + async function testTabbingThroughScrollableShadowDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host0 = document.createElement("div"); @@ -718,7 +723,7 @@ input1.onfocus = focusLogger; document.body.appendChild(input1); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)"); @@ -783,7 +788,7 @@ } // Bug 1604140 - function testTabbingThroughScrollableShadowHost() { + async function testTabbingThroughScrollableShadowHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); @@ -824,7 +829,7 @@ belowSecondHost.onfocus = focusLogger; document.body.appendChild(belowSecondHost); - document.body.offsetLeft; + flushLayout(); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (1)"); @@ -873,7 +878,7 @@ belowSecondHost.remove(); } - function testDeeplyNestedShadowTree() { + async function testDeeplyNestedShadowTree() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host1 = document.createElement("test-node"); var lastHost = host1; @@ -885,7 +890,8 @@ var input = document.createElement("input"); document.body.appendChild(host1); document.body.appendChild(input); - document.body.offsetLeft; + + flushLayout(); // Test shadow tree which doesn't have anything tab-focusable. host1.shadowRoot.querySelector("div").focus(); @@ -901,7 +907,9 @@ host2.insertBefore(input2, host3); var input3 = document.createElement("input"); lastHost.appendChild(input3); - document.body.offsetLeft; + + flushLayout(); + host3.shadowRoot.querySelector("div").focus(); synthesizeKey("KEY_Tab"); is(document.activeElement, input3, "Should have focused input3 element."); @@ -920,7 +928,7 @@ } // Bug 1558393 - function testBackwardsTabbingWithSlotsWithoutFocusableContent() { + async function testBackwardsTabbingWithSlotsWithoutFocusableContent() { let first = document.createElement("div"); first.tabIndex = 0; let host = document.createElement("div"); @@ -933,7 +941,8 @@ document.body.appendChild(first); document.body.appendChild(host); document.body.appendChild(second); - document.body.offsetLeft; + + flushLayout(); first.focus(); opener.is(document.activeElement, first, "First light div should have focus"); @@ -953,22 +962,22 @@ first.remove(); } - function runTest() { - - testTabbingThroughShadowDOMWithTabIndexes(); - testTabbingThroughSimpleShadowDOM(); - testTabbingThroughNestedShadowDOM(); - testTabbingThroughDisplayContentsHost(); - testTabbingThroughLightDOMShadowDOMLightDOM(); - testFocusableHost(); - testShiftTabbingThroughFocusableHost(); - testTabbingThroughNestedSlot(); - testTabbingThroughSlotInLightDOM(); - testTabbingThroughFocusableSlotInLightDOM(); - testTabbingThroughScrollableShadowDOM(); - testTabbingThroughScrollableShadowHost(); - testDeeplyNestedShadowTree(); - testBackwardsTabbingWithSlotsWithoutFocusableContent(); + async function runTest() { + + await testTabbingThroughShadowDOMWithTabIndexes(); + await testTabbingThroughSimpleShadowDOM(); + await testTabbingThroughNestedShadowDOM(); + await testTabbingThroughDisplayContentsHost(); + await testTabbingThroughLightDOMShadowDOMLightDOM(); + await testFocusableHost(); + await testShiftTabbingThroughFocusableHost(); + await testTabbingThroughNestedSlot(); + await testTabbingThroughSlotInLightDOM(); + await testTabbingThroughFocusableSlotInLightDOM(); + await testTabbingThroughScrollableShadowDOM(); + await testTabbingThroughScrollableShadowHost(); + await testDeeplyNestedShadowTree(); + await testBackwardsTabbingWithSlotsWithoutFocusableContent(); opener.didRunTests(); window.close(); diff --git a/dom/base/test/file_window_close.html b/dom/base/test/file_window_close.html @@ -10,7 +10,7 @@ function run() { if (location.search.substr(1) == "withhistory") { // An entry for the initial load, pushState, iframe and the next page. - if (history.length == 4) { + if (history.length == 3) { // We're coming back from history. function listener(m) { if (m.message.includes("Scripts may only close windows that were opened by a script.")) { diff --git a/dom/base/test/fullscreen/test_fullscreen-api-race.html b/dom/base/test/fullscreen/test_fullscreen-api-race.html @@ -43,17 +43,17 @@ const OPEN_WINDOW_FUNCS = [ function openNewTab() { return new Promise(resolve => { var win = window.open("about:blank"); - win.addEventListener("load", () => { + setTimeout(function() { resolve(win); - }, { once: true }); + }, 0); }); }, function openNewWindow() { return new Promise(resolve => { var win = window.open("about:blank", "", "width=300,height=200"); - win.addEventListener("load", () => { + setTimeout(function() { resolve(win); - }, { once: true }); + }, 0); }); } ]; diff --git a/dom/base/test/head.js b/dom/base/test/head.js @@ -1,15 +1,22 @@ -async function newFocusedWindow(trigger) { +async function newFocusedWindow(trigger, isInitialBlank = false) { let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); let delayedStartupPromise = BrowserTestUtils.waitForNewWindow(); await trigger(); let win = await winPromise; - // New windows get focused after the first paint, see bug 1262946 - await BrowserTestUtils.waitForContentEvent( - win.gBrowser.selectedBrowser, - "MozAfterPaint" - ); + // New windows get focused after the first paint, see bug 1262946, + // but this is racy for the initial about:blank + if (!isInitialBlank) { + await BrowserTestUtils.waitForContentEvent( + win.gBrowser.selectedBrowser, + "MozAfterPaint" + ); + } else { + await new Promise(res => + win.requestAnimationFrame(() => win.requestAnimationFrame(res)) + ); + } await delayedStartupPromise; return win; } diff --git a/dom/base/test/test_bug1126851.html b/dom/base/test/test_bug1126851.html @@ -14,19 +14,30 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1126851 SimpleTest.waitForExplicitFinish(); + let gotUnload = false; + function runTest() { win = window.open("about:blank", ""); - win.onload = function() { - win.onunload = function() { + win.onunload = function() { + if (gotUnload) { + SimpleTest.finish(); + } else { + gotUnload = true; ok(true, "got unload"); win.close(); - SimpleTest.finish(); } - win.onresize = function() { + } + // Bug 543435 caused there to be a resize event after `window.open` which + // is also there in other browsers. But now we must ensure to get the right + // resize event to avoid races. + let evt; + win.onresize = function(e) { + if (e == evt) { win.location.reload(); } - win.document.dispatchEvent(new win.Event("resize", { bubbles: true })); } + evt = new win.Event("resize", { bubbles: true }); + win.document.dispatchEvent(evt); } diff --git a/dom/base/test/test_navigator_cookieEnabled.html b/dom/base/test/test_navigator_cookieEnabled.html @@ -17,6 +17,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1753586 const BEHAVIOR_REJECT_TRACKER = 4; const BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN = 5; + function behavior_to_string(behavior) { + switch (behavior) { + case BEHAVIOR_REJECT: + return "BEHAVIOR_REJECT"; + case BEHAVIOR_REJECT_TRACKER: + return "BEHAVIOR_REJECT_TRACKER"; + case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN: + return "BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN"; + default: + throw new Error("unreachable"); + } + } + const TESTS = [ { cookieBehavior: BEHAVIOR_REJECT, expect: false }, { cookieBehavior: BEHAVIOR_REJECT_TRACKER, expect: true }, @@ -63,6 +76,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1753586 ); await SpecialPowers.spawn(iframe, [expect], value => { + is(content.location.href, "http://mochi.test:8888/"); is( content.navigator.cookieEnabled, value, @@ -78,6 +92,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1753586 ); await SpecialPowers.spawn(iframe, [expect], value => { + is(content.location.href, "http://example.com/"); is( content.navigator.cookieEnabled, value, @@ -95,6 +110,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1753586 ); await SpecialPowers.spawn(iframe, [expect], value => { + is(content.location.href, "http://mochi.test:8888/"); is( content.navigator.cookieEnabled, value, @@ -107,6 +123,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1753586 add_task(async function doTests() { for (let test of TESTS) { + info(`cookieBehavior ${behavior_to_string(test.cookieBehavior)} expect ${test.expect}`); + await SpecialPowers.pushPrefEnv({"set": [ ["network.cookie.cookieBehavior", test.cookieBehavior], ]}); diff --git a/dom/base/test/test_suppressed_microtasks.html b/dom/base/test/test_suppressed_microtasks.html @@ -11,46 +11,44 @@ var previousTask = -1; function test() { let win = window.open("about:blank"); - win.onload = function() { + win.onmessage = function() { + win.start = win.performance.now(); + win.didRunMicrotask = false; win.onmessage = function() { - win.start = win.performance.now(); - win.didRunMicrotask = false; - win.onmessage = function() { - ok(win.didRunMicrotask, "Should have run a microtask."); - let period = win.performance.now() - win.start; - win.opener.ok( - period < 200, - "Running a task should be fast. Took " + period + "ms."); - win.onmessage = null; - } - win.queueMicrotask(function() { win.didRunMicrotask = true; }); - win.postMessage("measurementMessage", "*"); - } - win.postMessage("initialMessage", "*"); - - const last = 500000; - for (let i = 0; i < last + 1; ++i) { - window.queueMicrotask(function() { - // Check that once microtasks are unsuppressed, they are handled in - // the correct order. - if (previousTask != i - 1) { - // Explicitly optimize out cases which pass. - ok(false, "Microtasks should be handled in order."); - } - previousTask = i; - if (i == last) { - win.close(); - SimpleTest.finish(); - } - }); + ok(win.didRunMicrotask, "Should have run a microtask."); + let period = win.performance.now() - win.start; + win.opener.ok( + period < 200, + "Running a task should be fast. Took " + period + "ms."); + win.onmessage = null; } + win.queueMicrotask(function() { win.didRunMicrotask = true; }); + win.postMessage("measurementMessage", "*"); + } + win.postMessage("initialMessage", "*"); - // Synchronous XMLHttpRequest suppresses microtasks. - var xhr = new XMLHttpRequest(); - xhr.open("GET", "slow.sjs", false); - xhr.send(); - is(previousTask, -1, "Shouldn't have run microtasks during a sync XHR."); + const last = 500000; + for (let i = 0; i < last + 1; ++i) { + window.queueMicrotask(function() { + // Check that once microtasks are unsuppressed, they are handled in + // the correct order. + if (previousTask != i - 1) { + // Explicitly optimize out cases which pass. + ok(false, "Microtasks should be handled in order."); + } + previousTask = i; + if (i == last) { + win.close(); + SimpleTest.finish(); + } + }); } + + // Synchronous XMLHttpRequest suppresses microtasks. + var xhr = new XMLHttpRequest(); + xhr.open("GET", "slow.sjs", false); + xhr.send(); + is(previousTask, -1, "Shouldn't have run microtasks during a sync XHR."); } </script> </head> diff --git a/dom/base/test/useractivation/test_useractivation_transient_consuming.html b/dom/base/test/useractivation/test_useractivation_transient_consuming.html @@ -14,6 +14,7 @@ SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation"); let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000; +let [iframe0, iframe1] = document.querySelectorAll("iframe"); function waitForEvent(aTarget, aEvent, aCallback) { return new Promise((aResolve) => { diff --git a/dom/canvas/crashtests/1441613.html b/dom/canvas/crashtests/1441613.html @@ -1,8 +1,7 @@ <!DOCTYPE html> <html class="reftest-wait"> -<iframe id="iframe" src="about:blank"></iframe> <script> -iframe.onload = function() { +function runTest() { let doc = iframe.contentDocument; let canvas = doc.createElement('canvas'); doc.body.appendChild(canvas); @@ -16,4 +15,5 @@ iframe.onload = function() { } }; </script> +<iframe id="iframe" src="about:blank" onload="runTest();"></iframe> </html> diff --git a/dom/chrome-webidl/WindowGlobalActors.webidl b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -91,11 +91,14 @@ interface WindowGlobalParent : WindowContext { // embedder is in a different process. readonly attribute boolean isProcessRoot; - // Is the document loaded in this WindowGlobalParent the initial document - // implicitly created while "creating a new browsing context". - // https://html.spec.whatwg.org/multipage/browsers.html#creating-a-new-browsing-context + // True if the document loaded in this WindowGlobalParent has the + // isInitialDocument flag set. readonly attribute boolean isInitialDocument; + // True if the document loaded in this WindowGlobalParent has the + // isUncommittedInitialDocument flag set. + readonly attribute boolean isUncommittedInitialDocument; + readonly attribute FrameLoader? rootFrameLoader; // Embedded (browser) only readonly attribute WindowGlobalChild? childActor; // in-process only diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -9,6 +9,7 @@ #include "ClientInfo.h" #include "ClientManager.h" #include "ClientState.h" +#include "mozilla/NullPrincipal.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/CanonicalBrowsingContext.h" @@ -501,7 +502,11 @@ RefPtr<ClientOpPromise> ClientOpenWindow( RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); openInfo->mBrowsingContextReadyCallback = callback; - openInfo->mOriginAttributes = principal->OriginAttributesRef(); + nsCOMPtr<nsIURI> nullPrincipalURI = NullPrincipal::CreateURI(nullptr); + nsCOMPtr<nsIPrincipal> initialPrincipal = + NullPrincipal::Create(principal->OriginAttributesRef(), nullPrincipalURI); + openInfo->mPrincipalToInheritForAboutBlank = initialPrincipal; + openInfo->mPartitionedPrincipalToInheritForAboutBlank = initialPrincipal; openInfo->mIsRemote = true; RefPtr<BrowsingContext> bc; diff --git a/dom/events/test/test_bug1539497.html b/dom/events/test/test_bug1539497.html @@ -9,11 +9,9 @@ SimpleTest.waitForExplicitFinish(); function runTest() { var win = window.open("about:blank"); - win.onload = function() { - is(win.navigator.maxTouchPoints, 5, "Should have max touch points"); - win.close(); - SimpleTest.finish(); - } + is(win.navigator.maxTouchPoints, 5, "Should have max touch points"); + win.close(); + SimpleTest.finish(); } function init() { SpecialPowers.pushPrefEnv( diff --git a/dom/events/test/test_legacy_touch_api.html b/dom/events/test/test_legacy_touch_api.html @@ -43,11 +43,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1412485 ["dom.w3c_touch_events.legacy_apis.enabled", true]]}, function() { var iframe = document.createElement("iframe"); - document.body.appendChild(iframe); iframe.onload = function() { testExistenceOfLegacyTouchAPIs(iframe.contentWindow, true); SimpleTest.finish(); } + document.body.appendChild(iframe); }); } diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp @@ -303,35 +303,33 @@ void HTMLFormElement::MaybeSubmit(Element* aSubmitter) { presShell = doc->GetPresShell(); } - // If |PresShell::Destroy| has been called due to handling the event the pres - // context will return a null pres shell. See bug 125624. Using presShell to - // dispatch the event. It makes sure that event is not handled if the window - // is being destroyed. - if (presShell) { - SubmitEventInit init; - init.mBubbles = true; - init.mCancelable = true; - init.mSubmitter = - aSubmitter ? nsGenericHTMLElement::FromNode(aSubmitter) : nullptr; - RefPtr<SubmitEvent> event = - SubmitEvent::Constructor(this, u"submit"_ns, init); - event->SetTrusted(true); - nsEventStatus status = nsEventStatus_eIgnore; - presShell->HandleDOMEventWithTarget(this, event, &status); + if (!doc->IsCurrentActiveDocument()) { + // Bug 125624 + return; } + + SubmitEventInit init; + init.mBubbles = true; + init.mCancelable = true; + init.mSubmitter = + aSubmitter ? nsGenericHTMLElement::FromNode(aSubmitter) : nullptr; + RefPtr<SubmitEvent> event = + SubmitEvent::Constructor(this, u"submit"_ns, init); + event->SetTrusted(true); + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, &status); } void HTMLFormElement::MaybeReset(Element* aSubmitter) { - // If |PresShell::Destroy| has been called due to handling the event the pres - // context will return a null pres shell. See bug 125624. Using presShell to - // dispatch the event. It makes sure that event is not handled if the window - // is being destroyed. - if (RefPtr<PresShell> presShell = OwnerDoc()->GetPresShell()) { - InternalFormEvent event(true, eFormReset); - event.mOriginator = aSubmitter; - nsEventStatus status = nsEventStatus_eIgnore; - presShell->HandleDOMEventWithTarget(this, &event, &status); + if (!OwnerDoc()->IsCurrentActiveDocument()) { + // Bug 125624 + return; } + + InternalFormEvent event(true, eFormReset); + event.mOriginator = aSubmitter; + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::DispatchDOMEvent(this, &event, nullptr, nullptr, &status); } void HTMLFormElement::Submit(ErrorResult& aRv) { aRv = DoSubmit(); } diff --git a/dom/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp @@ -72,127 +72,6 @@ static const HTMLContentCreatorFunction sHTMLContentCreatorFunctions[] = { #undef HTML_OTHER NS_NewHTMLUnknownElement}; -class SinkContext; -class HTMLContentSink; - -/** - * This class is near-OBSOLETE. It is used for about:blank only. - * Don't bother adding new stuff in this file. - */ -class HTMLContentSink : public nsContentSink, public nsIHTMLContentSink { - public: - friend class SinkContext; - - HTMLContentSink(); - - nsresult Init(Document* aDoc, nsIURI* aURI, nsISupports* aContainer, - nsIChannel* aChannel); - - // nsISupports - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentSink, nsContentSink) - - // nsIContentSink - NS_IMETHOD WillParse(void) override; - NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override; - NS_IMETHOD DidBuildModel(bool aTerminated) override; - NS_IMETHOD WillInterrupt(void) override; - void WillResume() override; - NS_IMETHOD SetParser(nsParserBase* aParser) override; - virtual void FlushPendingNotifications(FlushType aType) override; - virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override; - virtual nsISupports* GetTarget() override; - virtual bool IsScriptExecuting() override; - virtual bool WaitForPendingSheets() override; - virtual void ContinueInterruptedParsingAsync() override; - - // nsIHTMLContentSink - NS_IMETHOD OpenContainer(ElementType aNodeType) override; - NS_IMETHOD CloseContainer(ElementType aTag) override; - - protected: - virtual ~HTMLContentSink(); - - RefPtr<nsHTMLDocument> mHTMLDocument; - - RefPtr<nsGenericHTMLElement> mRoot; - RefPtr<nsGenericHTMLElement> mBody; - RefPtr<nsGenericHTMLElement> mHead; - - AutoTArray<SinkContext*, 8> mContextStack; - SinkContext* mCurrentContext; - SinkContext* mHeadContext; - - // Boolean indicating whether we've seen a <head> tag that might have had - // attributes once already. - bool mHaveSeenHead; - - // Boolean indicating whether we've notified insertion of our root content - // yet. We want to make sure to only do this once. - bool mNotifiedRootInsertion; - - nsresult FlushTags() override; - - // Routines for tags that require special handling - nsresult CloseHTML(); - nsresult OpenBody(); - nsresult CloseBody(); - - void CloseHeadContext(); - - // nsContentSink overrides - void UpdateChildCounts() override; - - void NotifyInsert(nsIContent* aContent, nsIContent* aChildContent); - void NotifyRootInsertion(); - - private: - void ContinueInterruptedParsingIfEnabled(); -}; - -class SinkContext { - public: - explicit SinkContext(HTMLContentSink* aSink); - ~SinkContext(); - - nsresult Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot, - uint32_t aNumFlushed, int32_t aInsertionPoint); - nsresult OpenBody(); - nsresult CloseBody(); - nsresult End(); - - nsresult GrowStack(); - nsresult FlushTags(); - - bool IsCurrentContainer(nsHTMLTag aTag) const; - - void DidAddContent(nsIContent* aContent); - void UpdateChildCounts(); - - private: - // Function to check whether we've notified for the current content. - // What this actually does is check whether we've notified for all - // of the parent's kids. - bool HaveNotifiedForCurrentContent() const; - - public: - HTMLContentSink* mSink; - int32_t mNotifyLevel; - - struct Node { - nsHTMLTag mType; - nsGenericHTMLElement* mContent; - uint32_t mNumFlushed; - int32_t mInsertionPoint; - - nsIContent* Add(nsIContent* child); - }; - - Node* mStack; - int32_t mStackSize; - int32_t mStackPos; -}; - nsresult NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, FromParser aFromParser, nsAtom* aIsAtom, @@ -223,702 +102,3 @@ already_AddRefed<nsGenericHTMLElement> CreateHTMLElement( return result.forget(); } - -//---------------------------------------------------------------------- - -SinkContext::SinkContext(HTMLContentSink* aSink) - : mSink(aSink), - mNotifyLevel(0), - mStack(nullptr), - mStackSize(0), - mStackPos(0) { - MOZ_COUNT_CTOR(SinkContext); -} - -SinkContext::~SinkContext() { - MOZ_COUNT_DTOR(SinkContext); - - if (mStack) { - for (int32_t i = 0; i < mStackPos; i++) { - NS_RELEASE(mStack[i].mContent); - } - delete[] mStack; - } -} - -nsresult SinkContext::Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot, - uint32_t aNumFlushed, int32_t aInsertionPoint) { - if (mStackSize < 1) { - nsresult rv = GrowStack(); - if (NS_FAILED(rv)) { - return rv; - } - } - - mStack[0].mType = aNodeType; - mStack[0].mContent = aRoot; - mStack[0].mNumFlushed = aNumFlushed; - mStack[0].mInsertionPoint = aInsertionPoint; - NS_ADDREF(aRoot); - mStackPos = 1; - - return NS_OK; -} - -bool SinkContext::IsCurrentContainer(nsHTMLTag aTag) const { - return aTag == mStack[mStackPos - 1].mType; -} - -void SinkContext::DidAddContent(nsIContent* aContent) { - if ((mStackPos == 2) && (mSink->mBody == mStack[1].mContent)) { - // We just finished adding something to the body - mNotifyLevel = 0; - } - - // If we just added content to a node for which - // an insertion happen, we need to do an immediate - // notification for that insertion. - if (0 < mStackPos && mStack[mStackPos - 1].mInsertionPoint != -1 && - mStack[mStackPos - 1].mNumFlushed < - mStack[mStackPos - 1].mContent->GetChildCount()) { - nsIContent* parent = mStack[mStackPos - 1].mContent; - mSink->NotifyInsert(parent, aContent); - mStack[mStackPos - 1].mNumFlushed = parent->GetChildCount(); - } else if (mSink->IsTimeToNotify()) { - FlushTags(); - } -} - -nsresult SinkContext::OpenBody() { - if (mStackPos <= 0) { - NS_ERROR("container w/o parent"); - - return NS_ERROR_FAILURE; - } - - nsresult rv; - if (mStackPos + 1 > mStackSize) { - rv = GrowStack(); - if (NS_FAILED(rv)) { - return rv; - } - } - - RefPtr<mozilla::dom::NodeInfo> nodeInfo = - mSink->mNodeInfoManager->GetNodeInfo( - nsGkAtoms::body, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); - NS_ENSURE_TRUE(nodeInfo, NS_ERROR_UNEXPECTED); - - // Make the content object - RefPtr<nsGenericHTMLElement> body = - NS_NewHTMLBodyElement(nodeInfo.forget(), FROM_PARSER_NETWORK); - if (!body) { - return NS_ERROR_OUT_OF_MEMORY; - } - - mStack[mStackPos].mType = eHTMLTag_body; - body.forget(&mStack[mStackPos].mContent); - mStack[mStackPos].mNumFlushed = 0; - mStack[mStackPos].mInsertionPoint = -1; - ++mStackPos; - mStack[mStackPos - 2].Add(mStack[mStackPos - 1].mContent); - - return NS_OK; -} - -bool SinkContext::HaveNotifiedForCurrentContent() const { - if (0 < mStackPos) { - nsIContent* parent = mStack[mStackPos - 1].mContent; - return mStack[mStackPos - 1].mNumFlushed == parent->GetChildCount(); - } - - return true; -} - -nsIContent* SinkContext::Node::Add(nsIContent* child) { - NS_ASSERTION(mContent, "No parent to insert/append into!"); - if (mInsertionPoint != -1) { - NS_ASSERTION(mNumFlushed == mContent->GetChildCount(), - "Inserting multiple children without flushing."); - nsCOMPtr<nsIContent> nodeToInsertBefore = - mContent->GetChildAt_Deprecated(mInsertionPoint++); - mContent->InsertChildBefore(child, nodeToInsertBefore, false, - IgnoreErrors()); - } else { - mContent->AppendChildTo(child, false, IgnoreErrors()); - } - return child; -} - -nsresult SinkContext::CloseBody() { - NS_ASSERTION(mStackPos > 0, "stack out of bounds. wrong context probably!"); - - if (mStackPos <= 0) { - return NS_OK; // Fix crash - Ref. bug 45975 or 45007 - } - - --mStackPos; - NS_ASSERTION(mStack[mStackPos].mType == eHTMLTag_body, - "Tag mismatch. Closing tag on wrong context or something?"); - - nsGenericHTMLElement* content = mStack[mStackPos].mContent; - - content->Compact(); - - // If we're in a state where we do append notifications as - // we go up the tree, and we're at the level where the next - // notification needs to be done, do the notification. - if (mNotifyLevel >= mStackPos) { - // Check to see if new content has been added after our last - // notification - - if (mStack[mStackPos].mNumFlushed < content->GetChildCount()) { - mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed); - mStack[mStackPos].mNumFlushed = content->GetChildCount(); - } - - // Indicate that notification has now happened at this level - mNotifyLevel = mStackPos - 1; - } - - DidAddContent(content); - NS_IF_RELEASE(content); - - return NS_OK; -} - -nsresult SinkContext::End() { - for (int32_t i = 0; i < mStackPos; i++) { - NS_RELEASE(mStack[i].mContent); - } - - mStackPos = 0; - - return NS_OK; -} - -nsresult SinkContext::GrowStack() { - int32_t newSize = mStackSize * 2; - if (newSize == 0) { - newSize = 32; - } - - Node* stack = new Node[newSize]; - - if (mStackPos != 0) { - memcpy(stack, mStack, sizeof(Node) * mStackPos); - delete[] mStack; - } - - mStack = stack; - mStackSize = newSize; - - return NS_OK; -} - -/** - * NOTE!! Forked into nsXMLContentSink. Please keep in sync. - * - * Flush all elements that have been seen so far such that - * they are visible in the tree. Specifically, make sure - * that they are all added to their respective parents. - * Also, do notification at the top for all content that - * has been newly added so that the frame tree is complete. - */ -nsresult SinkContext::FlushTags() { - mSink->mDeferredFlushTags = false; - uint32_t oldUpdates = mSink->mUpdatesInNotification; - - ++(mSink->mInNotification); - mSink->mUpdatesInNotification = 0; - { - // Scope so we call EndUpdate before we decrease mInNotification - mozAutoDocUpdate updateBatch(mSink->mDocument, true); - - // Start from the base of the stack (growing downward) and do - // a notification from the node that is closest to the root of - // tree for any content that has been added. - - // Note that we can start at stackPos == 0 here, because it's the caller's - // responsibility to handle flushing interactions between contexts (see - // HTMLContentSink::BeginContext). - int32_t stackPos = 0; - bool flushed = false; - uint32_t childCount; - nsGenericHTMLElement* content; - - while (stackPos < mStackPos) { - content = mStack[stackPos].mContent; - childCount = content->GetChildCount(); - - if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) { - if (mStack[stackPos].mInsertionPoint != -1) { - // We might have popped the child off our stack already - // but not notified on it yet, which is why we have to get it - // directly from its parent node. - - int32_t childIndex = mStack[stackPos].mInsertionPoint - 1; - nsIContent* child = content->GetChildAt_Deprecated(childIndex); - // Child not on stack anymore; can't assert it's correct - NS_ASSERTION(!(mStackPos > (stackPos + 1)) || - (child == mStack[stackPos + 1].mContent), - "Flushing the wrong child."); - mSink->NotifyInsert(content, child); - } else { - mSink->NotifyAppend(content, mStack[stackPos].mNumFlushed); - } - - flushed = true; - } - - mStack[stackPos].mNumFlushed = childCount; - stackPos++; - } - mNotifyLevel = mStackPos - 1; - } - --(mSink->mInNotification); - - if (mSink->mUpdatesInNotification > 1) { - UpdateChildCounts(); - } - - mSink->mUpdatesInNotification = oldUpdates; - - return NS_OK; -} - -/** - * NOTE!! Forked into nsXMLContentSink. Please keep in sync. - */ -void SinkContext::UpdateChildCounts() { - // Start from the top of the stack (growing upwards) and see if any - // new content has been appended. If so, we recognize that reflows - // have been generated for it and we should make sure that no - // further reflows occur. Note that we have to include stackPos == 0 - // to properly notify on kids of <html>. - int32_t stackPos = mStackPos - 1; - while (stackPos >= 0) { - Node& node = mStack[stackPos]; - node.mNumFlushed = node.mContent->GetChildCount(); - - stackPos--; - } - - mNotifyLevel = mStackPos - 1; -} - -nsresult NS_NewHTMLContentSink(nsIHTMLContentSink** aResult, Document* aDoc, - nsIURI* aURI, nsISupports* aContainer, - nsIChannel* aChannel) { - NS_ENSURE_ARG_POINTER(aResult); - - RefPtr<HTMLContentSink> it = new HTMLContentSink(); - - nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel); - - NS_ENSURE_SUCCESS(rv, rv); - - *aResult = it; - NS_ADDREF(*aResult); - - return NS_OK; -} - -HTMLContentSink::HTMLContentSink() - : mCurrentContext(nullptr), - mHeadContext(nullptr), - mHaveSeenHead(false), - mNotifiedRootInsertion(false) {} - -HTMLContentSink::~HTMLContentSink() { - if (mNotificationTimer) { - mNotificationTimer->Cancel(); - } - - if (mCurrentContext == mHeadContext && !mContextStack.IsEmpty()) { - // Pop off the second html context if it's not done earlier - mContextStack.RemoveLastElement(); - } - - for (int32_t i = 0, numContexts = mContextStack.Length(); i < numContexts; - i++) { - SinkContext* sc = mContextStack.ElementAt(i); - if (sc) { - sc->End(); - if (sc == mCurrentContext) { - mCurrentContext = nullptr; - } - - delete sc; - } - } - - if (mCurrentContext == mHeadContext) { - mCurrentContext = nullptr; - } - - delete mCurrentContext; - - delete mHeadContext; -} - -NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLContentSink, nsContentSink, - mHTMLDocument, mRoot, mBody, mHead) - -NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLContentSink, nsContentSink, - nsIContentSink, nsIHTMLContentSink) - -nsresult HTMLContentSink::Init(Document* aDoc, nsIURI* aURI, - nsISupports* aContainer, nsIChannel* aChannel) { - NS_ENSURE_TRUE(aContainer, NS_ERROR_NULL_POINTER); - - nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel); - if (NS_FAILED(rv)) { - return rv; - } - - aDoc->AddObserver(this); - mIsDocumentObserver = true; - mHTMLDocument = aDoc->AsHTMLDocument(); - - NS_ASSERTION(mDocShell, "oops no docshell!"); - - RefPtr<mozilla::dom::NodeInfo> nodeInfo; - nodeInfo = mNodeInfoManager->GetNodeInfo( - nsGkAtoms::html, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); - - // Make root part - mRoot = NS_NewHTMLHtmlElement(nodeInfo.forget()); - if (!mRoot) { - return NS_ERROR_OUT_OF_MEMORY; - } - - NS_ASSERTION(mDocument->GetChildCount() == 0, - "Document should have no kids here!"); - ErrorResult error; - mDocument->AppendChildTo(mRoot, false, error); - if (error.Failed()) { - return error.StealNSResult(); - } - - // Make head part - nodeInfo = mNodeInfoManager->GetNodeInfo( - nsGkAtoms::head, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); - - mHead = NS_NewHTMLHeadElement(nodeInfo.forget()); - if (NS_FAILED(rv)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - mRoot->AppendChildTo(mHead, false, IgnoreErrors()); - - mCurrentContext = new SinkContext(this); - mCurrentContext->Begin(eHTMLTag_html, mRoot, 0, -1); - mContextStack.AppendElement(mCurrentContext); - - return NS_OK; -} - -NS_IMETHODIMP -HTMLContentSink::WillParse(void) { return WillParseImpl(); } - -NS_IMETHODIMP -HTMLContentSink::WillBuildModel(nsDTDMode aDTDMode) { - WillBuildModelImpl(); - - mDocument->SetCompatibilityMode(aDTDMode == eDTDMode_full_standards - ? eCompatibility_FullStandards - : eCompatibility_NavQuirks); - - // Notify document that the load is beginning - mDocument->BeginLoad(); - - return NS_OK; -} - -NS_IMETHODIMP -HTMLContentSink::DidBuildModel(bool aTerminated) { - DidBuildModelImpl(aTerminated); - - // Reflow the last batch of content - if (mBody) { - mCurrentContext->FlushTags(); - } else if (!mLayoutStarted) { - // We never saw the body, and layout never got started. Force - // layout *now*, to get an initial reflow. - // NOTE: only force the layout if we are NOT destroying the - // docshell. If we are destroying it, then starting layout will - // likely cause us to crash, or at best waste a lot of time as we - // are just going to tear it down anyway. - bool bDestroying = true; - if (mDocShell) { - mDocShell->IsBeingDestroyed(&bDestroying); - } - - if (!bDestroying) { - StartLayout(false); - } - } - - ScrollToRef(); - - // Make sure we no longer respond to document mutations. We've flushed all - // our notifications out, so there's no need to do anything else here. - - // XXXbz I wonder whether we could End() our contexts here too, or something, - // just to make sure we no longer notify... Or is the mIsDocumentObserver - // thing sufficient? - mDocument->RemoveObserver(this); - mIsDocumentObserver = false; - - mDocument->EndLoad(); - - DropParserAndPerfHint(); - - return NS_OK; -} - -NS_IMETHODIMP -HTMLContentSink::SetParser(nsParserBase* aParser) { - MOZ_ASSERT(aParser, "Should have a parser here!"); - mParser = aParser; - return NS_OK; -} - -nsresult HTMLContentSink::CloseHTML() { - if (mHeadContext) { - if (mCurrentContext == mHeadContext) { - // Pop off the second html context if it's not done earlier - mCurrentContext = mContextStack.PopLastElement(); - } - - mHeadContext->End(); - - delete mHeadContext; - mHeadContext = nullptr; - } - - return NS_OK; -} - -nsresult HTMLContentSink::OpenBody() { - CloseHeadContext(); // do this just in case if the HEAD was left open! - - // if we already have a body we're done - if (mBody) { - return NS_OK; - } - - nsresult rv = mCurrentContext->OpenBody(); - - if (NS_FAILED(rv)) { - return rv; - } - - mBody = mCurrentContext->mStack[mCurrentContext->mStackPos - 1].mContent; - - if (mCurrentContext->mStackPos > 1) { - int32_t parentIndex = mCurrentContext->mStackPos - 2; - nsGenericHTMLElement* parent = - mCurrentContext->mStack[parentIndex].mContent; - int32_t numFlushed = mCurrentContext->mStack[parentIndex].mNumFlushed; - int32_t childCount = parent->GetChildCount(); - if (numFlushed < childCount) { - int32_t insertionPoint = - mCurrentContext->mStack[parentIndex].mInsertionPoint; - - // XXX: I have yet to see a case where numFlushed is non-zero and - // insertionPoint is not -1, but this code will try to handle - // those cases too. - - uint32_t oldUpdates = mUpdatesInNotification; - mUpdatesInNotification = 0; - if (insertionPoint != -1) { - NotifyInsert(parent, mBody); - } else { - NotifyAppend(parent, numFlushed); - } - mCurrentContext->mStack[parentIndex].mNumFlushed = childCount; - if (mUpdatesInNotification > 1) { - UpdateChildCounts(); - } - mUpdatesInNotification = oldUpdates; - } else { - MOZ_ASSERT( - false, - "This isn't supposed to happen but per bug 1782501 actually does " - "with the NoScript extension. Please debug if this assertion fails."); - mCurrentContext->mStack[parentIndex].mNumFlushed = childCount; - } - } - - StartLayout(false); - - return NS_OK; -} - -nsresult HTMLContentSink::CloseBody() { - // Flush out anything that's left - mCurrentContext->FlushTags(); - mCurrentContext->CloseBody(); - - return NS_OK; -} - -NS_IMETHODIMP -HTMLContentSink::OpenContainer(ElementType aElementType) { - nsresult rv = NS_OK; - - switch (aElementType) { - case eBody: - rv = OpenBody(); - break; - case eHTML: - if (mRoot) { - // If we've already hit this code once, then we're done - if (!mNotifiedRootInsertion) { - NotifyRootInsertion(); - } - } - break; - } - - return rv; -} - -NS_IMETHODIMP -HTMLContentSink::CloseContainer(const ElementType aTag) { - nsresult rv = NS_OK; - - switch (aTag) { - case eBody: - rv = CloseBody(); - break; - case eHTML: - rv = CloseHTML(); - break; - } - - return rv; -} - -NS_IMETHODIMP -HTMLContentSink::WillInterrupt() { return WillInterruptImpl(); } - -void HTMLContentSink::WillResume() { WillResumeImpl(); } - -void HTMLContentSink::CloseHeadContext() { - if (mCurrentContext) { - if (!mCurrentContext->IsCurrentContainer(eHTMLTag_head)) return; - - mCurrentContext->FlushTags(); - } - - if (!mContextStack.IsEmpty()) { - mCurrentContext = mContextStack.PopLastElement(); - } -} - -void HTMLContentSink::NotifyInsert(nsIContent* aContent, - nsIContent* aChildContent) { - mInNotification++; - - { - // Scope so we call EndUpdate before we decrease mInNotification - // Note that aContent->OwnerDoc() may be different to mDocument already. - MOZ_AUTO_DOC_UPDATE(aContent ? aContent->OwnerDoc() : mDocument.get(), - true); - MutationObservers::NotifyContentInserted(NODE_FROM(aContent, mDocument), - aChildContent, {}); - mLastNotificationTime = PR_Now(); - } - - mInNotification--; -} - -void HTMLContentSink::NotifyRootInsertion() { - MOZ_ASSERT(!mNotifiedRootInsertion, "Double-notifying on root?"); - NS_ASSERTION(!mLayoutStarted, - "How did we start layout without notifying on root?"); - // Now make sure to notify that we have now inserted our root. If - // there has been no initial reflow yet it'll be a no-op, but if - // there has been one we need this to get its frames constructed. - // Note that if mNotifiedRootInsertion is true we don't notify here, - // since that just means there are multiple <html> tags in the - // document; in those cases we just want to put all the attrs on one - // tag. - mNotifiedRootInsertion = true; - NotifyInsert(nullptr, mRoot); - - // Now update the notification information in all our - // contexts, since we just inserted the root and notified on - // our whole tree - UpdateChildCounts(); - - nsContentUtils::AddScriptRunner( - new nsDocElementCreatedNotificationRunner(mDocument)); -} - -void HTMLContentSink::UpdateChildCounts() { - uint32_t numContexts = mContextStack.Length(); - for (uint32_t i = 0; i < numContexts; i++) { - SinkContext* sc = mContextStack.ElementAt(i); - - sc->UpdateChildCounts(); - } - - mCurrentContext->UpdateChildCounts(); -} - -void HTMLContentSink::FlushPendingNotifications(FlushType aType) { - // Only flush tags if we're not doing the notification ourselves - // (since we aren't reentrant) - if (!mInNotification) { - // Only flush if we're still a document observer (so that our child counts - // should be correct). - if (mIsDocumentObserver) { - if (aType >= FlushType::ContentAndNotify) { - FlushTags(); - } - } - if (aType >= FlushType::EnsurePresShellInitAndFrames) { - // Make sure that layout has started so that the reflow flush - // will actually happen. - StartLayout(true); - } - } -} - -nsresult HTMLContentSink::FlushTags() { - if (!mNotifiedRootInsertion) { - NotifyRootInsertion(); - return NS_OK; - } - - return mCurrentContext ? mCurrentContext->FlushTags() : NS_OK; -} - -void HTMLContentSink::SetDocumentCharset(NotNull<const Encoding*> aEncoding) { - MOZ_ASSERT_UNREACHABLE("<meta charset> case doesn't occur with about:blank"); -} - -nsISupports* HTMLContentSink::GetTarget() { return ToSupports(mDocument); } - -bool HTMLContentSink::IsScriptExecuting() { return IsScriptExecutingImpl(); } - -void HTMLContentSink::ContinueInterruptedParsingIfEnabled() { - if (mParser && mParser->IsParserEnabled()) { - static_cast<nsIParser*>(mParser.get())->ContinueInterruptedParsing(); - } -} - -bool HTMLContentSink::WaitForPendingSheets() { - return nsContentSink::WaitForPendingSheets(); -} - -void HTMLContentSink::ContinueInterruptedParsingAsync() { - nsCOMPtr<nsIRunnable> ev = NewRunnableMethod( - "HTMLContentSink::ContinueInterruptedParsingIfEnabled", this, - &HTMLContentSink::ContinueInterruptedParsingIfEnabled); - mHTMLDocument->Dispatch(ev.forget()); -} diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp @@ -334,16 +334,6 @@ nsresult nsHTMLDocument::StartDocumentLoad( loadAsHtml5 = false; } - // TODO: Proper about:blank treatment is bug 543435 - if (loadAsHtml5 && view) { - // mDocumentURI hasn't been set, yet, so get the URI from the channel - nsCOMPtr<nsIURI> uri; - aChannel->GetURI(getter_AddRefs(uri)); - if (NS_IsAboutBlankAllowQueryAndFragment(uri)) { - loadAsHtml5 = false; - } - } - nsresult rv = Document::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, aReset); if (NS_FAILED(rv)) { @@ -371,6 +361,17 @@ nsresult nsHTMLDocument::StartDocumentLoad( } } else if (mViewSource && !html) { html5Parser->MarkAsNotScriptCreated("view-source-xml"); + } else if (view && NS_IsAboutBlank(uri)) { + // Sadness: There are Chromium-originating WPTs that assume that + // as soon as `iframe.contentWindow.location.href == "about:blank"`, + // the about:blank DOM exists even for _non-initial_ navigations to + // about:blank. Since Chromium-originating WPTs manage to expect this, + // chances are that Web content might expect this as well, and the + // expectation was valid in Gecko previously. Therefore, let's + // special-case even _non-initial_ about:blank. + // /content-security-policy/inheritance/history-iframe.sub.html + // /content-security-policy/inheritance/window-open-local-after-network-scheme.sub.html + html5Parser->MarkAsNotScriptCreated("about-blank"); } else { html5Parser->MarkAsNotScriptCreated(aCommand); } @@ -487,15 +488,8 @@ nsresult nsHTMLDocument::StartDocumentLoad( mParser->SetContentSink(xmlsink); } } else { - if (loadAsHtml5) { - html5Parser->Initialize(this, uri, docShell, aChannel); - } else { - // about:blank *only* - nsCOMPtr<nsIHTMLContentSink> htmlsink; - NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri, docShell, - aChannel); - mParser->SetContentSink(htmlsink); - } + MOZ_ASSERT(loadAsHtml5); + html5Parser->Initialize(this, uri, docShell, aChannel); } // parser the content of the URI diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp @@ -451,9 +451,13 @@ bool BrowserChild::DoUpdateZoomConstraints( } nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent, - WindowGlobalChild* aInitialWindowChild) { - MOZ_ASSERT_IF(aInitialWindowChild, - aInitialWindowChild->BrowsingContext() == mBrowsingContext); + WindowGlobalChild* aInitialWindowChild, + nsIOpenWindowInfo* aOpenWindowInfo) { + MOZ_ASSERT(aOpenWindowInfo, "Must have openwindowinfo"); + MOZ_ASSERT(aInitialWindowChild, "Must have window child"); + MOZ_ASSERT(aInitialWindowChild->BrowsingContext() == mBrowsingContext); + MOZ_ASSERT(aInitialWindowChild->DocumentPrincipal() == + aOpenWindowInfo->PrincipalToInheritForAboutBlank()); nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(this); mPuppetWidget = static_cast<PuppetWidget*>(widget.get()); @@ -465,7 +469,12 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent, widget::InitData()); mWebBrowser = nsWebBrowser::Create(this, mPuppetWidget, mBrowsingContext, - aInitialWindowChild); + aInitialWindowChild, aOpenWindowInfo); + if (!mWebBrowser) { + // At least the JS recursion depth check can cause an early return + // here. dom/base/crashtests/1419902.html + return NS_ERROR_FAILURE; + } nsIWebBrowser* webBrowser = mWebBrowser; mWebNav = do_QueryInterface(webBrowser); diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h @@ -48,6 +48,7 @@ class nsIWebProgress; class nsPIDOMWindowInner; class nsWebBrowser; class nsDocShellLoadState; +class nsIOpenWindowInfo; template <typename T> class nsTHashtable; @@ -168,7 +169,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor, bool aIsTopLevel); MOZ_CAN_RUN_SCRIPT nsresult Init(mozIDOMWindowProxy* aParent, - WindowGlobalChild* aInitialWindowChild); + WindowGlobalChild* aInitialWindowChild, + nsIOpenWindowInfo* aOpenWindowInfo); /** Return a BrowserChild with the given attributes. */ static already_AddRefed<BrowserChild> Create( diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp @@ -139,6 +139,7 @@ #include "nsISimpleEnumerator.h" #include "nsIStringBundle.h" #include "nsIURIMutator.h" +#include "nsOpenWindowInfo.h" #include "nsQueryObject.h" #include "nsRefreshDriver.h" #include "nsSandboxFlags.h" @@ -962,6 +963,8 @@ nsresult ContentChild::ProvideWindowCommon( bool aForceNoReferrer, bool aIsPopupRequested, nsDocShellLoadState* aLoadState, bool* aWindowIsNew, BrowsingContext** aReturn) { + MOZ_ASSERT(aOpenWindowInfo, "Must have openwindowinfo"); + *aReturn = nullptr; nsAutoCString features(aFeatures); @@ -1089,18 +1092,26 @@ nsresult ContentChild::ProvideWindowCommon( browsingContext->EnsureAttached(); - // The initial about:blank document we generate within the nsDocShell will - // almost certainly be replaced at some point. Unfortunately, getting the - // principal right here causes bugs due to frame scripts not getting events - // they expect, due to the real initial about:blank not being created yet. - // - // For this reason, we intentionally mispredict the initial principal here, so - // that we can act the same as we did before when not predicting a result - // principal. This `PWindowGlobal` will almost immediately be destroyed. + // Since bug 543435, aOpenWindowInfo has the right principal to load for the + // initial document. But creating windows is complicated and our code expects + // to setup the initial window global and browser pairs before the document. + // But without a document and window, we cannot use the + // WindowGlobalActor::WindowInitializer to correctly initialize a window + // global. So we must use the AboutBlankInitializer to create a dummy window + // global with corresponding document and window. Then we immediately set the + // correct principal which causes the creation of a new document, window and + // window global. These use the dummy window to correctly initialize the + // window global via a WindowInitializer. nsCOMPtr<nsIPrincipal> initialPrincipal = NullPrincipal::Create(browsingContext->OriginAttributesRef()); WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer( browsingContext, initialPrincipal); + nsCOMPtr<nsIOpenWindowInfo> openWindowInfoInitialPrincipal; + // Init code expects window global and open window info to have matching + // principals. + aOpenWindowInfo->CloneWithPrincipals( + initialPrincipal, initialPrincipal, + getter_AddRefs(openWindowInfoInitialPrincipal)); RefPtr<WindowGlobalChild> windowChild = WindowGlobalChild::CreateDisconnected(windowInit); @@ -1150,10 +1161,19 @@ nsresult ContentChild::ProvideWindowCommon( // tell that NotNull<RefPtr<BrowserChild>> is a strong pointer. RefPtr<nsPIDOMWindowOuter> parentWindow = parent ? parent->GetDOMWindow() : nullptr; - if (NS_FAILED(MOZ_KnownLive(newChild)->Init(parentWindow, windowChild))) { + if (NS_FAILED(MOZ_KnownLive(newChild)->Init( + parentWindow, windowChild, openWindowInfoInitialPrincipal))) { return NS_ERROR_ABORT; } + // Now change the principal to what it should be according to aOpenWindowInfo. + // This creates a new document and the timing is quite fragile. + NS_ENSURE_TRUE(browsingContext->GetDOMWindow(), NS_ERROR_ABORT); + browsingContext->GetDOMWindow()->SetInitialPrincipal( + aOpenWindowInfo->PrincipalToInheritForAboutBlank(), + aOpenWindowInfo->PolicyContainerToInheritForAboutBlank(), + aOpenWindowInfo->CoepToInheritForAboutBlank()); + // Set to true when we're ready to return from this function. bool ready = false; @@ -1938,8 +1958,11 @@ mozilla::ipc::IPCResult ContentChild::RecvConstructBrowser( MOZ_RELEASE_ASSERT(browserChild->mBrowsingContext->Id() == aWindowInit.context().mBrowsingContextId); - if (NS_WARN_IF( - NS_FAILED(browserChild->Init(/* aOpener */ nullptr, windowChild)))) { + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + openWindowInfo->mPrincipalToInheritForAboutBlank = aWindowInit.principal(); + + if (NS_WARN_IF(NS_FAILED(browserChild->Init(/* aOpener */ nullptr, + windowChild, openWindowInfo)))) { return IPC_FAIL(browserChild, "BrowserChild::Init failed"); } diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp @@ -1438,6 +1438,8 @@ already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser( return nullptr; } + windowParent->Init(); + // Tell the content process to set up its PBrowserChild. bool ok = constructorSender->SendConstructBrowser( std::move(childEp), std::move(windowEp), tabId, @@ -1452,8 +1454,6 @@ already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser( // CanonicalBrowsingContext. aBrowsingContext->Canonical()->SetCurrentBrowserParent(browserParent); - windowParent->Init(); - RefPtr<BrowserHost> browserHost = new BrowserHost(browserParent); browserParent->SetOwnerElement(aFrameElement); return browserHost.forget(); @@ -5166,10 +5166,13 @@ mozilla::ipc::IPCResult ContentParent::CommonCreateWindow( openInfo->mIsForPrinting = aForPrinting; openInfo->mIsForWindowDotPrint = aForWindowDotPrint; openInfo->mNextRemoteBrowser = aNextRemoteBrowser; - openInfo->mOriginAttributes = aOriginAttributes; openInfo->mIsTopLevelCreatedByWebContent = aIsTopLevelCreatedByWebContent; openInfo->mHasValidUserGestureActivation = aUserActivation; openInfo->mTextDirectiveUserActivation = aTextDirectiveUserActivation; + openInfo->mPrincipalToInheritForAboutBlank = aTriggeringPrincipal; + MOZ_ASSERT( + aOriginAttributes == openInfo->GetOriginAttributes(), + "Open window info gets the expected origin attributes from principal"); MOZ_ASSERT_IF(aForWindowDotPrint, aForPrinting); diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh @@ -251,6 +251,8 @@ struct DocShellLoadStateInit bool IsMetaRefresh; bool IsCaptivePortalTab; + + bool IsInitialAboutBlankHandlingProhibited; }; struct TimedChannelInfo diff --git a/dom/ipc/PWindowGlobal.ipdl b/dom/ipc/PWindowGlobal.ipdl @@ -147,6 +147,9 @@ parent: /// Send down initial document bit to the parent. [LazySend] async SetIsInitialDocument(bool aIsInitialDocument); + /// Indicate that the initial document is being navigated to. + [LazySend] async CommitToInitialDocument(); + // Attempts to perform a "Web Share". async Share(IPCWebShareData aData) returns (nsresult rv); diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp @@ -64,6 +64,10 @@ WindowGlobalInit WindowGlobalActor::BaseInitializer( WindowGlobalInit WindowGlobalActor::AboutBlankInitializer( dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal) { + MOZ_DIAGNOSTIC_ASSERT( + aPrincipal && aPrincipal->GetIsNullPrincipal(), + "AboutBlankInitializer is a dummy that should not be web-exposed"); + WindowGlobalInit init = BaseInitializer(aBrowsingContext, nsContentUtils::GenerateWindowId(), nsContentUtils::GenerateWindowId()); @@ -72,6 +76,7 @@ WindowGlobalInit WindowGlobalActor::AboutBlankInitializer( init.storagePrincipal() = aPrincipal; (void)NS_NewURI(getter_AddRefs(init.documentURI()), "about:blank"); init.isInitialDocument() = true; + init.isUncommittedInitialDocument() = true; return init; } @@ -89,6 +94,7 @@ WindowGlobalInit WindowGlobalActor::WindowInitializer( Document* doc = aWindow->GetDocument(); init.isInitialDocument() = doc->IsInitialDocument(); + init.isUncommittedInitialDocument() = doc->IsUncommittedInitialDocument(); init.blockAllMixedContent() = doc->GetBlockAllMixedContent(false); init.upgradeInsecureRequests() = doc->GetUpgradeInsecureRequests(false); init.sandboxFlags() = doc->GetSandboxFlags(); diff --git a/dom/ipc/WindowGlobalActor.h b/dom/ipc/WindowGlobalActor.h @@ -11,6 +11,8 @@ #include "mozilla/dom/JSActor.h" #include "mozilla/dom/JSActorManager.h" #include "mozilla/dom/WindowGlobalTypes.h" +#include "nsILoadInfo.h" +#include "nsIOpenWindowInfo.h" #include "nsISupports.h" #include "nsIURI.h" #include "nsString.h" diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp @@ -58,6 +58,8 @@ WindowGlobalChild::WindowGlobalChild(dom::WindowContext* aWindowContext, mDocumentURI(aDocumentURI) { MOZ_DIAGNOSTIC_ASSERT(mWindowContext); MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal); + MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == + mWindowContext->IsLocalIP()); if (!mDocumentURI) { NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp @@ -100,6 +100,7 @@ WindowGlobalParent::WindowGlobalParent( uint64_t aOuterWindowId, FieldValues&& aInit) : WindowContext(aBrowsingContext, aInnerWindowId, aOuterWindowId, std::move(aInit)), + mIsUncommittedInitialDocument(false), mSandboxFlags(0), mDocumentHasLoaded(false), mDocumentHasUserInteracted(false), @@ -127,6 +128,7 @@ already_AddRefed<WindowGlobalParent> WindowGlobalParent::CreateDisconnected( wgp->mDocumentPrincipal = aInit.principal(); wgp->mDocumentURI = aInit.documentURI(); wgp->mIsInitialDocument = Some(aInit.isInitialDocument()); + wgp->mIsUncommittedInitialDocument = aInit.isUncommittedInitialDocument(); wgp->mBlockAllMixedContent = aInit.blockAllMixedContent(); wgp->mUpgradeInsecureRequests = aInit.upgradeInsecureRequests(); wgp->mSandboxFlags = aInit.sandboxFlags(); diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h @@ -157,6 +157,8 @@ class WindowGlobalParent final : public WindowContext, return mIsInitialDocument.isSome() && mIsInitialDocument.value(); } + bool IsUncommittedInitialDocument() { return mIsUncommittedInitialDocument; } + already_AddRefed<mozilla::dom::Promise> PermitUnload( PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv); @@ -275,6 +277,12 @@ class WindowGlobalParent final : public WindowContext, } mIsInitialDocument = Some(aIsInitialDocument); + mIsUncommittedInitialDocument = aIsInitialDocument; + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvCommitToInitialDocument() { + MOZ_ASSERT(mIsInitialDocument.isSome() && mIsInitialDocument.value()); + mIsUncommittedInitialDocument = false; return IPC_OK(); } mozilla::ipc::IPCResult RecvUpdateDocumentSecurityInfo( @@ -381,6 +389,8 @@ class WindowGlobalParent final : public WindowContext, Maybe<bool> mIsInitialDocument; + bool mIsUncommittedInitialDocument; + // True if this window has a "beforeunload" event listener. bool mHasBeforeUnload; diff --git a/dom/ipc/WindowGlobalTypes.ipdlh b/dom/ipc/WindowGlobalTypes.ipdlh @@ -28,6 +28,7 @@ struct WindowGlobalInit nullable nsIURI documentURI; bool isInitialDocument; + bool isUncommittedInitialDocument; bool blockAllMixedContent; bool upgradeInsecureRequests; uint32_t sandboxFlags; diff --git a/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js @@ -11,7 +11,6 @@ declTest("destroy actor by iframe remove", { let frame = content.document.createElement("iframe"); frame.id = "frame"; content.document.body.appendChild(frame); - await ContentTaskUtils.waitForEvent(frame, "load"); is(content.window.frames.length, 1, "There should be an iframe."); let child = frame.contentWindow.windowGlobalChild; let actorChild = child.getActor("TestWindow"); diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp @@ -880,6 +880,8 @@ void nsJSChannel::EvaluateScript() { // return from the javascript: URL... mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED; } + // Note: `docShell` may have been destroyed in `PermitUnload`, so don't + // add uses of `docShell` later in this method! } } diff --git a/dom/media/test/test_suspend_media_by_inactive_docshell.html b/dom/media/test/test_suspend_media_by_inactive_docshell.html @@ -43,19 +43,29 @@ function mediaSuspendedStateShouldEqualTo(expected) { is(result, expected, `media's suspended state is correct`); } -function setDocShellActive(isActive) { - const win = SpecialPowers.wrap(window); - const docShell = win.docShell; - let p = new Promise(r => { - docShell.chromeEventHandler.addEventListener("MozMediaSuspendChanged", - () => { - mediaSuspendedStateShouldEqualTo(!isActive); +function waitForEvent(target, name, condition) { + return new Promise(r => { + const listener = function(event) { + if (condition(event)) { + target.removeEventListener(name, listener); r(); - }, {once : true} - ); + } + }; + target.addEventListener(name, listener); }); +} - SpecialPowers.spawnChrome([isActive], active => { +async function setDocShellActive(isActive) { + const win = SpecialPowers.wrap(window); + const docShell = win.docShell; + + const video = document.getElementById("testVideo"); + let mediaSuspendChangedPromise = waitForEvent( + docShell.chromeEventHandler, "MozMediaSuspendChanged", + event => event.target === video + ); + + await SpecialPowers.spawnChrome([isActive], active => { // This flag is used to prevent media from playing when docShell is // inactive. After updating `docshell.isActive`, it would suspend/resume // media and we // wait suspending/resuming finishing by listening to @@ -64,7 +74,9 @@ function setDocShellActive(isActive) { this.browsingContext.top.isActive = active; }); - return p; + await mediaSuspendChangedPromise; + + mediaSuspendedStateShouldEqualTo(!isActive); } </script> diff --git a/dom/media/webspeech/synth/test/test_bfcache.html b/dom/media/webspeech/synth/test/test_bfcache.html @@ -34,10 +34,8 @@ SpecialPowers.pushPrefEnv({ set: [ ['media.webspeech.synth.force_global_queue', true]] }, function() { testWin = window.open("about:blank", "testWin"); - testWin.onload = function(e) { - waitForVoices(testWin) - .then(() => testWin.location = "file_bfcache_page1.html") - }; + waitForVoices(testWin) + .then(() => testWin.location = "file_bfcache_page1.html") }); </script> diff --git a/dom/navigation/Navigation.cpp b/dom/navigation/Navigation.cpp @@ -294,9 +294,7 @@ bool SupportsInterface(nsISupports* aSupports) { bool Navigation::HasEntriesAndEventsDisabled() const { Document* doc = GetAssociatedDocument(); return !doc || !doc->IsCurrentActiveDocument() || - doc->GetInitialStatus() == Document::InitialStatus::IsInitial || - doc->GetInitialStatus() == - Document::InitialStatus::IsInitialButExplicitlyOpened || + doc->IsEverInitialDocument() || doc->GetPrincipal()->GetIsNullPrincipal() || // We explicitly disallow documents loaded through multipart and script // channels from having events or entries. See bug 1996218 and bug diff --git a/dom/serviceworkers/test/performance/test_update.html b/dom/serviceworkers/test/performance/test_update.html @@ -76,7 +76,7 @@ let promiseSW = new Promise((resolve, reject) => { window.onmessage = (e) => { - is(e.data, "updatefound"); + is(e.data, "updatefound", "got updatefound message"); resolve(performance.now()); }; }); @@ -93,14 +93,15 @@ }; }); - is(callbackCounter, 0); + is(callbackCounter, 0, "no onupdatefound calls"); await waitForState(reg.installing, "activated"); - is(callbackCounter, 1); + is(callbackCounter, 1, "one onupdatefound call"); let content = document.getElementById("content"); let iframe = document.createElement("iframe"); - content.appendChild(iframe); iframe.setAttribute("src", "./fwd_messages_upward.html"); + content.appendChild(iframe); + await new Promise(res => iframe.onload = res); let begin_ts = performance.now(); await reg.update(); diff --git a/dom/svg/crashtests/1329093-2.html b/dom/svg/crashtests/1329093-2.html @@ -9,11 +9,9 @@ Loading the below iframe should not crash Firefox in Stylo mode. <circle cx="50" cy="50" r="40" stroke="yellow" stroke-width="2" fill="green"/> </svg> -<iframe src="" id="myFrame"></iframe> -<div style="display: none" id="triggerRestyle"></div> <script type="text/javascript"> -let frame = document.getElementById("myFrame"); -frame.onload = function() { +function runTest() { + let frame = document.getElementById("myFrame"); let baz = frame.contentDocument.adoptNode(document.getElementById("svgElement")); frame.contentDocument.body.appendChild(baz); baz = null; @@ -24,5 +22,7 @@ frame.onload = function() { document.documentElement.className = ""; } </script> +<iframe src="" id="myFrame" onload="runTest();"></iframe> +<div style="display: none" id="triggerRestyle"></div> </body> </html> diff --git a/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js @@ -1,91 +1,64 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -function test() { - // initialization - waitForExplicitFinish(); - let windowsToClose = []; - let innerID; - let beforeEvents; - let afterEvents; - let storageShouldOccur; - let testURI = - "http://example.com/browser/dom/tests/browser/test-console-api.html"; - let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( - Ci.nsIConsoleAPIStorage - ); - function getInnerWindowId(aWindow) { - return aWindow.windowGlobalChild.innerWindowId; - } +const testURI = + "http://example.com/browser/dom/tests/browser/test-console-api.html"; - function whenNewWindowLoaded(aPrivate, aCallback) { - BrowserTestUtils.openNewBrowserWindow({ - private: aPrivate, - }).then(aCallback); - } +function getInnerWindowId(aWindow) { + return aWindow.windowGlobalChild.innerWindowId; +} - function doTest(aIsPrivateMode, aWindow, aCallback) { - BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then( - () => { - function observe() { - afterEvents = ConsoleAPIStorage.getEvents(innerID); - is( - beforeEvents.length == afterEvents.length - 1, - storageShouldOccur, - "storage should" + (storageShouldOccur ? "" : " not") + " occur" - ); +async function doTest(aIsPrivateMode) { + const window = await BrowserTestUtils.openNewBrowserWindow({ + private: aIsPrivateMode, + }); - executeSoon(function () { - ConsoleAPIStorage.removeLogEventListener(observe); - aCallback(); - }); - } + const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage + ); - ConsoleAPIStorage.addLogEventListener( - observe, - aWindow.document.nodePrincipal - ); - aWindow.nativeConsole.log( - "foo bar baz (private: " + aIsPrivateMode + ")" - ); - } - ); + const innerID = getInnerWindowId(window); + const beforeEvents = ConsoleAPIStorage.getEvents(innerID); + BrowserTestUtils.startLoadingURIString( + window.gBrowser.selectedBrowser, + testURI + ); - // We expect that console API messages are always stored. - storageShouldOccur = true; - innerID = getInnerWindowId(aWindow); - beforeEvents = ConsoleAPIStorage.getEvents(innerID); - BrowserTestUtils.startLoadingURIString( - aWindow.gBrowser.selectedBrowser, - testURI + await BrowserTestUtils.browserLoaded(window.gBrowser.selectedBrowser, { + wantLoad: testURI, + }); + + const consoleEventPromise = new Promise(res => { + function listener() { + ConsoleAPIStorage.removeLogEventListener(listener); + res(); + } + ConsoleAPIStorage.addLogEventListener( + listener, + window.document.nodePrincipal ); - } + }); - function testOnWindow(aPrivate, aCallback) { - whenNewWindowLoaded(aPrivate, function (aWin) { - windowsToClose.push(aWin); - // execute should only be called when need, like when you are opening - // web pages on the test. If calling executeSoon() is not necesary, then - // call whenNewWindowLoaded() instead of testOnWindow() on your test. - executeSoon(() => aCallback(aWin)); - }); - } + window.nativeConsole.log("foo bar baz (private: " + aIsPrivateMode + ")"); - // this function is called after calling finish() on the test. - registerCleanupFunction(function () { - windowsToClose.forEach(function (aWin) { - aWin.close(); - }); - }); + await consoleEventPromise; - // test first when not on private mode - testOnWindow(false, function (aWin) { - doTest(false, aWin, function () { - // then test when on private mode - testOnWindow(true, function (aWin) { - doTest(true, aWin, finish); - }); - }); - }); + const afterEvents = ConsoleAPIStorage.getEvents(innerID); + // We expect that console API messages are always stored. + is( + beforeEvents.length == afterEvents.length - 1, + true, + "storage should occur" + ); + + await BrowserTestUtils.closeWindow(window); } + +add_task(async function test_console_storage() { + await doTest(false); +}); + +add_task(async function test_console_storage_private_browsing() { + await doTest(true); +}); diff --git a/dom/tests/browser/browser_alert_from_about_blank.js b/dom/tests/browser/browser_alert_from_about_blank.js @@ -16,18 +16,32 @@ add_task(async function test_check_alert_from_blank() { await SpecialPowers.spawn(browser, [], () => { let button = content.document.createElement("button"); button.addEventListener("click", () => { + info("Button onclick: opening about:blank"); let newWin = content.open( "about:blank", "", "popup,width=600,height=600" ); + // Start an async navigation that will close the alert + // Previously this wasn't needed as the initial about:blank + // was followed by an async about:blank load. See Bug 543435 + newWin.location = "/blank"; newWin.alert("Alert from the popup."); + info("Button onclick: finished"); }); content.document.body.append(button); + info( + "Added alert-from-about-blank-popup trigger button to " + + content.document.location.href + ); }); + let newWinPromise = BrowserTestUtils.waitForNewWindow(); await BrowserTestUtils.synthesizeMouseAtCenter("button", {}, browser); + + // otherWin is the same as newWin let otherWin = await newWinPromise; + Assert.equal( otherWin.gBrowser.currentURI?.spec, "about:blank", diff --git a/dom/tests/mochitest/bugs/test_bug346659.html b/dom/tests/mochitest/bugs/test_bug346659.html @@ -12,7 +12,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=346659 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=346659">Mozilla Bug 346659</a> <p id="display"></p> <div id="content" style="display: none"> - + </div> <pre id="test"> <script type="application/javascript"> @@ -40,7 +40,7 @@ async function handleCmd(evt) { } catch (e) { // Not json, so it should be a test result. We don't need to set up test. return false; - } + } if ("load" in cmd) { var testNum = cmd.load; @@ -146,7 +146,7 @@ async function messageReceiver(evt) { is(testResult, "4", "Props on window opened from new window should be preserved when writing"); break; case 5: - is(testResult, "undefined", "Props on new window's child should go away when loading"); + is(testResult, "5", "Props on new window's child should be preserved when loading"); break; case 6: is(testResult, "6", "Props on new window's child should not go away when writing"); @@ -155,7 +155,7 @@ async function messageReceiver(evt) { is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay"); break; case 9: - is(testResult, "undefined", "Props on different-domain new window's child should go away when loading"); + is(testResult, "9", "Props on different-domain new window's child should be preserved when loading"); break; case 11: is(testResult, "undefined", "Props on same-domain window opened from different-domain new window should go away when loading"); diff --git a/dom/tests/mochitest/chrome/test_resize_move_windows.xhtml b/dom/tests/mochitest/chrome/test_resize_move_windows.xhtml @@ -215,7 +215,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=565541 var w = window.open("file_resize_move_windows_1.html", '', 'width=170,height=170,screenX=25,screenY=25'); - await SimpleTest.promiseWaitForCondition(() => w.document.readyState == "complete"); + await SimpleTest.promiseWaitForCondition(() => + w.document.readyState == "complete" && w.location.href != "about:blank" + ); SimpleTest.waitForFocus(function() { w.wrappedJSObject.ok = SimpleTest.ok; w.wrappedJSObject.check(); diff --git a/dom/tests/mochitest/dom-level0/test_location.html b/dom/tests/mochitest/dom-level0/test_location.html @@ -2,13 +2,13 @@ <html> <head> <title>Test for location object behaviors</title> - <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"> - + </div> <pre id="test"> <script class="testbody" type="text/javascript"> diff --git a/dom/tests/mochitest/general/mochitest.toml b/dom/tests/mochitest/general/mochitest.toml @@ -199,7 +199,7 @@ skip-if = [ skip-if = [ "os == 'android'", # Window sizes cannot be controled on android; Windows "os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland'", # Decorations arrive async and mess up the resize intermittently on automation. - "os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && opt", # Bug 1604152 + "os == 'linux' && os_version == '24.04' && arch == 'x86_64'", # Bug 1604152, 1974454 ] ["test_resource_timing.html"] diff --git a/dom/tests/mochitest/general/test_bug631440.html b/dom/tests/mochitest/general/test_bug631440.html @@ -12,7 +12,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=631440 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=631440">Mozilla Bug 631440</a> <p id="display"></p> <div id="content" style="display: none"> - + </div> <pre id="test"> <script type="application/javascript"> @@ -20,10 +20,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=631440 SimpleTest.waitForExplicitFinish(); var w = window.open("about:blank"); -w.addEventListener("load", function() { - w.close(); - SimpleTest.finish(); -}); try { w.history.pushState(null, "title", "pushState.html"); @@ -33,6 +29,9 @@ catch (e) { ok(true, "Should have thrown a security exception"); } +w.close(); +SimpleTest.finish(); + </script> </pre> </body> diff --git a/dom/tests/mochitest/general/test_resizeby.html b/dom/tests/mochitest/general/test_resizeby.html @@ -12,7 +12,6 @@ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1369627">Mozilla Bug 1369627</a> <p id="display"></p> <div id="content"> - <button id="clicky">clicky</button> </div> <pre id="test"> </pre> @@ -22,41 +21,37 @@ add_task(async function resizeby() { await SimpleTest.promiseFocus(); - let clicky = document.querySelector("#clicky"); - - let popupPromise = new Promise(resolve => { - let linkclick = () => { - clicky.removeEventListener('click', linkclick); - let popup = window.open("about:blank", "_blank", "width=500,height=500"); - function loaded() { - is(popup.innerHeight, 500, "starting height is 500"); - is(popup.innerWidth, 500, "starting width in 500"); - - popup.resizeBy(50, 0); - - // We resized synchronously. If this happened, we sometimes won't fire - // an resize event, so we resolve immediately. - if (popup.innerWidth == 550) { - resolve(popup); - return; - } - let popupresize = () => { - info(`Got resize event ${popup.innerWidth}\n`); - resolve(popup); - }; - popup.addEventListener('resize', popupresize, { once: true }); - }; - popup.addEventListener("load", loaded, { once: true }) - }; - - clicky.addEventListener('click', linkclick); + const popup = window.open("about:blank", "_blank", "width=500,height=500"); + + is(popup.document.location.href, "about:blank"); + is(popup.document.readyState, "complete"); + + // This fails flakily, see bug 1974454 + is(popup.innerHeight, 500, "starting height is 500"); + is(popup.innerWidth, 500, "starting width in 500"); + + // When this test was written, it said that resizeBy can occur sync without a resize event. + // Since bug 543435, window.open can fire a resize event. + // So there can be up to two resize, we only care for the correct innerWidth. + const resizeByDone = new Promise(resolve => { + const checkResized = () => { + info(`Checking target width: ${popup.innerWidth} vs ${550}`); + if (popup.innerWidth == 550) { + popup.removeEventListener("resize", checkResized); + resolve(); + } + } + popup.addEventListener("resize", checkResized); + checkResized(); }); - synthesizeMouseAtCenter(clicky, {}, window); + popup.resizeBy(50, 0); + + await resizeByDone; - let popup = await popupPromise; is(popup.innerHeight, 500, "ending height is 500"); is(popup.innerWidth, 550, "ending width is 550"); + popup.close(); }); </script> diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl @@ -445,6 +445,11 @@ partial interface Document { [ChromeOnly] readonly attribute nsILoadGroup? documentLoadGroup; // Blocks the initial document parser until the given promise is settled. + // Note: In order to prevent extension or test code from altering about:blank + // semantics, this cannot block about:blank. + // + // If the option `blockScriptCreated` is not set to `false` this alters + // the Web-exposed behavior of `document.open()`ed documents, which is bad. [ChromeOnly, NewObject] Promise<any> blockParsing(Promise<any> promise, optional BlockParsingOptions options = {}); @@ -690,8 +695,28 @@ partial interface Document { // Extension to allow chrome code to detect initial about:blank documents. partial interface Document { + /** + * https://html.spec.whatwg.org/#is-initial-about:blank + * + * True if this is the initial about:blank document that the browsing context + * started with. Any web-observable browsing context starts out with such + * an empty document. + * + * This flag remains true for the entire lifetime of that document. + */ [ChromeOnly] readonly attribute boolean isInitialDocument; + + /** + * True if this is the initial about:blank document and it is still transient, + * i.e. it has not been committed to as a navigation destination. + * + * In this state, many actions (e.g. firing the load event) have not yet occurred. + * The browser may commit to it synchronously (causing those actions to run), + * but it could also remain transient or be replaced. + */ + [ChromeOnly] + readonly attribute boolean isUncommittedInitialDocument; }; // Extension to allow chrome code to get some wireframe-like structure. diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp @@ -285,6 +285,7 @@ XMLHttpRequestMainThread::XMLHttpRequestMainThread( mLoadTransferred(0), mIsSystem(false), mIsAnon(false), + mAlreadyGotStopRequest(false), mResultJSON(JS::UndefinedValue()), mArrayBufferBuilder(new ArrayBufferBuilder()), mResultArrayBuffer(nullptr), @@ -2239,9 +2240,17 @@ XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) { AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK); if (request != mChannel) { - // Can this still happen? + // This can happen when we have already faked an OnStopRequest earlier + // when synchronously canceling a sync XHR. + return NS_OK; + } + + if (mAlreadyGotStopRequest) { + // This is needed to filter out the real, second call after faking one in + // SendInternal. return NS_OK; } + mAlreadyGotStopRequest = true; // Send the decoder the signal that we've hit the end of the stream, // but only when decoding text eagerly. @@ -2624,6 +2633,8 @@ nsresult XMLHttpRequestMainThread::CreateChannel() { } NS_ENSURE_SUCCESS(rv, rv); + mAlreadyGotStopRequest = false; + if (mCSPEventListener) { nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); rv = loadInfo->SetCspEventListener(mCSPEventListener); @@ -3245,13 +3256,35 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody, return; } + nsresult channelStatus = NS_OK; nsAutoSyncOperation sync(suspendedDoc, SyncOperationBehavior::eSuspendInput); - if (!SpinEventLoopUntil("XMLHttpRequestMainThread::SendInternal"_ns, - [&]() { return !mFlagSyncLooping; })) { + if (!SpinEventLoopUntil("XMLHttpRequestMainThread::SendInternal"_ns, [&]() { + if (mFlagSyncLooping && mChannel) { + // The purpose of this check is to enable XHR channel cancelation + // upon navigating away from the page that is doing sync XHR + // to genuinely make the sync XHR go away within the same task. + mChannel->GetStatus(&channelStatus); + // Can't change mFlagSyncLooping to false, because other + // end-of-request code expects to be able to see it as true + // still even if we exit the loop early due to the channel + // getting canceled. + } + return !mFlagSyncLooping || NS_FAILED(channelStatus); + })) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } + if (NS_FAILED(channelStatus)) { + MOZ_ASSERT(mFlagSyncLooping); + // As mentioned above, when navigating away, we want channel cancelation + // to make the sync XHR go away within the same task. This also requires + // us to set the correct error result and dispatch events. So call + // OnStopRequest explicitly instead of the channel calling it after + // SendInternal has already completed. + // Consecutive OnStopRequests are blocked due to mAlreadyGotStopRequest + OnStopRequest(mChannel, channelStatus); + } // Time expired... We should throw. if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) { diff --git a/dom/xhr/XMLHttpRequestMainThread.h b/dom/xhr/XMLHttpRequestMainThread.h @@ -706,6 +706,10 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest, bool mIsSystem; bool mIsAnon; + // Prevent duplicate OnStopRequest calls due to the explicit + // OnStopRequest in the sync path of SendInternal + bool mAlreadyGotStopRequest; + /** * Close the XMLHttpRequest's channels. */ diff --git a/dom/xhr/tests/test_nestedSyncXHR.html b/dom/xhr/tests/test_nestedSyncXHR.html @@ -14,6 +14,7 @@ let xhr = new XMLHttpRequest(); let testCompleted = false; let frame = document.createElement('frame'); +frame.src = "data:text/html,"; frame.addEventListener('load', function() { if (testCompleted) { return; diff --git a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js @@ -1312,6 +1312,9 @@ function promiseMoveMouseAndScrollWheelOver( }); if (waitForScroll) { p = p.then(() => { + info( + "Printing something here to avoid failure; see https://bugzilla.mozilla.org/show_bug.cgi?id=1776963" + ); return promiseNativeWheelAndWaitForScrollEvent( target, dx, diff --git a/gfx/tests/browser/browser_windowless_troubleshoot_crash.js b/gfx/tests/browser/browser_windowless_troubleshoot_crash.js @@ -1,38 +1,23 @@ add_task(async function test_windowlessBrowserTroubleshootCrash() { let webNav = Services.appShell.createWindowlessBrowser(false); - let onLoaded = new Promise(resolve => { - let docShell = webNav.docShell; - let listener = { - observe(contentWindow) { - let observedDocShell = - contentWindow.docShell.sameTypeRootTreeItem.QueryInterface( - Ci.nsIDocShell - ); - if (docShell === observedDocShell) { - Services.obs.removeObserver( - listener, - "content-document-global-created" - ); - resolve(); - } - }, - }; - Services.obs.addObserver(listener, "content-document-global-created"); - }); let loadURIOptions = { triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), }; + + // will synchronously commit to the initial about:blank and finish the load webNav.loadURI(Services.io.newURI("about:blank"), loadURIOptions); - await onLoaded; + is(webNav.document.location.href, "about:blank", "location is about:blank"); + is(webNav.document.readyState, "complete", "readyState is complete"); let winUtils = webNav.document.defaultView.windowUtils; try { let layerManager = winUtils.layerManagerType; ok( - layerManager == "Basic" || layerManager == "WebRender (Software)", - "windowless browser's layerManagerType should be 'Basic' or 'WebRender (Software)'" + ["WebRender (Software)", "Fallback"].includes(layerManager), + "windowless browser's layerManagerType should be 'WebRender (Software)' or 'Fallback': " + + layerManager ); } catch (e) { // The windowless browser may not have a layermanager at all yet, and that's ok. diff --git a/js/xpconnect/tests/chrome/test_discardSystemSource.xhtml b/js/xpconnect/tests/chrome/test_discardSystemSource.xhtml @@ -69,7 +69,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=990353 ok(/someBitOfSource/.test(canary.toString()), "Should have own source"); window[0].frameElement.onload = frameLoaded; - window[0].location = "file_discardSystemSource.html"; + window[0].location = "chrome://mochitests/content/chrome/js/xpconnect/tests/chrome/file_discardSystemSource.html"; } addLoadEvent(function() { SpecialPowers.pushPrefEnv({set: [['javascript.options.discardSystemSource', true]]}, go); diff --git a/js/xpconnect/tests/unit/xpcshell.toml b/js/xpconnect/tests/unit/xpcshell.toml @@ -281,6 +281,8 @@ skip-if = [ ["test_nuke_sandbox_event_listeners.js"] ["test_nuke_webextension_wrappers.js"] +# bug 1865028 +skip-if = ["true"] ["test_onGarbageCollection-01.js"] head = "head_ongc.js" diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -1727,9 +1727,15 @@ nsresult PresShell::Initialize() { // fires, if painting is still locked down, then we will go ahead and // trigger a full invalidate and allow painting to proceed normally. mPaintingSuppressed = true; - // Don't suppress painting if the document isn't loading. - Document::ReadyState readyState = mDocument->GetReadyStateEnum(); - if (readyState != Document::READYSTATE_COMPLETE) { + // Don't suppress painting if the document isn't loading. However, + // the initial about:blank appears not to be loading, but we still + // want to suppress painting. + nsIDocShell* docShell = mDocument->GetDocShell(); + if ((docShell && + !nsDocShell::Cast(docShell) + ->HasStartedLoadingOtherThanInitialBlankURI() && + mDocument->IsInitialDocument()) || + mDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) { mPaintSuppressionTimer = NS_NewTimer(); } if (!mPaintSuppressionTimer) { diff --git a/layout/base/crashtests/1453342.html b/layout/base/crashtests/1453342.html @@ -21,9 +21,9 @@ // DDBEGIN o1 = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mroot'); o2 = document.createElement('frame'); + o2.addEventListener('load', frameLoader); document.documentElement.appendChild(o1); document.documentElement.appendChild(o2); - o2.addEventListener('load', frameLoader); } window.addEventListener('load', start) </script> diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp @@ -932,6 +932,7 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { // First, get the window from the document... nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); + RefPtr<nsDocShell> docShell = nsDocShell::Cast(window->GetDocShell()); mLoaded = true; @@ -957,7 +958,6 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { // onload to the document content since that would likely confuse scripts // on the page. - RefPtr<nsDocShell> docShell = nsDocShell::Cast(window->GetDocShell()); NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); // Unfortunately, docShell->GetRestoringDocument() might no longer be set @@ -966,15 +966,16 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { // But we can detect the restoring case very simply: by whether our // document's readyState is COMPLETE. restoring = - (mDocument->GetReadyStateEnum() == Document::READYSTATE_COMPLETE); + (mDocument->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) && + !mDocument->InitialAboutBlankLoadCompleting(); if (!restoring) { NS_ASSERTION( mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE || // test_stricttransportsecurity.html has old-style // docshell-generated about:blank docs reach this code! (mDocument->GetReadyStateEnum() == - Document::READYSTATE_UNINITIALIZED && - NS_IsAboutBlank(mDocument->GetDocumentURI())), + Document::READYSTATE_COMPLETE && + mDocument->InitialAboutBlankLoadCompleting()), "Bad readystate"); #ifdef DEBUG bool docShellThinksWeAreRestoring; @@ -984,7 +985,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { "READYSTATE_COMPLETE document?"); #endif // DEBUG nsCOMPtr<Document> d = mDocument; - mDocument->SetReadyStateInternal(Document::READYSTATE_COMPLETE); + if (!mDocument->InitialAboutBlankLoadCompleting()) { + mDocument->SetReadyStateInternal(Document::READYSTATE_COMPLETE); + } RefPtr<nsDOMNavigationTiming> timing(d->GetNavigationTiming()); if (timing) { @@ -1046,7 +1049,7 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { // Re-get window, since it might have changed during above firing of onload window = mDocument->GetWindow(); if (window) { - nsIDocShell* docShell = window->GetDocShell(); + docShell = nsDocShell::Cast(window->GetDocShell()); bool isInUnload; if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) && !isInUnload) { @@ -1068,11 +1071,27 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) { // Now that the document has loaded, we can tell the presshell // to unsuppress painting. if (mPresShell) { - RefPtr<PresShell> presShell = mPresShell; - presShell->UnsuppressPainting(); - // mPresShell could have been removed now, see bug 378682/421432 - if (mPresShell) { - mPresShell->LoadComplete(); + if (mDocument && mDocument->IsInitialDocument() && docShell && + !docShell->HasStartedLoadingOtherThanInitialBlankURI()) { + // Delay paint unsuppression in case a new load elsewhere is started + // in the same task that permitted the initial about:blank to fire + // its load event. This is important for the front end, which assumes + // that it's performance-wise OK to create an empty XUL browser, + // append it in a document, and only then make it start navigating + // away from the initial about:blank. + nsCOMPtr<nsIRunnable> task = NewRunnableMethod<RefPtr<PresShell>>( + "nsDocShell::UnsuppressPaintingIfNoNavigationAwayFromAboutBlank", + docShell, + &nsDocShell::UnsuppressPaintingIfNoNavigationAwayFromAboutBlank, + mPresShell); + mDocument->Dispatch(task.forget()); + } else { + RefPtr<PresShell> presShell = mPresShell; + presShell->UnsuppressPainting(); + // mPresShell could have been removed now, see bug 378682/421432 + if (mPresShell) { + mPresShell->LoadComplete(); + } } } } diff --git a/layout/build/nsContentDLF.cpp b/layout/build/nsContentDLF.cpp @@ -269,6 +269,8 @@ already_AddRefed<Document> nsContentDLF::CreateBlankDocument( blankDoc->ResetToURI(uri, aLoadGroup, aPrincipal, aPartitionedPrincipal); blankDoc->SetContainer(aContainer); + blankDoc->SetAllowDeclarativeShadowRoots(true); + // add some simple content structure nsNodeInfoManager* nim = blankDoc->NodeInfoManager(); diff --git a/layout/style/crashtests/1384824-1.html b/layout/style/crashtests/1384824-1.html @@ -5,23 +5,23 @@ min-height: 100px; } </style> -<div> - <iframe src="about:blank"></iframe> -</div> <script> +function runTest() { let div = document.querySelector('div'); let iframe = document.querySelector('iframe'); - iframe.onload = function() { - let doc = iframe.contentDocument; - let e = doc.createElement('textarea'); - doc.body.appendChild(e); + let doc = iframe.contentDocument; + let e = doc.createElement('textarea'); + doc.body.appendChild(e); + setTimeout(function() { + getComputedStyle(e).minHeight; + div.style.display = 'none'; setTimeout(function() { getComputedStyle(e).minHeight; - div.style.display = 'none'; - setTimeout(function() { - getComputedStyle(e).minHeight; - document.documentElement.className = ""; - }, 0); + document.documentElement.className = ""; }, 0); - }; + }, 0); +} </script> +<div> + <iframe src="about:blank" onload="runTest();"></iframe> +</div> diff --git a/layout/style/crashtests/1384824-2.html b/layout/style/crashtests/1384824-2.html @@ -5,27 +5,27 @@ min-height: 100px; } </style> -<div> - <iframe src="about:blank"></iframe> -</div> <script> +function runTest() { let div = document.querySelector('div'); let iframe = document.querySelector('iframe'); - iframe.onload = function() { - let doc = iframe.contentDocument; - let e = doc.createElement('textarea'); - doc.body.appendChild(e); + let doc = iframe.contentDocument; + let e = doc.createElement('textarea'); + doc.body.appendChild(e); + setTimeout(function() { + var cs = getComputedStyle(e); + cs.minHeight; + div.style.display = 'none'; setTimeout(function() { - var cs = getComputedStyle(e); - cs.minHeight; - div.style.display = 'none'; + div.style.display = 'block'; setTimeout(function() { - div.style.display = 'block'; - setTimeout(function() { - cs.minHeight; - document.documentElement.className = ""; - }, 0); + cs.minHeight; + document.documentElement.className = ""; }, 0); }, 0); - }; + }, 0); +} </script> +<div> + <iframe src="about:blank" onload="runTest();"></iframe> +</div> diff --git a/layout/style/test/test_media_query_list.html b/layout/style/test/test_media_query_list.html @@ -292,6 +292,9 @@ async function run() { is(newIframe.contentWindow.matchMedia("all").matches, true, "matchMedia should work in newly-created iframe"); + // Passes due to initial about:blank poking layout. + // See https://github.com/w3c/csswg-drafts/issues/3101, bug 1458816, + // and bug 1011468. is(newIframe.contentWindow.matchMedia("(min-width: 1px)").matches, true, "(min-width: 1px) should match in newly-created iframe"); is(newIframe.contentWindow.matchMedia("(max-width: 1px)").matches, false, diff --git a/layout/svg/tests/test_context_properties_allowed_domains.html b/layout/svg/tests/test_context_properties_allowed_domains.html @@ -48,6 +48,10 @@ await waitForLoad(frame); + // ensure layout flushed, see Bug 1955324 comment 15 + await new Promise(res => frame.contentWindow.requestAnimationFrame(res)); + ok(frame.contentWindow.innerHeight != 0, 'iframe has non-zero size'); + let snapshot = await snapshotWindow(frame, false); document.body.removeChild(frame); return snapshot; diff --git a/layout/tools/reftest/api.js b/layout/tools/reftest/api.js @@ -128,7 +128,7 @@ this.reftest = class extends ExtensionAPI { "chrome,dialog=no,left=800,height=200,width=200,all", null ); - dummy.onload = async function () { + dummy.setTimeout(async function () { // Close pre-existing window win.close(); @@ -149,7 +149,7 @@ this.reftest = class extends ExtensionAPI { "chrome,dialog=no,all", {} ); - }; + }, 0); } onShutdown() { diff --git a/layout/tools/reftest/reftest.sys.mjs b/layout/tools/reftest/reftest.sys.mjs @@ -6,6 +6,8 @@ import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs"; import { globals } from "resource://reftest/globals.sys.mjs"; +import { setTimeout } from "resource://gre/modules/Timer.sys.mjs"; + const { XHTML_NS, XUL_NS, @@ -1944,8 +1946,13 @@ function RecvContentReady(info) { g.resolveContentReady(); g.resolveContentReady = null; } else { - g.contentGfxInfo = info.gfx; - InitAndStartRefTests(); + // Prevent a race with GeckoView:SetFocused, bug 1960620 + // If about:blank loads synchronously, we'll RecvContentReady on the first tick, + // which is also the tick where GeckoViewContent processes messages from GeckoView. + setTimeout(() => { + g.contentGfxInfo = info.gfx; + InitAndStartRefTests(); + }, 0); } return { remote: g.browserIsRemote }; } diff --git a/mobile/android/geckoview/src/androidTest/assets/www/forms.html b/mobile/android/geckoview/src/androidTest/assets/www/forms.html @@ -24,11 +24,10 @@ <iframe id="iframe"></iframe> </body> <script> - addEventListener("load", function () { - if (window.parent === window) { - document.getElementById("iframe").contentWindow.location.href = - window.location.href; - } - }); + // ensure page isn't loaded before the iframe loaded it's target location + if (window.parent === window) { + document.getElementById("iframe").contentWindow.location.href = + window.location.href; + } </script> </html> diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt @@ -410,7 +410,8 @@ class ContentDelegateTest : BaseSessionTest() { override fun onCloseRequest(session: GeckoSession) { } - @AssertCalled(count = 1) + // (1) artificial from GeckoViewProgress._fireInitialLoad (2) about:blank load + @AssertCalled(count = 2) override fun onPageStop(session: GeckoSession, success: Boolean) { } }) diff --git a/mobile/shared/components/extensions/ext-tabs.js b/mobile/shared/components/extensions/ext-tabs.js @@ -419,9 +419,8 @@ this.tabs = class extends ExtensionAPIPersistent { }, }); - // Make sure things like about:blank URIs never inherit, - // and instead always get a NullPrincipal. - if (url !== null) { + // The initial about:blank loads synchronously, so no listener is needed + if (url !== null && !url.startsWith("about:blank")) { tabListener.initializingTabs.add(nativeTab); } else { url = "about:blank"; diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp @@ -1893,10 +1893,10 @@ static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab( RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); openInfo->mBrowsingContextReadyCallback = new nsBrowsingContextReadyCallback(promise); - openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef(); openInfo->mParent = aLoadingBrowsingContext; openInfo->mForceNoOpener = true; openInfo->mIsRemote = true; + openInfo->mPrincipalToInheritForAboutBlank = triggeringPrincipal; // Do the actual work to open a new tab or window async. nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( diff --git a/parser/html/nsHtml5Parser.cpp b/parser/html/nsHtml5Parser.cpp @@ -6,6 +6,8 @@ #include "nsHtml5Parser.h" +#include "ErrorList.h" +#include "encoding_rs_statics.h" #include "mozilla/AutoRestore.h" #include "mozilla/UniquePtr.h" #include "nsCRT.h" @@ -17,7 +19,8 @@ #include "nsNetUtil.h" NS_INTERFACE_TABLE_HEAD(nsHtml5Parser) - NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference) + NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference, + nsIStreamListener) NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser) NS_INTERFACE_MAP_END @@ -38,7 +41,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser) NS_IMPL_CYCLE_COLLECTION_UNLINK_END nsHtml5Parser::nsHtml5Parser() - : mLastWasCR(false), + : mAboutBlankMode(false), + mLastWasCR(false), mDocWriteSpeculativeLastWasCR(false), mBlocked(0), mDocWriteSpeculatorActive(false), @@ -99,9 +103,13 @@ void nsHtml5Parser::SetDocumentCharset(NotNull<const Encoding*> aEncoding, int32_t aCharsetSource, bool aForceAutoDetection) { MOZ_ASSERT(!mExecutor->HasStarted(), "Document charset set too late."); - MOZ_ASSERT(GetStreamParser(), "Setting charset on a script-only parser."); - GetStreamParser()->SetDocumentCharset( - aEncoding, (nsCharsetSource)aCharsetSource, aForceAutoDetection); + if (mAboutBlankMode) { + MOZ_ASSERT(aEncoding == UTF_8_ENCODING); + } else { + MOZ_ASSERT(GetStreamParser(), "Setting charset on a script-only parser."); + GetStreamParser()->SetDocumentCharset( + aEncoding, (nsCharsetSource)aCharsetSource, aForceAutoDetection); + } mExecutor->SetDocumentCharsetAndSource(aEncoding, (nsCharsetSource)aCharsetSource); } @@ -109,12 +117,14 @@ void nsHtml5Parser::SetDocumentCharset(NotNull<const Encoding*> aEncoding, nsresult nsHtml5Parser::GetChannel(nsIChannel** aChannel) { if (GetStreamParser()) { return GetStreamParser()->GetChannel(aChannel); - } else { - return NS_ERROR_NOT_AVAILABLE; } + return NS_ERROR_NOT_AVAILABLE; } nsIStreamListener* nsHtml5Parser::GetStreamListener() { + if (mAboutBlankMode) { + return this; + } return mStreamListener; } @@ -125,10 +135,14 @@ nsHtml5Parser::ContinueInterruptedParsing() { } NS_IMETHODIMP_(void) -nsHtml5Parser::BlockParser() { mBlocked++; } +nsHtml5Parser::BlockParser() { + MOZ_ASSERT(!mAboutBlankMode, "Must not block about:blank"); + mBlocked++; +} NS_IMETHODIMP_(void) nsHtml5Parser::UnblockParser() { + MOZ_ASSERT(!mAboutBlankMode, "Must not unblock about:blank"); MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0); if (MOZ_LIKELY(mBlocked > 0)) { mBlocked--; @@ -162,11 +176,14 @@ nsHtml5Parser::Parse(nsIURI* aURL) { */ MOZ_ASSERT(!mExecutor->HasStarted(), "Tried to start parse without initializing the parser."); - MOZ_ASSERT(GetStreamParser(), - "Can't call this Parse() variant on script-created parser"); + if (!mAboutBlankMode) { + MOZ_ASSERT(GetStreamParser(), + "Can't call this Parse() variant on script-created parser"); - GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source - mExecutor->SetStreamParser(GetStreamParser()); + GetStreamParser()->SetViewSourceTitle( + aURL); // In case we're viewing source + mExecutor->SetStreamParser(GetStreamParser()); + } mExecutor->SetParser(this); return NS_OK; } @@ -519,6 +536,8 @@ void nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) { mode = PLAIN_TEXT; } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) { mode = LOAD_AS_DATA; + } else if (!nsCRT::strcmp(aCommand, "about-blank")) { + mode = ABOUT_BLANK; } #ifdef DEBUG else { @@ -528,12 +547,18 @@ void nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) { "Unsupported parser command!"); } #endif - mStreamListener = - new nsHtml5StreamListener(new nsHtml5StreamParser(mExecutor, this, mode)); + if (mode == ABOUT_BLANK) { + mAboutBlankMode = true; + } else { + mStreamListener = new nsHtml5StreamListener( + new nsHtml5StreamParser(mExecutor, this, mode)); + } } bool nsHtml5Parser::IsScriptCreated() { return !GetStreamParser(); } +bool nsHtml5Parser::IsAboutBlankMode() { return mAboutBlankMode; } + /* End nsIParser */ // not from interface @@ -711,3 +736,62 @@ void nsHtml5Parser::ContinueAfterFailedCharsetSwitch() { "Tried to continue after failed charset switch without a stream parser"); GetStreamParser()->ContinueAfterFailedCharsetSwitch(); } + +NS_IMETHODIMP nsHtml5Parser::OnStartRequest(nsIRequest* aRequest) { + if (!mAboutBlankMode) { + MOZ_ASSERT(false, + "Attempted to use nsHtml5Parser as stream listener in " + "non-about:blank mode."); + return NS_ERROR_NOT_IMPLEMENTED; + } + MOZ_RELEASE_ASSERT(!GetStreamParser(), + "Should not have stream parser in about:blank mode."); + mTokenizer->start(); + mExecutor->Start(); + nsresult rv = mExecutor->WillBuildModel(); + NS_ENSURE_SUCCESS(rv, rv); + PermanentlyUndefineInsertionPoint(); + mTokenizer->eof(); + if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { + mExecutor->MarkAsBroken(rv); + } else { + mTreeBuilder->StreamEnded(); + } + auto r = mTreeBuilder->Flush(); + if (r.isErr()) { + return mExecutor->MarkAsBroken(r.unwrapErr()); + } + mExecutor->FlushDocumentWrite(); + // The below call does memory cleanup, so call it even if the + // parser has been marked as broken. + mTokenizer->end(); + return rv; +} + +NS_IMETHODIMP nsHtml5Parser::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInStream, + uint64_t aSourceOffset, + uint32_t aLength) { + if (!mAboutBlankMode) { + MOZ_ASSERT(false, + "Attempted to use nsHtml5Parser as stream listener in " + "non-about:blank mode."); + return NS_ERROR_NOT_IMPLEMENTED; + } + if (aLength) { + MOZ_ASSERT(false, "Non-zero-length stream in about:blank mode."); + return NS_ERROR_ILLEGAL_INPUT; + } + return NS_OK; +} + +NS_IMETHODIMP nsHtml5Parser::OnStopRequest(nsIRequest* aRequest, + nsresult aStatus) { + if (!mAboutBlankMode) { + MOZ_ASSERT(false, + "Attempted to use nsHtml5Parser as stream listener in " + "non-about:blank mode."); + return NS_ERROR_NOT_IMPLEMENTED; + } + return NS_OK; +} diff --git a/parser/html/nsHtml5Parser.h b/parser/html/nsHtml5Parser.h @@ -23,7 +23,9 @@ #include "nsHtml5StreamListener.h" #include "nsCharsetSource.h" -class nsHtml5Parser final : public nsIParser, public nsSupportsWeakReference { +class nsHtml5Parser final : public nsIParser, + public nsSupportsWeakReference, + public nsIStreamListener { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -31,6 +33,16 @@ class nsHtml5Parser final : public nsIParser, public nsSupportsWeakReference { nsHtml5Parser(); + // about:blank-only + NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override; + + // about:blank-only and exists only for interface compat. + NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStream, + uint64_t aSourceOffset, uint32_t aLength) override; + + // about:blank-only and exists only for interface compat. + NS_IMETHOD OnStopRequest(nsIRequest* aRequest, nsresult aStatus) override; + /* Start nsIParser */ /** * No-op for backwards compat. @@ -177,6 +189,12 @@ class nsHtml5Parser final : public nsIParser, public nsSupportsWeakReference { */ virtual bool IsScriptCreated() override; + /** + * True iff this is an about:blank-mode HTML5 parser + * (i.e. a parser for non-initial about:blank). + */ + virtual bool IsAboutBlankMode() override; + /* End nsIParser */ // Not from an external interface @@ -235,6 +253,12 @@ class nsHtml5Parser final : public nsIParser, public nsSupportsWeakReference { // State variables /** + * This parser is parsing (non-initial) about:blank for viewing (not View + * Source or data) + */ + bool mAboutBlankMode; + + /** * Whether the last character tokenized was a carriage return (for CRLF) */ bool mLastWasCR; diff --git a/parser/html/nsHtml5StreamParser.h b/parser/html/nsHtml5StreamParser.h @@ -86,7 +86,13 @@ enum eParserMode { /** * Load as data (XHR) */ - LOAD_AS_DATA + LOAD_AS_DATA, + + /** + * Parse (non-initial) about:blank for normal viewing (not View Source or + * data). + */ + ABOUT_BLANK, }; enum eBomState { diff --git a/parser/htmlparser/nsIParser.h b/parser/htmlparser/nsIParser.h @@ -175,6 +175,12 @@ class nsIParser : public nsParserBase { * True if this is a script-created HTML5 parser. */ virtual bool IsScriptCreated() = 0; + + /** + * True iff this is an about:blank-mode HTML5 parser + * (i.e. a parser for non-initial about:blank). + */ + virtual bool IsAboutBlankMode() = 0; }; #endif diff --git a/parser/htmlparser/nsParser.cpp b/parser/htmlparser/nsParser.cpp @@ -513,6 +513,8 @@ bool nsParser::HasNonzeroScriptNestingLevel() const { return false; } bool nsParser::IsScriptCreated() { return false; } +bool nsParser::IsAboutBlankMode() { return false; } + /** * This is the main controlling routine in the parsing process. * Note that it may get called multiple times for the same scanner, diff --git a/parser/htmlparser/nsParser.h b/parser/htmlparser/nsParser.h @@ -223,6 +223,11 @@ class nsParser final : public nsIParser, virtual bool IsScriptCreated() override; /** + * Always false. + */ + virtual bool IsAboutBlankMode() override; + + /** * This is called when the final chunk has been * passed to the parser and the content sink has * interrupted token processing. It schedules diff --git a/parser/prototype/PrototypeDocumentParser.h b/parser/prototype/PrototypeDocumentParser.h @@ -88,6 +88,8 @@ class PrototypeDocumentParser final : public nsIParser, virtual bool IsScriptCreated() override { return false; } + virtual bool IsAboutBlankMode() override { return false; } + // End nsIParser private: diff --git a/remote/marionette/actors/MarionetteEventsChild.sys.mjs b/remote/marionette/actors/MarionetteEventsChild.sys.mjs @@ -39,7 +39,9 @@ export class MarionetteEventsChild extends JSWindowActorChild { // Ignore invalid combinations of load events and document's readyState. if ( - (type === "DOMContentLoaded" && target.readyState != "interactive") || + (type === "DOMContentLoaded" && + target.readyState != "interactive" && + !target.isInitialDocument) || (type === "pageshow" && target.readyState != "complete") ) { lazy.logger.warn( @@ -60,6 +62,8 @@ export class MarionetteEventsChild extends JSWindowActorChild { browsingContext: this.browsingContext, documentURI: target.documentURI, readyState: target.readyState, + isInitialDocument: target.isInitialDocument, + isUncommittedInitialDocument: target.isUncommittedInitialDocument, type, windowId: this.innerWindowId, }); diff --git a/remote/marionette/navigate.sys.mjs b/remote/marionette/navigate.sys.mjs @@ -46,7 +46,7 @@ export const navigate = {}; * True if the page load has been finished. */ function checkReadyState(pageLoadStrategy, eventData = {}) { - const { documentURI, readyState } = eventData; + const { documentURI, readyState, isUncommittedInitialDocument } = eventData; const result = { error: null, finished: false }; @@ -77,7 +77,9 @@ function checkReadyState(pageLoadStrategy, eventData = {}) { break; case "complete": - result.finished = true; + if (!isUncommittedInitialDocument) { + result.finished = true; + } break; } @@ -342,7 +344,8 @@ navigate.waitForNavigationCompleted = async function waitForNavigationCompleted( case "pageshow": { // Don't require an unload event when a top-level browsing context // change occurred. - if (!seenUnload && !browsingContextChanged) { + // The initial about:blank load has no previous page to unload. + if (!seenUnload && !browsingContextChanged && !data.isInitialDocument) { return; } const result = checkReadyState(pageLoadStrategy, data); diff --git a/remote/shared/Navigate.sys.mjs b/remote/shared/Navigate.sys.mjs @@ -11,7 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, { setTimeout: "resource://gre/modules/Timer.sys.mjs", Deferred: "chrome://remote/content/shared/Sync.sys.mjs", - isInitialDocument: + isUncommittedInitialDocument: "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", NavigationListener: @@ -94,15 +94,16 @@ export async function waitForInitialNavigationCompleted( }); const navigated = listener.start(); - const isInitial = lazy.isInitialDocument(browsingContext); + const isUncommittedInitial = + lazy.isUncommittedInitialDocument(browsingContext); const isLoadingDocument = listener.isLoadingDocument; lazy.logger.trace( - lazy.truncate`[${browsingContext.id}] Wait for initial navigation: isInitial=${isInitial}, isLoadingDocument=${isLoadingDocument}` + lazy.truncate`[${browsingContext.id}] Wait for initial navigation: isUncommittedInitial=${isUncommittedInitial}, isLoadingDocument=${isLoadingDocument}` ); // If the current document is not the initial "about:blank" and is also // no longer loading, assume the navigation is done and return. - if (!isInitial && !isLoadingDocument) { + if (!isUncommittedInitial && !isLoadingDocument) { lazy.logger.trace( lazy.truncate`[${browsingContext.id}] Document already finished loading: ${browsingContext.currentURI?.spec}` ); @@ -355,18 +356,8 @@ export class ProgressListener { return; } - // If a non initial page finished loading the navigation is done. - if (!this.isInitialDocument) { - this.stop(); - return; - } - - // Otherwise wait for a potential additional page load. - this.#trace( - "Initial document loaded. Wait for a potential further navigation." - ); - this.#seenStartFlag = false; - this.#setUnloadTimer(); + // If a page finished loading the navigation is done. + this.stop(); } } } diff --git a/remote/shared/listeners/ParentWebProgressListener.sys.mjs b/remote/shared/listeners/ParentWebProgressListener.sys.mjs @@ -7,7 +7,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BrowsingContextListener: "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs", - isInitialDocument: + isUncommittedInitialDocument: "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", notifyFragmentNavigated: @@ -143,8 +143,9 @@ export class ParentWebProgressListener { const url = targetURI?.spec; - const isInitialDocument = lazy.isInitialDocument(context); - if (isInitialDocument && url === "about:blank") { + const isUncommittedInitialDocument = + lazy.isUncommittedInitialDocument(context); + if (isUncommittedInitialDocument && url === "about:blank") { this.#trace("Skip initial navigation to about:blank", context.id); return; } diff --git a/remote/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs b/remote/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs @@ -73,7 +73,7 @@ function isParentProcess(browsingContext) { /** * Check if the provided browsing context is currently displaying its initial * document. For top level browsing contexts, this is usually the initial - * about:blank which will be replaced soon. + * about:blank. * * @param {BrowsingContext} browsingContext * The browsing context to check. @@ -93,6 +93,28 @@ export function isInitialDocument(browsingContext) { } /** + * Check if the provided browsing context is currently displaying its initial + * document. For top level browsing contexts, this is usually the initial + * about:blank which will be replaced soon. + * + * @param {BrowsingContext} browsingContext + * The browsing context to check. + * + * @returns {boolean} + * True if the browsing context is on the initial document, false otherwise. + */ +export function isUncommittedInitialDocument(browsingContext) { + if (!browsingContext.currentWindowGlobal) { + // Right after a browsing context has been attached it could happen that + // no window global has been set yet. Consider this as nothing has been + // loaded yet. + return true; + } + + return browsingContext.currentWindowGlobal.isUncommittedInitialDocument; +} + +/** * Check if the given browsing context is valid for the message handler * to use. * diff --git a/remote/shared/test/xpcshell/test_Navigate.js b/remote/shared/test/xpcshell/test_Navigate.js @@ -18,6 +18,11 @@ const { "chrome://remote/content/shared/Navigate.sys.mjs" ); +const { isInitialDocument, isUncommittedInitialDocument } = + ChromeUtils.importESModule( + "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs" + ); + const LOAD_FLAG_ERROR_PAGE = 0x10000; const CURRENT_URI = Services.io.newURI("http://foo.bar/"); @@ -100,6 +105,8 @@ class MockWebProgress { } this.browsingContext.currentWindowGlobal.isInitialDocument = isInitial; + // Start is sent for the initial about:blank if and only if we commit to it + this.browsingContext.currentWindowGlobal.isUncommittedInitialDocument = false; this.isLoadingDocument = true; this.loadType = 0; @@ -147,6 +154,7 @@ class MockTopContext { this.currentURI = CURRENT_URI; this.currentWindowGlobal = { isInitialDocument: true, + isUncommittedInitialDocument: true, documentURI: CURRENT_URI, }; this.id = 7; @@ -174,6 +182,7 @@ add_task( ok(!webProgress.isLoadingDocument, "Document is not loading"); + // without window global, we'll wait for start and stop const navigated = waitForInitialNavigationCompleted(webProgress); await webProgress.sendStartState({ isInitial: true }); @@ -205,7 +214,12 @@ add_task( const webProgress = browsingContext.webProgress; ok(!webProgress.isLoadingDocument, "Document is not loading"); + ok( + isUncommittedInitialDocument(webProgress.browsingContext), + "Document is uncommitted initial" + ); + // for uncommitted initial, we'll wait for start and stop const navigated = waitForInitialNavigationCompleted(webProgress); await webProgress.sendStartState({ isInitial: true }); @@ -273,12 +287,17 @@ add_task( await webProgress.sendStopState(); ok(!webProgress.isLoadingDocument, "Document is not loading"); + ok(isInitialDocument(webProgress.browsingContext), "Document is initial"); + ok( + !isUncommittedInitialDocument(webProgress.browsingContext), + "Document is uncommitted" + ); const navigated = waitForInitialNavigationCompleted(webProgress); ok( - !(await hasPromiseResolved(navigated)), - "waitForInitialNavigationCompleted has not resolved yet" + await hasPromiseResolved(navigated), + "waitForInitialNavigationCompleted resolves immediately" ); const { currentURI, targetURI } = await navigated; @@ -298,83 +317,6 @@ add_task( ); add_task( - async function test_waitForInitialNavigation_initialDocumentLoadingAndAdditionalLoad() { - const browsingContext = new MockTopContext(); - const webProgress = browsingContext.webProgress; - - await webProgress.sendStartState({ isInitial: true }); - - ok(webProgress.isLoadingDocument, "Document is loading"); - - const navigated = waitForInitialNavigationCompleted(webProgress); - - ok( - !(await hasPromiseResolved(navigated)), - "waitForInitialNavigationCompleted has not resolved yet" - ); - - await webProgress.sendStopState(); - - await wait(100); - - await webProgress.sendStartState({ isInitial: false }); - await webProgress.sendStopState(); - - const { currentURI, targetURI } = await navigated; - - ok(!webProgress.isLoadingDocument, "Document is not loading"); - ok( - !webProgress.browsingContext.currentWindowGlobal.isInitialDocument, - "Is not initial document" - ); - equal( - currentURI.spec, - TARGET_URI.spec, - "Expected current URI has been set" - ); - equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set"); - } -); - -add_task( - async function test_waitForInitialNavigation_initialDocumentFinishedLoadingAndAdditionalLoad() { - const browsingContext = new MockTopContext(); - const webProgress = browsingContext.webProgress; - - await webProgress.sendStartState({ isInitial: true }); - await webProgress.sendStopState(); - - ok(!webProgress.isLoadingDocument, "Document is not loading"); - - const navigated = waitForInitialNavigationCompleted(webProgress); - - ok( - !(await hasPromiseResolved(navigated)), - "waitForInitialNavigationCompleted has not resolved yet" - ); - - await wait(100); - - await webProgress.sendStartState({ isInitial: false }); - await webProgress.sendStopState(); - - const { currentURI, targetURI } = await navigated; - - ok(!webProgress.isLoadingDocument, "Document is not loading"); - ok( - !webProgress.browsingContext.currentWindowGlobal.isInitialDocument, - "Is not initial document" - ); - equal( - currentURI.spec, - TARGET_URI.spec, - "Expected current URI has been set" - ); - equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set"); - } -); - -add_task( async function test_waitForInitialNavigation_notInitialDocumentNotLoading() { const browsingContext = new MockTopContext(); const webProgress = browsingContext.webProgress; @@ -523,12 +465,7 @@ add_task(async function test_waitForInitialNavigation_unloadTimeout_default() { const browsingContext = new MockTopContext(); const webProgress = browsingContext.webProgress; - // Stop the navigation on an initial page which is not loading anymore. - // This situation happens with new tabs on Android, even though they are on - // the initial document, they will not start another navigation on their own. - await webProgress.sendStartState({ isInitial: true }); - await webProgress.sendStopState(); - + // Document starts out as uncommitted initial and not loading ok(!webProgress.isLoadingDocument, "Document is not loading"); const navigated = waitForInitialNavigationCompleted(webProgress); @@ -556,12 +493,7 @@ add_task(async function test_waitForInitialNavigation_unloadTimeout_longer() { const browsingContext = new MockTopContext(); const webProgress = browsingContext.webProgress; - // Stop the navigation on an initial page which is not loading anymore. - // This situation happens with new tabs on Android, even though they are on - // the initial document, they will not start another navigation on their own. - await webProgress.sendStartState({ isInitial: true }); - await webProgress.sendStopState(); - + // Document starts out as uncommitted initial and not loading ok(!webProgress.isLoadingDocument, "Document is not loading"); const navigated = waitForInitialNavigationCompleted(webProgress, { @@ -615,7 +547,7 @@ add_task(async function test_ProgressListener_expectNavigation() { }); add_task( - async function test_ProgressListener_expectNavigation_initialDocumentFinishedLoading() { + async function test_ProgressListener_expectNavigation_initialDocument() { const browsingContext = new MockTopContext(); const webProgress = browsingContext.webProgress; @@ -630,14 +562,6 @@ add_task( await webProgress.sendStartState({ isInitial: true }); await webProgress.sendStopState(); - // Wait for unloadTimeout to finish in case it started - await wait(30); - - ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved yet"); - - await webProgress.sendStartState(); - await webProgress.sendStopState(); - ok(await hasPromiseResolved(navigated), "Listener has resolved"); } ); diff --git a/remote/webdriver-bidi/modules/windowglobal/_configuration.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/_configuration.sys.mjs @@ -354,7 +354,7 @@ class _ConfigurationModule extends WindowGlobalBiDiModule { async #onConfigurationComplete(window) { // parser blocking doesn't work for initial about:blank, so ensure // browsing_context.create waits for configuration to complete - if (window.location.href.startsWith("about:blank")) { + if (window.document.isInitialDocument) { await this.messageHandler.forwardCommand({ moduleName: "browsingContext", commandName: "_onConfigurationComplete", diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.sys.mjs b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.sys.mjs @@ -773,6 +773,10 @@ export var BrowserTestUtils = { ); } let newTab = openEvent.target; + if (wantLoad == "about:blank") { + TestUtils.executeSoon(() => resolve(newTab)); + return; + } let newBrowser = newTab.linkedBrowser; let result; if (waitForLoad) { @@ -902,12 +906,14 @@ export var BrowserTestUtils = { } } - promises.push( - TestUtils.topicObserved( - "browser-delayed-startup-finished", - subject => subject == win - ) - ); + if (!(win.gBrowserInit && win.gBrowserInit.delayedStartupFinished)) { + promises.push( + TestUtils.topicObserved( + "browser-delayed-startup-finished", + subject => subject == win + ) + ); + } if (url || waitForAnyURLLoaded) { let loadPromise = this.browserLoaded(win.gBrowser.selectedBrowser, { diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -3560,7 +3560,12 @@ function synthesizeDrop( } finally { let srcWindowUtils = _getDOMWindowUtils(aWindow); const srcDragSession = srcWindowUtils.dragSession; - srcDragSession.endDragSession(true, _parseModifiers(aDragEvent)); + if (srcDragSession) { + // After each event handler, there is a microtask checkpoint. + // Event handlers or microtasks might've already ended our drag session. + // E.g. in SubDialog.open during browser_toolbar_drop_bookmarklet.js + srcDragSession.endDragSession(true, _parseModifiers(aDragEvent)); + } } } diff --git a/testing/mochitest/tests/SimpleTest/SimpleTest.js b/testing/mochitest/tests/SimpleTest/SimpleTest.js @@ -2162,11 +2162,15 @@ var add_task = (function () { // These checks ensure that we are in an HTML document without // throwing TypeError; also I am told that readyState in XUL documents // are totally bogus so we don't try to do this there. + // The readyState of the initial about:blank is stuck at "complete", + // so check for "about:blank" separately. if ( - typeof window !== "undefined" && - typeof HTMLDocument !== "undefined" && - window.document instanceof HTMLDocument && - window.document.readyState !== "complete" + (typeof window !== "undefined" && + typeof HTMLDocument !== "undefined" && + window.document instanceof HTMLDocument && + window.document.readyState !== "complete") || + (typeof window !== "undefined" && + window.document.location.href === "about:blank") ) { setTimeout(nextTick); return; diff --git a/testing/modules/XPCShellContentUtils.sys.mjs b/testing/modules/XPCShellContentUtils.sys.mjs @@ -146,7 +146,6 @@ export class ContentPage { Ci.nsIWebNavigation ); - chromeShell.createAboutBlankDocumentViewer(system, system); this.windowlessBrowser.browsingContext.useGlobalHistory = false; let loadURIOptions = { triggeringPrincipal: system, @@ -244,10 +243,15 @@ export class ContentPage { async loadURL(url, redirectUrl = undefined) { await this.browserReady; + let browserLoadedPromise = promiseBrowserLoaded( + this.browser, + url, + redirectUrl + ); this.browser.fixupAndLoadURIString(url, { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); - return promiseBrowserLoaded(this.browser, url, redirectUrl); + return browserLoadedPromise; } async fetch(...args) { diff --git a/testing/web-platform/meta/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js.ini b/testing/web-platform/meta/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js.ini @@ -1,2 +1,15 @@ [rewriter-from-detached-iframe.tentative.https.window.html] - expected: ERROR + [Detaching iframe during Rewriter.create() should not leak memory] + expected: FAIL + + [Rewriter.create() fails on a detached iframe.] + expected: FAIL + + [Rewriter.rewrite() fails on a detached iframe.] + expected: FAIL + + [Rewriter.rewriteStreaming() fails on a detached iframe.] + expected: FAIL + + [Detaching iframe during Rewriter.rewrite() should not leak memory] + expected: FAIL diff --git a/testing/web-platform/meta/ai/summarizer/summarizer-from-detached-iframe.tentative.https.window.js.ini b/testing/web-platform/meta/ai/summarizer/summarizer-from-detached-iframe.tentative.https.window.js.ini @@ -1,2 +1,15 @@ [summarizer-from-detached-iframe.tentative.https.window.html] - expected: ERROR + [Detaching iframe during Summarizer.create() should not leak memory] + expected: FAIL + + [Summarizer.create() fails on a detached iframe] + expected: FAIL + + [Summarizer.summarize() fails on a detached iframe] + expected: FAIL + + [Summarizer.summarizeStreaming() fails on a detached iframe] + expected: FAIL + + [Detaching iframe during Summarizer.summarize() should not leak memory] + expected: FAIL diff --git a/testing/web-platform/meta/ai/writer/writer-from-detached-iframe.tentative.https.window.js.ini b/testing/web-platform/meta/ai/writer/writer-from-detached-iframe.tentative.https.window.js.ini @@ -1,2 +1,15 @@ [writer-from-detached-iframe.tentative.https.window.html] - expected: ERROR + [Detaching iframe during Writer.create() should not leak memory] + expected: FAIL + + [Writer.create() fails on a detached iframe] + expected: FAIL + + [Writer.write() fails on a detached iframe] + expected: FAIL + + [Writer.writeStreaming() fails on a detached iframe] + expected: FAIL + + [Detaching iframe during Writer.write() should not leak memory] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-iframe-background-about-blank.tentative.html.ini b/testing/web-platform/meta/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-iframe-background-about-blank.tentative.html.ini @@ -1,2 +0,0 @@ -[color-scheme-iframe-background-about-blank.tentative.html] - expected: FAIL diff --git a/testing/web-platform/meta/custom-elements/registries/Element-customElementRegistry-exceptions.html.ini b/testing/web-platform/meta/custom-elements/registries/Element-customElementRegistry-exceptions.html.ini @@ -4,6 +4,3 @@ [customElementRegistry on a failed custom element created by setting innerHTML should return the associated scoped registry] expected: FAIL - - [customElementRegistry on a failed custom element created by parser should return the specified custom regsitry] - expected: FAIL diff --git a/testing/web-platform/meta/fetch/metadata/generated/element-frame.https.sub.html.ini b/testing/web-platform/meta/fetch/metadata/generated/element-frame.https.sub.html.ini @@ -1,5 +0,0 @@ -[element-frame.https.sub.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [sec-fetch-user with user activation] - expected: FAIL diff --git a/testing/web-platform/meta/fetch/metadata/generated/element-iframe.https.sub.html.ini b/testing/web-platform/meta/fetch/metadata/generated/element-iframe.https.sub.html.ini @@ -1,5 +0,0 @@ -[element-iframe.https.sub.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [sec-fetch-user with user activation] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/010.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/010.html.ini @@ -1,3 +0,0 @@ -[010.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html.ini @@ -1,8 +1,4 @@ [abort-document-load.html] [Aborting a Document load] - expected: - if (os == "win") and debug and (processor == "x86_64") and swgl: [PASS, FAIL] - if (os == "win") and debug and (processor == "x86"): [PASS, FAIL] - if (os == "linux") and swgl and not fission: [PASS, FAIL] - if (os == "mac") and not debug: [PASS, FAIL] - if (os == "linux") and not swgl: [PASS, FAIL] + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1791784 + expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js.ini @@ -1,34 +0,0 @@ -[child-navigates-parent-cross-origin.window.html] - expected: TIMEOUT - [Child document attempts to navigate cross-origin parent via location] - expected: TIMEOUT - - [Child document attempts to navigate cross-origin parent via location.hash] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.host] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.hostname] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.href] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.pathname] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.protocol] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.reload()] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.replace()] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via location.search] - expected: NOTRUN - - [Child document attempts to navigate cross-origin parent via non-standard location property] - expected: NOTRUN diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html.ini @@ -1,14 +0,0 @@ -[empty-iframe-load-event.html] - [Check execution order on load handler] - expected: - if not asan and not fission and sessionHistoryInParent and debug and (os == "linux"): [FAIL, PASS] - if not asan and not fission and sessionHistoryInParent and not debug: [FAIL, PASS] - if asan: [FAIL, PASS] - FAIL - - [Check execution order from nested timeout] - expected: - if not asan and not fission and sessionHistoryInParent and debug and (os == "linux"): [FAIL, PASS] - if not asan and not fission and sessionHistoryInParent and not debug: [FAIL, PASS] - if asan: [FAIL, PASS] - FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini @@ -3,3 +3,15 @@ if (os == "android") and fission: [OK, TIMEOUT] [link click] expected: FAIL + + [location.href] + expected: FAIL + + [location.assign] + expected: FAIL + + [window.open] + expected: FAIL + + [form submission] + expected: FAIL # these failures are fixed in the SH patch of the stack diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini @@ -3,3 +3,15 @@ if (os == "android") and fission: [OK, TIMEOUT] [Navigating to a different document with link click] expected: FAIL + + [Navigating to a different document with location.href] + expected: FAIL + + [Navigating to a different document with location.assign] + expected: FAIL + + [Navigating to a different document with window.open] + expected: FAIL + + [Navigating to a different document with form submission] + expected: FAIL # these failures are fixed in the SH patch of the stack diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html.ini @@ -1,43 +0,0 @@ -[initial-content-replacement.html] - [Content synchronously added to <iframe> with no src won't get replaced] - expected: - if (os == "linux") and debug: [FAIL, PASS] - FAIL - - [Content synchronously added to <iframe> with src='' won't get replaced] - expected: - if (os == "win") and not debug and (processor == "x86_64"): [FAIL, PASS] - if (os == "linux") and debug and not fission: [FAIL, PASS] - FAIL - - [Content synchronously added to <iframe> with src='about:blank' won't get replaced] - expected: - if (os == "win") and not debug and (processor == "x86_64"): [FAIL, PASS] - FAIL - - [Content synchronously added to <iframe> with src='about:blank#foo' won't get replaced] - expected: - if (os == "win") and not swgl and debug and (processor == "x86"): [FAIL, PASS] - if (os == "win") and not swgl and not debug and (processor == "x86_64"): [FAIL, PASS] - if (os == "linux") and debug and not fission and not swgl: [FAIL, PASS] - if (os == "linux") and debug and fission: [FAIL, PASS] - if (os == "win") and swgl: [FAIL, PASS] - FAIL - - [Content synchronously added to <iframe> with src='about:blank?foo' won't get replaced] - expected: - if (os == "linux") and debug and fission and swgl: [FAIL, PASS] - if (os == "linux") and debug and fission and not swgl: [FAIL, PASS] - if (os == "win") and not debug and (processor == "x86_64"): [FAIL, PASS] - if (os == "linux") and debug and not fission: [FAIL, PASS] - FAIL - - [Content synchronously added to window.open('about:blank')-ed document won't get replaced] - expected: - if not fission and (os == "linux") and not swgl: [FAIL, PASS] - FAIL - - [Content synchronously added to window.open('about:blank?foo')-ed document won't get replaced] - expected: - if (os == "android") and debug and not swgl: [FAIL, PASS] - FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html.ini @@ -1,17 +0,0 @@ -[load-event-iframe-element.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [load event fires synchronously on <iframe> element created with no src] - expected: FAIL - - [load event fires synchronously on <iframe> element created with src=''] - expected: FAIL - - [load event fires synchronously on <iframe> element created with src='about:blank'] - expected: FAIL - - [load event fires synchronously on <iframe> element created with src='about:blank#foo'] - expected: FAIL - - [load event fires synchronously on <iframe> element created with src='about:blank?foo'] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html.ini @@ -1,15 +0,0 @@ -[load-pageshow-events-iframe-contentWindow.html] - [load & pageshow event do not fire on contentWindow of <iframe> element created with no src] - expected: [FAIL, PASS] - - [load & pageshow events do not fire on contentWindow of <iframe> element created with src=''] - expected: [FAIL, PASS] - - [load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank'] - expected: [FAIL, PASS] - - [load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank#foo'] - expected: [FAIL, PASS] - - [load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank?foo'] - expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html.ini @@ -1,6 +0,0 @@ -[load-pageshow-events-window-open.html] - [load event does not fire on window.open('about:blank')] - expected: [FAIL, PASS] - - [load event does not fire on window.open('about:blank?foo')] - expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html.ini @@ -2,4 +2,19 @@ expected: if not fission and (os == "linux") and debug: TIMEOUT [after the first iframe load event, navigate iframe with no initial src.] - expected: FAIL + expected: FAIL # the whole test is outdated + + [synchronously navigate iframe with initial src == "".] + expected: FAIL # the whole test is outdated + + [after the first iframe load event, navigate iframe with initial src == "".] + expected: FAIL # the whole test is outdated + + [synchronously navigate iframe with initial src == "about:blank".] + expected: FAIL # the whole test is outdated + + [after the first iframe load event, navigate iframe with initial src == "about:blank".] + expected: FAIL # the whole test is outdated + + [synchronously navigate iframe with no initial src.] + expected: FAIL # the whole test is outdated diff --git a/testing/web-platform/meta/html/browsers/windows/browsing-context.html.ini b/testing/web-platform/meta/html/browsers/windows/browsing-context.html.ini @@ -1,8 +1,5 @@ [browsing-context.html] expected: - if (os == "android") and fission: [TIMEOUT, OK] - [Check that browsing context has new, ready HTML document] - expected: FAIL - + if (os == "android") and fission: [OK, TIMEOUT] [Check the document properties corresponding to the creator browsing context] expected: FAIL diff --git a/testing/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js.ini b/testing/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js.ini @@ -0,0 +1,4 @@ +[base-url-detached-document-srcdoc.https.window.html] + expected: ERROR + [about:srcdoc] + expected: TIMEOUT diff --git a/testing/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js.ini b/testing/web-platform/meta/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js.ini @@ -1,4 +0,0 @@ -[base-url-detached-document.https.window.html] - expected: ERROR - [about:srcdoc] - expected: TIMEOUT diff --git a/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-link-click-fragment.html.ini b/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-link-click-fragment.html.ini @@ -0,0 +1,3 @@ +[iframe-loading-lazy-nav-link-click-fragment.html] + [iframe-loading-lazy-nav-link-click-fragment] + expected: FAIL diff --git a/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html.ini b/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html.ini @@ -1,7 +0,0 @@ -[iframe-nosrc.html] - [load event of iframe should not be fired after processing the element] - expected: FAIL - - [iframe.contentDocument should not be changed] - expected: FAIL - diff --git a/testing/web-platform/meta/navigation-api/navigation-methods/navigate-from-initial-about-blank.html.ini b/testing/web-platform/meta/navigation-api/navigation-methods/navigate-from-initial-about-blank.html.ini @@ -1,3 +1,3 @@ [navigate-from-initial-about-blank.html] [navigate() from <iframe> still on initial about:blank works] - expected: [PASS, FAIL] -\ No newline at end of file + expected: [PASS, FAIL] diff --git a/testing/web-platform/meta/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html.ini b/testing/web-platform/meta/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html.ini @@ -1,3 +0,0 @@ -[navigate-push-initial-about-blank.html] - [navigate() with history: 'push' in initial about:blank document] - expected: FAIL diff --git a/testing/web-platform/meta/selection/getSelection.html.ini b/testing/web-platform/meta/selection/getSelection.html.ini @@ -1,8 +0,0 @@ -[getSelection.html] - expected: - if (os == "android") and fission: [TIMEOUT, OK] - [window.getSelection() instanceof Selection in an iframe immediately after appendChild] - expected: FAIL - - [document.getSelection() instanceof Selection in an iframe immediately after appendChild] - expected: FAIL diff --git a/testing/web-platform/meta/shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms/test-003.html.ini b/testing/web-platform/meta/shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms/test-003.html.ini @@ -1,5 +0,0 @@ -[test-003.html] - expected: TIMEOUT - [A_08_02_03_T01] - expected: TIMEOUT - diff --git a/testing/web-platform/meta/webdriver/tests/bidi/script/add_preload_script/execution_order_tentative.py.ini b/testing/web-platform/meta/webdriver/tests/bidi/script/add_preload_script/execution_order_tentative.py.ini @@ -1,24 +0,0 @@ -[execution_order_tentative.py] - [test_preload_script_properties_available_immediately[popup-current_context_with_url\]] - expected: FAIL - - [test_preload_script_properties_available_immediately[popup-current_context_without_url\]] - expected: FAIL - - [test_preload_script_properties_available_immediately[popup-opener_context_with_url\]] - expected: FAIL - - [test_preload_script_properties_available_immediately[popup-opener_context_without_url\]] - expected: FAIL - - [test_preload_script_properties_available_immediately[iframe-current_context_with_url\]] - expected: [FAIL, PASS] - - [test_preload_script_properties_available_immediately[iframe-current_context_without_url\]] - expected: [FAIL, PASS] - - [test_preload_script_properties_available_immediately[iframe-opener_context_with_url\]] - expected: FAIL - - [test_preload_script_properties_available_immediately[iframe-opener_context_without_url\]] - expected: FAIL diff --git a/testing/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini b/testing/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini @@ -4,3 +4,6 @@ [test_shared_worker] expected: FAIL + + [test_reload_context] + expected: FAIL # this is fixed in the window reuse patch of the stack diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js @@ -29,7 +29,7 @@ promise_test(async t => { }); let iframe1Loaded = false, iframe2Loaded = false; - iframe1.onload = e => { + iframe1.onload = t.step_func(e => { // iframe1 assertions: iframe1Loaded = true; assert_equals(window.frames.length, 1, @@ -45,15 +45,15 @@ promise_test(async t => { assert_equals(iframe2.contentWindow, null, "... but iframe1 cannot observe iframe2's contentWindow because " + "iframe2's insertion steps have not been run yet"); - }; + }); - iframe2.onload = e => { + iframe2.onload = t.step_func(e => { iframe2Loaded = true; assert_equals(window.frames.length, 2, "iframe2 load event can observe its own participation in the frame tree"); assert_equals(iframe1.contentWindow, window.frames[0]); assert_equals(iframe2.contentWindow, window.frames[1]); - }; + }); // Synchronously consecutively adds both `iframe1` and `iframe2` to the DOM, // invoking their insertion steps (and thus firing each of their `load` @@ -91,7 +91,7 @@ function runRemovalTest(removal_method) { // mutations (removals) are only observed atomically at the end. Specifically, // the observer's callback is not invoked synchronously for each removal. let observerCallbackInvoked = false; - const removalObserver = new MutationObserver(mutations => { + const removalObserver = new MutationObserver(t.step_func(mutations => { assert_false(observerCallbackInvoked, "MO callback is only invoked once, not multiple times, i.e., for " + "each removal"); @@ -103,7 +103,7 @@ function runRemovalTest(removal_method) { assert_equals(document.querySelector('iframe'), null, "No iframe elements are connected to the DOM when the MO callback is " + "run"); - }); + })); removalObserver.observe(div, {childList: true}); t.add_cleanup(() => removalObserver.disconnect()); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html @@ -1,17 +0,0 @@ -<!doctype html> -<title>Link with onclick form submit to javascript url with delayed document.write and href navigation </title> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<div id="log"></div> -<iframe id="test" name="test"></iframe> -<form target="test" action="javascript:(function() {var x = new XMLHttpRequest(); x.open('GET', 'resources/blank.html?pipe=trickle(d2)', false); x.send(); document.write('<script>parent.postMessage(&quot;write&quot;, &quot;*&quot;)</script>'); return '<script>parent.postMessage(&quot;click&quot;, &quot;*&quot;)</script>'})()"></form> -<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> -<script> -var t = async_test(); -onload = t.step_func(function() {document.getElementsByTagName("a")[0].click()}); -onmessage = t.step_func( - function(e) { - assert_equals(e.data, "href"); - t.done(); - }); -</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>Link with onclick form submit to javascript url with delayed document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var flag = false; +</script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<form target="test" action="javascript:(function() {parent.flag = true; var x = new XMLHttpRequest(); x.open('GET', 'resources/blank.html?pipe=trickle(d2)', false); x.send(); document.write('WRITE <script>parent.postMessage(&quot;write&quot;, &quot;*&quot;)</script>'); return 'RETURN <script>parent.postMessage(&quot;click&quot;, &quot;*&quot;)</script>'})()"></form> +<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +onload = t.step_func(function() {document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(flag, true); + assert_equals(e.data, "write"); + t.done(); + }); +</script> +<!-- +Tentative, because: + * Chrome doesn't appear to execute the javascript: URL at all. + * Safari seems to start the navigation to href.html to the extent + that the sync XHR goes away, but then document.write() takes + place so that postMessage succeeds and then the DOM for href.html + replaces the DOM for the document.write(). + * In Firefox, the navigation to href.html makes the sync XHR go + away, but then document.write() wins over the href.html parse. +--> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html @@ -14,7 +14,7 @@ async_test(test => { const frame = document.querySelector('iframe'); const child = frame.contentWindow; assert_equals(child.document.readyState, 'complete', 'readyState is complete'); - assert_array_equals(events, ["loading", "DOMContentLoaded", "stop", "complete"], 'no load event was fired'); + assert_array_equals(events, ["loading", "stop"], 'no load event was fired'); events = []; frame.src = "abort-document-load-2.html"; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html @@ -175,4 +175,24 @@ promise_test(async t => { assert_equals(history.length, startingHistoryLength + 1, "history.length increases after normal navigation from non-initial empty document"); }, "Navigating to a different document with form submission"); + +promise_test(async t => { + // The ready state is asserted to be what it is currently Blink, Gecko, WebKit + const iframe = insertIframeWith204Src(t); + assert_equals(iframe.contentDocument.readyState, "complete"); + + let loaded = false; + iframe.onload = () => loaded = true; + + iframe.src = "about:blank"; + assert_false(loaded, "No sync load event"); + assert_equals(iframe.contentDocument.readyState, "complete"); + + await Promise.resolve(); + assert_false(loaded, "No load event after microtask checkpoint"); + + await waitForLoad(t, iframe, "about:blank"); + assert_true(loaded, "Iframe loaded eventually"); + assert_equals(iframe.contentDocument.readyState, "complete"); +}, "about:blank on top of 204 loads async"); </script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html @@ -16,6 +16,7 @@ promise_test(async t => { }); document.body.appendChild(iframe); assert_equals(loadCount, 1); + assert_equals(iframe.contentDocument.location.href, "about:blank"); }, "load event fires synchronously on <iframe> element created with no src"); promise_test(async t => { @@ -27,6 +28,7 @@ promise_test(async t => { }); document.body.appendChild(iframe); assert_equals(loadCount, 1); + assert_equals(iframe.contentDocument.location.href, "about:blank"); }, "load event fires synchronously on <iframe> element created with src=''"); promise_test(async t => { @@ -38,6 +40,7 @@ promise_test(async t => { }); document.body.appendChild(iframe); assert_equals(loadCount, 1); + assert_equals(iframe.contentDocument.location.href, "about:blank"); }, "load event fires synchronously on <iframe> element created with src='about:blank'"); promise_test(async t => { @@ -49,6 +52,7 @@ promise_test(async t => { }); document.body.appendChild(iframe); assert_equals(loadCount, 1); + assert_equals(iframe.contentDocument.location.href, "about:blank#foo"); }, "load event fires synchronously on <iframe> element created with src='about:blank#foo'"); promise_test(async t => { @@ -60,5 +64,6 @@ promise_test(async t => { }); document.body.appendChild(iframe); assert_equals(loadCount, 1); + assert_equals(iframe.contentDocument.location.href, "about:blank?foo"); }, "load event fires synchronously on <iframe> element created with src='about:blank?foo'"); </script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html @@ -5,6 +5,15 @@ <script src="/resources/testharnessreport.js"></script> <body> <script> +function waitForEvent(name, target) { + return new Promise(resolve => { + function listener(event) { + resolve(event); + } + target.addEventListener(name, listener, { once: true }); + }); +} + // Check what happens when executing window.open("about:srcdoc") from a // sandboxed iframe. Srcdoc can't be loaded in the main frame. It should // result in an error page. The error page should be cross-origin with the @@ -16,7 +25,7 @@ // applies the sandbox flags of the opener to the internal error page document. // // This test is mainly a coverage test. It passes if it doesn't crash. -async_test(test => { +promise_test(async t => { let iframe = document.createElement("iframe"); iframe.sandbox = "allow-scripts allow-popups allow-same-origin"; iframe.srcdoc = ` @@ -40,13 +49,31 @@ async_test(test => { </scr`+`ipt> `; - let closed = false; - addEventListener("message", event => { - closed = (event.data === "done"); - iframe.contentWindow.postMessage("ping","*"); - }); - + let msg = waitForEvent("message", window); document.body.appendChild(iframe); - test.step_wait_func_done(()=>closed); + while ( (await msg).data !== "done" ) { + iframe.contentWindow.postMessage("ping","*"); + msg = waitForEvent("message", window); + } + iframe.remove(); }, "window.open('about:srcdoc') from sandboxed srcdoc doesn't crash."); + +promise_test(async t => { + let ifr = document.createElement("iframe"); + ifr.sandbox = "allow-scripts allow-popups"; + ifr.srcdoc = `<script> + const w = window.open(); + try { + w.document; + parent.postMessage("fail", "*") + } catch (e) { + parent.postMessage(e.name, "*") + } + </scri`+`pt>`; + + const msg = waitForEvent("message", window); + document.body.appendChild(ifr); + const data = (await msg).data; + assert_equals(data, "SecurityError", ""); +}, "popup is isolated from an isolated iframe"); </script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test that a resize event is fired on newly opened windows</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +promise_test(async t => { + popup = window.open("about:blank", "_blank", "width=500,height=500"); + await new Promise(resolve => popup.onresize = resolve); + assert_true(true, "Got resize event"); +}, "Opening a popup with specified size fires a resize event"); + +promise_test(async t => { + popup = window.open("about:blank", "_blank", "popup"); + await new Promise(resolve => popup.onresize = resolve); + assert_true(true, "Got resize event"); +}, "Opening a popup fires a resize event"); + +promise_test(async t => { + popup = window.open("about:blank"); + await new Promise(resolve => popup.onresize = resolve); + assert_true(true, "Got resize event"); +}, "Opening a window fires a resize event"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/clear-window-name.https.html b/testing/web-platform/tests/html/browsers/windows/clear-window-name.https.html @@ -51,6 +51,14 @@ promise_test(async t => { promise_test(async t => { const id = token(); + w = window.open("about:blank", id); + w.location = `resources/window-name.sub.html?cross|same|report=${id}|close`; + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset at the first navigation away from loaded initial about:blank"); + +promise_test(async t => { + const id = token(); + window.open(`resources/window-name.sub.html?report=${id}|close`, id, "noopener"); await pollResultAndCheck(t, id, id); }, "Window.name is not reset at the first navigation away from initial about:blank with noopener"); diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js @@ -0,0 +1,29 @@ +// Verify that an about:blank document remembers the baseURI +// it was created with even after it's detached. +const runTest = () => { + async_test((t) => { + const frame = document.createElement('iframe'); + frame.src = "about:blank"; + + frame.onload = () => { + const frame_doc = frame.contentDocument; + const initial_base_uri = document.baseURI; + assert_equals(initial_base_uri, frame_doc.baseURI); + + const base_element = document.createElement('base'); + base_element.href = "https://example.com"; + document.head.appendChild(base_element); + assert_equals(initial_base_uri, frame_doc.baseURI); + + frame.remove(); + assert_equals(initial_base_uri, frame_doc.baseURI); + t.done(); + }; + + document.body.appendChild(frame); + }, "about:blank"); +}; + +onload = () => { + runTest(); +}; diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js @@ -0,0 +1,29 @@ +// Verify that an about:srcdoc document remembers the baseURI +// it was created with even after it's detached. +const runTest = () => { + async_test((t) => { + const frame = document.createElement('iframe'); + frame.srcdoc = "foo"; + + frame.onload = () => { + const frame_doc = frame.contentDocument; + const initial_base_uri = document.baseURI; + assert_equals(initial_base_uri, frame_doc.baseURI); + + const base_element = document.createElement('base'); + base_element.href = "https://example.com"; + document.head.appendChild(base_element); + assert_equals(initial_base_uri, frame_doc.baseURI); + + frame.remove(); + assert_equals(initial_base_uri, frame_doc.baseURI); + t.done(); + }; + + document.body.appendChild(frame); + }, "about:srcdoc"); +}; + +onload = () => { + runTest(); +}; diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js @@ -1,34 +0,0 @@ -// Verify that an about:blank or about:srcdoc document remembers the baseURI -// it was created with even after it's detached. -const runTest = (frame_type) => { - async_test((t) => { - const frame = document.createElement('iframe'); - - if (frame_type == "about:blank") - frame.src = "about:blank"; - else - frame.srcdoc = "foo"; - - frame.onload = () => { - const frame_doc = frame.contentDocument; - const initial_base_uri = document.baseURI; - assert_equals(initial_base_uri, frame_doc.baseURI); - - const base_element = document.createElement('base'); - base_element.href = "https://example.com"; - document.head.appendChild(base_element); - assert_equals(initial_base_uri, frame_doc.baseURI); - - frame.remove(); - assert_equals(initial_base_uri, frame_doc.baseURI); - t.done(); - }; - - document.body.appendChild(frame); - }, frame_type); -}; - -onload = () => { - runTest("about:blank"); - runTest("about:srcdoc"); -}; diff --git a/testing/web-platform/tests/old-tests/submission/Microsoft/history/history_000.htm b/testing/web-platform/tests/old-tests/submission/Microsoft/history/history_000.htm @@ -319,7 +319,9 @@ function startTestsWhenIframesLoaded() { if (testframe1.contentWindow.document.readyState != 'complete' || - testframe2.contentWindow.document.readyState != 'complete') { + testframe2.contentWindow.document.readyState != 'complete' || + testframe1.contentDocument.location.href == 'about:blank' || + testframe2.contentDocument.location.href == 'about:blank') { return; } testframe1.removeEventListener('load', startTestsWhenIframesLoaded, false); diff --git a/testing/web-platform/tests/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py b/testing/web-platform/tests/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py @@ -28,6 +28,18 @@ async def test_add_preload_script( ) assert result == {"type": "string", "value": "bar"} + await bidi_session.browsing_context.reload( + context=new_context["context"], wait="complete" + ) + + # Check that preload script was applied after reload + result = await bidi_session.script.evaluate( + expression="window.foo", + target=ContextTarget(new_context["context"]), + await_promise=True, + ) + assert result == {"type": "string", "value": "bar"} + url = inline("<div>foo</div>") await bidi_session.browsing_context.navigate( context=new_context["context"], diff --git a/toolkit/components/aboutprocesses/tests/browser/browser_aboutprocesses_shortcut.js b/toolkit/components/aboutprocesses/tests/browser/browser_aboutprocesses_shortcut.js @@ -9,6 +9,9 @@ add_task(async function () { await BrowserTestUtils.waitForEvent(window, "MozLayerTreeReady"); } + // Allow the initial about:blank to settle. + await new Promise(resolve => setTimeout(resolve, 0)); + EventUtils.synthesizeKey("KEY_Escape", { shiftKey: true }); await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); diff --git a/toolkit/components/antitracking/AntiTrackingUtils.cpp b/toolkit/components/antitracking/AntiTrackingUtils.cpp @@ -1252,7 +1252,7 @@ bool AntiTrackingUtils::IsThirdPartyContext(BrowsingContext* aBrowsingContext) { if (!parentDocShell) { return true; } - Document* parentDoc = parentDocShell->GetDocument(); + Document* parentDoc = parentDocShell->GetExtantDocument(); if (!parentDoc || parentDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) { return true; } diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py b/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py @@ -2,6 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +from marionette_driver import Wait from marionette_harness import MarionetteTestCase # Tests the persistence of the bounce tracking protection storage across @@ -48,14 +49,23 @@ class BounceTrackingStoragePersistenceTestCase(MarionetteTestCase): def test_state_after_restart(self): self.marionette.restart(clean=False, in_app=True) - bounceTrackerCandidates = self.marionette.execute_script( - """ - let bounceTrackingProtection = Cc["@mozilla.org/bounce-tracking-protection;1"].getService( - Ci.nsIBounceTrackingProtection + + def get_candidates(_): + return self.marionette.execute_script( + """ + const bounceTrackingProtection = Cc["@mozilla.org/bounce-tracking-protection;1"].getService( + Ci.nsIBounceTrackingProtection ); - return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).map(entry => entry.siteHost).sort(); - """, + return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}) + .map(entry => entry.siteHost) + .sort(); + """ + ) + + bounceTrackerCandidates = Wait(self.marionette).until( + get_candidates, message="Wait for persistent state to be restored" ) + self.assertEqual( len(bounceTrackerCandidates), 2, diff --git a/toolkit/components/browser/nsWebBrowser.cpp b/toolkit/components/browser/nsWebBrowser.cpp @@ -7,6 +7,10 @@ // Local Includes #include "nsWebBrowser.h" +// Hack: nsIOpenWindowInfo depends on this without being able to include it +#include "mozilla/Assertions.h" +#include "mozilla/dom/BrowserParent.h" + // Helper Classes #include "nsGfxCIID.h" #include "nsWidgetsCID.h" @@ -31,6 +35,7 @@ #include "nsDocShell.h" #include "nsServiceManagerUtils.h" #include "WindowRenderer.h" +#include "nsOpenWindowInfo.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/BrowsingContext.h" @@ -80,9 +85,14 @@ nsIWidget* nsWebBrowser::EnsureWidget() { already_AddRefed<nsWebBrowser> nsWebBrowser::Create( nsIWebBrowserChrome* aContainerWindow, nsIWidget* aParentWidget, dom::BrowsingContext* aBrowsingContext, - dom::WindowGlobalChild* aInitialWindowChild) { + dom::WindowGlobalChild* aInitialWindowChild, + nsIOpenWindowInfo* aOpenWindowInfo) { + MOZ_ASSERT(aOpenWindowInfo, "Must have openwindowinfo"); MOZ_ASSERT_IF(aInitialWindowChild, aInitialWindowChild->BrowsingContext() == aBrowsingContext); + MOZ_ASSERT_IF(aInitialWindowChild, + aInitialWindowChild->DocumentPrincipal() == + aOpenWindowInfo->PrincipalToInheritForAboutBlank()); RefPtr<nsWebBrowser> browser = new nsWebBrowser( aBrowsingContext->IsContent() ? typeContentWrapper : typeChromeWrapper); @@ -129,7 +139,8 @@ already_AddRefed<nsWebBrowser> nsWebBrowser::Create( // events from subframes. To solve that we install our own chrome event // handler that always gets called (even for subframes) for any bubbling // event. - nsresult rv = docShell->InitWindow(docShellParentWidget, 0, 0, 0, 0); + nsresult rv = docShell->InitWindow(docShellParentWidget, 0, 0, 0, 0, + aOpenWindowInfo, aInitialWindowChild); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } @@ -137,10 +148,6 @@ already_AddRefed<nsWebBrowser> nsWebBrowser::Create( docShellTreeOwner->AddToWatcher(); // evil twin of Remove in SetDocShell(0) docShellTreeOwner->AddChromeListeners(); - if (aInitialWindowChild) { - docShell->CreateDocumentViewerForActor(aInitialWindowChild); - } - return browser.forget(); } @@ -840,15 +847,6 @@ nsWebBrowser::Cancel(nsresult aReason) { //***************************************************************************** NS_IMETHODIMP -nsWebBrowser::InitWindow(nsIWidget* aParentWidget, int32_t aX, int32_t aY, - int32_t aCX, int32_t aCY) { - // nsIBaseWindow::InitWindow and nsIBaseWindow::Create - // implementations have been merged into nsWebBrowser::Create - MOZ_DIAGNOSTIC_CRASH("Superceded by nsWebBrowser::Create()"); - return NS_ERROR_NULL_POINTER; -} - -NS_IMETHODIMP nsWebBrowser::Destroy() { InternalDestroy(); diff --git a/toolkit/components/browser/nsWebBrowser.h b/toolkit/components/browser/nsWebBrowser.h @@ -33,6 +33,8 @@ #include "nsTArray.h" #include "nsIWeakReferenceUtils.h" +class nsIOpenWindowInfo; + class nsWebBrowserInitInfo { public: // nsIBaseWindow Stuff @@ -90,7 +92,8 @@ class nsWebBrowser final : public nsIWebBrowser, static already_AddRefed<nsWebBrowser> Create( nsIWebBrowserChrome* aContainerWindow, nsIWidget* aParentWidget, mozilla::dom::BrowsingContext* aBrowsingContext, - mozilla::dom::WindowGlobalChild* aInitialWindowChild); + mozilla::dom::WindowGlobalChild* aInitialWindowChild, + nsIOpenWindowInfo* aOpenWindowInfo); protected: virtual ~nsWebBrowser(); diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js @@ -47,6 +47,9 @@ function assertNoCookies() { */ async function visitTestSites(urls = [ORIGIN_A, ORIGIN_B, ORIGIN_C]) { let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); for (let url of urls) { BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url); @@ -344,6 +347,9 @@ add_task(async function test_pbm() { private: true, }); let tab = BrowserTestUtils.addTab(pbmWindow.gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_A); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); @@ -580,6 +586,9 @@ add_task(async function test_site_preference_pbm() { private: true, }); let tab = BrowserTestUtils.addTab(pbmWindow.gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_B); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); diff --git a/toolkit/components/extensions/ExtensionContent.sys.mjs b/toolkit/components/extensions/ExtensionContent.sys.mjs @@ -499,12 +499,13 @@ class Script { } try { - // In case of initial about:blank documents, inject immediately without - // awaiting the runAt logic in the blocks below, to avoid getting stuck - // due to https://bugzilla.mozilla.org/show_bug.cgi?id=1900222#c7 + // In case of uncommitted initial about:blank documents, inject + // immediately without awaiting the runAt logic in the blocks below, to + // avoid getting stuck due to + // https://bugzilla.mozilla.org/show_bug.cgi?id=1900222#c7 // This is only relevant for dynamic code execution because declarative - // content scripts do not run on initial about:blank - bug 1415539). - if (!window.document.isInitialDocument) { + // content scripts do not run on this about:blank - bug 1415539. + if (!window.document.isUncommittedInitialDocument) { if (this.runAt === "document_end") { await promiseDocumentReady(window.document); } else if (this.runAt === "document_idle") { diff --git a/toolkit/components/extensions/ExtensionParent.sys.mjs b/toolkit/components/extensions/ExtensionParent.sys.mjs @@ -1490,6 +1490,9 @@ class HiddenXULWindow { awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated"); } + // Prevent initial about:blank load before navigating to extension URI + browser.setAttribute("nodefaultsrc", "true"); + chromeDoc.documentElement.appendChild(browser); // Forcibly flush layout so that we get a pres shell soon enough, see diff --git a/toolkit/components/extensions/WebExtensionPolicy.cpp b/toolkit/components/extensions/WebExtensionPolicy.cpp @@ -1089,9 +1089,10 @@ bool DocInfo::IsTopLevelOpaqueAboutBlank() const { bool isFinalAboutBlankDoc = mThis.URL().Scheme() == nsGkAtoms::about && mThis.URL().Spec().EqualsLiteral("about:blank") && - // Exclude initial about:blank to avoid matching initial about:blank - // of pending loads in the parent process, see bug 1901894. - !aWin->GetDoc()->IsInitialDocument(); + // Exclude uncommitted initial about:blank to avoid matching + // about:blank of pending non-blank loads in the parent process, + // see bug 1901894. + !aWin->GetDoc()->IsUncommittedInitialDocument(); // Principal() is expected to never be nullptr given a Window. MOZ_ASSERT(mThis.Principal()); diff --git a/toolkit/components/extensions/parent/ext-downloads.js b/toolkit/components/extensions/parent/ext-downloads.js @@ -1211,12 +1211,6 @@ this.downloads = class extends ExtensionAPIPersistent { let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); - let systemPrincipal = - Services.scriptSecurityManager.getSystemPrincipal(); - windowlessBrowser.docShell.createAboutBlankDocumentViewer( - systemPrincipal, - systemPrincipal - ); let canvas = windowlessBrowser.document.createElement("canvas"); let img = new windowlessBrowser.docShell.domWindow.Image(size, size); diff --git a/toolkit/components/extensions/parent/ext-identity.js b/toolkit/components/extensions/parent/ext-identity.js @@ -109,7 +109,7 @@ const openOAuthWindow = (details, redirectURI) => { // If the user just closes the window we need to reject unloadListener = () => { - if (window.document.isInitialDocument) { + if (window.document.isUncommittedInitialDocument) { // The "unload" event also fires when the initial "about:blank" // document transitions to the browser document, ignore it. return; diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html @@ -65,6 +65,7 @@ add_task(async function test_contentscript_data_uri() { // Hold on to the tab by the browser, as extension loads are COOP loads, and // will break WindowProxy references. let win = window.open(); + await scripts.awaitMessage("tab-ready"); const browserFrame = win.browsingContext.embedderElement; win.location.href = page; diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html @@ -229,7 +229,7 @@ async function top_level_about_blank({ if (filename === "2.does_not_care_about_exclude_matches_globs.js") { files[filename] += ` - // In an initial document, readyState = "uninitialized". + // In an initial document, readyState = "complete". dump("${filename} ran at readyState " + document.readyState + "\\n"); browser.test.sendMessage("seen_content_script"); `; diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html @@ -23,7 +23,6 @@ add_task(async function setup() { }); testWindow = window.open("about:blank", "_blank", "width=100,height=100"); - await waitForLoad(testWindow); // Fetch the windowId and tabId we need to filter with WebRequest. let extension = ExtensionTestUtils.loadExtension({ diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js @@ -213,8 +213,9 @@ add_task(async function test_unload_extension_during_background_page_startup() { backgroundPageUrl, "Expected background page" ); - // Reset to "about:blank" to not load the actual background page. - arguments[0] = "about:blank"; + // Reset URI to not load the actual background page. + // See Bug 1955324, loading about:blank with system principal crashes, so lets use data uri + arguments[0] = "data:text/html,"; browserFixupAndLoadURIString.apply(this, arguments); // And force the extension process to crash. diff --git a/toolkit/components/passwordmgr/test/browser/browser_basicAuth_switchTab.js b/toolkit/components/passwordmgr/test/browser/browser_basicAuth_switchTab.js @@ -4,6 +4,12 @@ add_task(async function test_auth_switchtab() { let tab = BrowserTestUtils.addTab(gBrowser); + // initial about:blank won't be canceled by subsequent load. So ensure it is + // finished. Otherwise, locationchange notification from content can cancel + // the auth dialog opened in the parent + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); isnot(tab, gBrowser.selectedTab, "New tab shouldn't be selected"); let authPromptShown = PromptTestUtils.waitForPrompt(tab.linkedBrowser, { diff --git a/toolkit/components/resistfingerprinting/tests/browser/browser_fingerprintingWebCompat.js b/toolkit/components/resistfingerprinting/tests/browser/browser_fingerprintingWebCompat.js @@ -307,7 +307,11 @@ async function openAndSetupTestPageForPopup() { let win; let loading = new content.Promise(resolve => { win = content.open(src); - win.onload = resolve; + if (src == "about:blank") { + resolve(); + } else { + win.onload = resolve; + } }); await loading; diff --git a/toolkit/components/thumbnails/BackgroundPageThumbs.sys.mjs b/toolkit/components/thumbnails/BackgroundPageThumbs.sys.mjs @@ -593,7 +593,8 @@ Capture.prototype = { this._done(browser, null, TEL_CAPTURE_DONE_BAD_URI); } }, - () => { + err => { + console.error(err); // The query can fail when a crash occurs while loading. The error causes // thumbnail crash tests to fail with an uninteresting error message. } diff --git a/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl b/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl @@ -4,10 +4,15 @@ * License, v. 2.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 "nsILoadInfo.idl" #include "nsISupports.idl" webidl BrowsingContext; +interface nsIPrincipal; +interface nsIPolicyContainer; +interface nsIURI; + %{ C++ namespace mozilla { class OriginAttributes; @@ -18,6 +23,7 @@ class BrowserParent; %} [ref] native const_OriginAttributes(const mozilla::OriginAttributes); [ptr] native BrowserParent(mozilla::dom::BrowserParent); +[ref] native MaybeCOEPRef(const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>); /* * nsIBrowsingContextReadyCallback.browsingContextReady() is called within @@ -79,6 +85,36 @@ interface nsIOpenWindowInfo : nsISupports { [infallible] readonly attribute boolean textDirectiveUserActivation; + /** + * The principal to inherit into the initial about:blank. + */ + [notxpcom, nostdcall, binaryname(PrincipalToInheritForAboutBlank)] + nsIPrincipal binaryGetPrincipalToInheritForAboutBlank(); + + /** + * The partitioned principal to inherit into the initial about:blank. + */ + [notxpcom, nostdcall, binaryname(PartitionedPrincipalToInheritForAboutBlank)] + nsIPrincipal binaryGetPartitionedPrincipalToInheritForAboutBlank(); + + /** + * The base URI to inherit into the initial about:blank. + */ + [notxpcom, nostdcall, binaryname(BaseUriToInheritForAboutBlank)] + nsIURI binaryGetBaseUriToInheritForAboutBlank(); + + /** + * The CSP to inherit into the initial about:blank. + */ + [notxpcom, nostdcall, binaryname(PolicyContainerToInheritForAboutBlank)] + nsIPolicyContainer binaryGetPolicyContainerToInheritForAboutBlank(); + + /** + * The COEP to inherit into the initial about:blank. + */ + [notxpcom, nostdcall, binaryname(CoepToInheritForAboutBlank)] + MaybeCOEPRef binaryGetCoepToInheritForAboutBlank(); + /** BrowserParent instance to use in the new window */ [notxpcom, nostdcall] BrowserParent getNextRemoteBrowser(); @@ -99,4 +135,10 @@ interface nsIOpenWindowInfo : nsISupports { * callback. If no callback, do nothing. */ void cancel(); + + /** + * Clone this open window info and update the princpals to be inherited by the + * initial about:blank document. + */ + nsIOpenWindowInfo cloneWithPrincipals(in nsIPrincipal aPrincipal, in nsIPrincipal aPartitionedPrincipal); }; diff --git a/toolkit/components/windowwatcher/nsIWindowWatcher.idl b/toolkit/components/windowwatcher/nsIWindowWatcher.idl @@ -13,7 +13,8 @@ interface nsIAuthPrompt; interface nsISimpleEnumerator; interface nsIWebBrowserChrome; interface nsIWindowCreator; - +interface nsIOpenWindowInfo; +interface nsIPrincipal; /** * nsIWindowWatcher is the keeper of Gecko/DOM Windows. It maintains @@ -153,7 +154,6 @@ interface nsIWindowWatcher : nsISupports { Retrieves the active window from the focus manager. */ readonly attribute mozIDOMWindowProxy activeWindow; - }; %{C++ diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp b/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp @@ -16,6 +16,25 @@ NS_IMPL_ISUPPORTS_CI(nsOpenWindowInfo, nsIOpenWindowInfo); nsOpenWindowInfo::nsOpenWindowInfo() = default; nsOpenWindowInfo::~nsOpenWindowInfo() = default; +nsOpenWindowInfo::nsOpenWindowInfo(const nsOpenWindowInfo& aOther) + : mForceNoOpener(aOther.mForceNoOpener), + mIsRemote(aOther.mIsRemote), + mIsForPrinting(aOther.mIsForPrinting), + mIsForWindowDotPrint(aOther.mIsForWindowDotPrint), + mIsTopLevelCreatedByWebContent(aOther.mIsTopLevelCreatedByWebContent), + mHasValidUserGestureActivation(aOther.mHasValidUserGestureActivation), + mTextDirectiveUserActivation(aOther.mTextDirectiveUserActivation), + mNextRemoteBrowser(aOther.mNextRemoteBrowser), + mParent(aOther.mParent), + mBrowsingContextReadyCallback(aOther.mBrowsingContextReadyCallback), + mPrincipalToInheritForAboutBlank(aOther.mPrincipalToInheritForAboutBlank), + mPartitionedPrincipalToInheritForAboutBlank( + aOther.mPartitionedPrincipalToInheritForAboutBlank), + mBaseUriToInheritForAboutBlank(aOther.mBaseUriToInheritForAboutBlank), + mPolicyContainerToInheritForAboutBlank( + aOther.mPolicyContainerToInheritForAboutBlank), + mCoepToInheritForAboutBlank(aOther.mCoepToInheritForAboutBlank) {}; + NS_IMETHODIMP nsOpenWindowInfo::GetParent( mozilla::dom::BrowsingContext** aParent) { *aParent = do_AddRef(mParent).take(); @@ -62,13 +81,39 @@ NS_IMETHODIMP nsOpenWindowInfo::GetTextDirectiveUserActivation( NS_IMETHODIMP nsOpenWindowInfo::GetScriptableOriginAttributes( JSContext* aCx, JS::MutableHandle<JS::Value> aAttrs) { - bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs); + bool ok = ToJSValue(aCx, GetOriginAttributes(), aAttrs); NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); return NS_OK; } +nsIPrincipal* nsOpenWindowInfo::PrincipalToInheritForAboutBlank() { + MOZ_ASSERT(mPrincipalToInheritForAboutBlank, "Must have principal"); + return mPrincipalToInheritForAboutBlank; +} + +nsIPrincipal* nsOpenWindowInfo::PartitionedPrincipalToInheritForAboutBlank() { + if (mPartitionedPrincipalToInheritForAboutBlank) { + return mPartitionedPrincipalToInheritForAboutBlank; + } + MOZ_ASSERT(mPrincipalToInheritForAboutBlank, "Must have principal"); + return mPrincipalToInheritForAboutBlank; +} + +nsIURI* nsOpenWindowInfo::BaseUriToInheritForAboutBlank() { + return mBaseUriToInheritForAboutBlank; +} + +nsIPolicyContainer* nsOpenWindowInfo::PolicyContainerToInheritForAboutBlank() { + return mPolicyContainerToInheritForAboutBlank; +} + +const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& +nsOpenWindowInfo::CoepToInheritForAboutBlank() { + return mCoepToInheritForAboutBlank; +} + const mozilla::OriginAttributes& nsOpenWindowInfo::GetOriginAttributes() { - return mOriginAttributes; + return mPrincipalToInheritForAboutBlank->OriginAttributesRef(); } mozilla::dom::BrowserParent* nsOpenWindowInfo::GetNextRemoteBrowser() { @@ -118,3 +163,15 @@ NS_IMETHODIMP nsBrowsingContextReadyCallback::BrowsingContextReady( mPromise = nullptr; return NS_OK; } + +NS_IMETHODIMP nsOpenWindowInfo::CloneWithPrincipals( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, + nsIOpenWindowInfo** aClone) { + NS_ENSURE_ARG(aPrincipal); + NS_ENSURE_ARG(aPartitionedPrincipal); + RefPtr<nsOpenWindowInfo> clone = new nsOpenWindowInfo(*this); + clone->mPrincipalToInheritForAboutBlank = aPrincipal; + clone->mPartitionedPrincipalToInheritForAboutBlank = aPartitionedPrincipal; + clone.forget(aClone); + return NS_OK; +} diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.h b/toolkit/components/windowwatcher/nsOpenWindowInfo.h @@ -23,6 +23,7 @@ class nsOpenWindowInfo : public nsIOpenWindowInfo { NS_DECL_NSIOPENWINDOWINFO nsOpenWindowInfo(); + nsOpenWindowInfo(const nsOpenWindowInfo& aOther); bool mForceNoOpener = false; bool mIsRemote = false; @@ -32,9 +33,14 @@ class nsOpenWindowInfo : public nsIOpenWindowInfo { bool mHasValidUserGestureActivation = false; bool mTextDirectiveUserActivation = false; RefPtr<mozilla::dom::BrowserParent> mNextRemoteBrowser; - mozilla::OriginAttributes mOriginAttributes; RefPtr<mozilla::dom::BrowsingContext> mParent; RefPtr<nsIBrowsingContextReadyCallback> mBrowsingContextReadyCallback; + nsCOMPtr<nsIPrincipal> mPrincipalToInheritForAboutBlank; + nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInheritForAboutBlank; + nsCOMPtr<nsIURI> mBaseUriToInheritForAboutBlank; + nsCOMPtr<nsIPolicyContainer> mPolicyContainerToInheritForAboutBlank; + mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> + mCoepToInheritForAboutBlank; private: virtual ~nsOpenWindowInfo(); diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -886,43 +886,43 @@ nsresult nsWindowWatcher::OpenWindowInternal( : nsContentUtils::GetSystemPrincipal(); MOZ_ASSERT(subjectPrincipal); - nsCOMPtr<nsIPrincipal> newWindowPrincipal; + // Information used when opening new content windows. This object will be + // passed through to the inner nsFrameLoader. + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + if (!targetBC) { if (windowTypeIsChrome) { // If we are creating a chrome window, we must be called with a system // principal, and should inherit that for the new chrome window. MOZ_RELEASE_ASSERT(subjectPrincipal->IsSystemPrincipal(), "Only system principals can create chrome windows"); - newWindowPrincipal = subjectPrincipal; + openWindowInfo->mPrincipalToInheritForAboutBlank = subjectPrincipal; } else if (nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal)) { // Don't allow initial about:blank documents to inherit a system or // expanded principal, instead replace it with a null principal. We can't // inherit origin attributes from the system principal, so use the parent // BC if it's available. if (parentBC) { - newWindowPrincipal = + openWindowInfo->mPrincipalToInheritForAboutBlank = NullPrincipal::Create(parentBC->OriginAttributesRef()); } else { - newWindowPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); + openWindowInfo->mPrincipalToInheritForAboutBlank = + NullPrincipal::CreateWithoutOriginAttributes(); } } else if (aForceNoOpener) { // If we're opening a new window with noopener, create a new opaque // principal for the new window, rather than re-using the existing // principal. - newWindowPrincipal = + openWindowInfo->mPrincipalToInheritForAboutBlank = NullPrincipal::CreateWithInheritedAttributes(subjectPrincipal); } else { // Finally, if there's an opener relationship and it's not a special // principal, we should inherit that principal for the new window. - newWindowPrincipal = subjectPrincipal; + openWindowInfo->mPrincipalToInheritForAboutBlank = subjectPrincipal; } } - // Information used when opening new content windows. This object will be - // passed through to the inner nsFrameLoader. - RefPtr<nsOpenWindowInfo> openWindowInfo; if (!targetBC && !windowTypeIsChrome) { - openWindowInfo = new nsOpenWindowInfo(); openWindowInfo->mForceNoOpener = aForceNoOpener; openWindowInfo->mParent = parentBC; openWindowInfo->mIsForPrinting = aPrintKind != PRINT_NONE; @@ -935,21 +935,17 @@ nsresult nsWindowWatcher::OpenWindowInternal( openWindowInfo->mIsRemote = XRE_IsContentProcess(); // Inherit our OriginAttributes from the computed new window principal. - MOZ_ASSERT( - newWindowPrincipal && - !nsContentUtils::IsSystemOrExpandedPrincipal(newWindowPrincipal)); - openWindowInfo->mOriginAttributes = - newWindowPrincipal->OriginAttributesRef(); + MOZ_ASSERT(openWindowInfo->mPrincipalToInheritForAboutBlank && + !nsContentUtils::IsSystemOrExpandedPrincipal( + openWindowInfo->mPrincipalToInheritForAboutBlank)); MOZ_DIAGNOSTIC_ASSERT( - !parentBC || openWindowInfo->mOriginAttributes.EqualsIgnoringFPD( + !parentBC || openWindowInfo->GetOriginAttributes().EqualsIgnoringFPD( parentBC->OriginAttributesRef()), "subject principal origin attributes doesn't match opener"); } uint32_t activeDocsSandboxFlags = 0; - nsCOMPtr<nsIPolicyContainer> policyContainerToInheritForAboutBlank; - Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> coepToInheritForAboutBlank; if (!targetBC) { // We're going to either open up a new window ourselves or ask a // nsIWindowProvider for one. In either case, we'll want to set the right @@ -963,8 +959,10 @@ nsresult nsWindowWatcher::OpenWindowInternal( activeDocsSandboxFlags = parentDoc->GetSandboxFlags(); if (!aForceNoOpener) { - policyContainerToInheritForAboutBlank = parentDoc->GetPolicyContainer(); - coepToInheritForAboutBlank = parentDoc->GetEmbedderPolicy(); + openWindowInfo->mPolicyContainerToInheritForAboutBlank = + parentDoc->GetPolicyContainer(); + openWindowInfo->mCoepToInheritForAboutBlank = + parentDoc->GetEmbedderPolicy(); } // Check to see if this frame is allowed to navigate, but don't check if @@ -1003,7 +1001,6 @@ nsresult nsWindowWatcher::OpenWindowInternal( windowIsNew = false; } } - } else if (rv == NS_ERROR_ABORT) { // NS_ERROR_ABORT means the window provider has flat-out rejected // the open-window call and we should bail. Don't return an error @@ -1090,8 +1087,10 @@ nsresult nsWindowWatcher::OpenWindowInternal( completely honest: we clear that indicator if the opener is chrome, so that the downstream consumer can treat the indicator to mean simply that the new window is subject to popup control. */ - rv = CreateChromeWindow(parentChrome, chromeFlags, openWindowInfo, - getter_AddRefs(newChrome)); + rv = CreateChromeWindow( + parentChrome, chromeFlags, + windowTypeIsChrome ? nullptr : openWindowInfo.get(), + getter_AddRefs(newChrome)); if (parentTopInnerWindow) { parentTopInnerWindow->Resume(); } @@ -1232,8 +1231,8 @@ nsresult nsWindowWatcher::OpenWindowInternal( if (windowIsNew) { MOZ_DIAGNOSTIC_ASSERT( !targetBC->IsContent() || - newWindowPrincipal->OriginAttributesRef().EqualsIgnoringFPD( - targetBC->OriginAttributesRef())); + openWindowInfo->mPrincipalToInheritForAboutBlank->OriginAttributesRef() + .EqualsIgnoringFPD(targetBC->OriginAttributesRef())); bool autoPrivateBrowsing = StaticPrefs::browser_privatebrowsing_autostart(); @@ -1263,16 +1262,28 @@ nsresult nsWindowWatcher::OpenWindowInternal( NS_ASSERTION(targetOuterWin == targetDocShell->GetWindow(), "Different windows??"); - // Initialize the principal of the initial about:blank document. For - // toplevel windows, this call may have already happened when the window was - // created, but SetInitialPrincipal is safe to call multiple times. if (targetOuterWin) { MOZ_ASSERT(windowIsNew); MOZ_ASSERT(!targetOuterWin->GetSameProcessOpener() || targetOuterWin->GetSameProcessOpener() == aParent); - targetOuterWin->SetInitialPrincipal(newWindowPrincipal, - policyContainerToInheritForAboutBlank, - coepToInheritForAboutBlank); + Document* doc = targetBC->GetExtantDocument(); + if (doc) { + // Previously, the principal was set here. Assert that we already have + // the principal that we would have previously set here. + MOZ_ASSERT(doc->GetPrincipal()->Equals( + openWindowInfo->mPrincipalToInheritForAboutBlank) || + (doc->GetPrincipal()->GetIsNullPrincipal() && + openWindowInfo->mPrincipalToInheritForAboutBlank + ->GetIsNullPrincipal()), + "Wrong principal!"); + // Setting the principal would've caused a location change event and + // frontend code depends on that for setting browser.documentURI. + if (nsIURI* uri = doc->GetDocumentURI()) { + targetDocShell->FireOnLocationChange(targetDocShell, nullptr, uri, 0); + } + } else { + MOZ_ASSERT_UNREACHABLE("How come there is no doc?"); + } if (aIsPopupSpam) { MOZ_ASSERT(!targetBC->GetIsPopupSpam(), @@ -1315,19 +1326,31 @@ nsresult nsWindowWatcher::OpenWindowInternal( targetBC->UseRemoteSubframes() == !!(chromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW)); - if (aLoadState) { + // We need to distinguish the case where the no-URL-argument case behaves like + // an explicit about:blank argument. + RefPtr<nsDocShellLoadState> loadState = aLoadState; + nsCOMPtr<nsIURI> uriToLoad = aUri; + if (windowIsNew && !uriToLoad && aCalledFromJS && !loadState) { + NS_NewURI(getter_AddRefs(uriToLoad), "about:blank"_ns); + // Create loadState lazily for about:blank instead of before consuming + // user activation in nsGlobalWindowOuter::OpenInternal. See Bug 1901139 + loadState = CreateLoadState( + uriToLoad, aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr); + } + + if (loadState) { // TriggeringPrincipal and ReferrerInfo are set up here because we // rely on the `jsapiChromeGuard` set above to get proper value. - // Ideally, aLoadState should contain that value when passed in. - if (!aLoadState->TriggeringPrincipal()) { - aLoadState->SetTriggeringPrincipal(subjectPrincipal); + // Ideally, loadState should contain that value when passed in. + if (!loadState->TriggeringPrincipal()) { + loadState->SetTriggeringPrincipal(subjectPrincipal); #ifndef ANDROID MOZ_ASSERT(subjectPrincipal, "nsWindowWatcher: triggeringPrincipal required"); #endif } - if (!aLoadState->GetReferrerInfo() && !aForceNoReferrer) { + if (!loadState->GetReferrerInfo() && !aForceNoReferrer) { /* use the URL from the *extant* document, if any. The usual accessor GetDocument will synchronously create an about:blank document if it has no better answer, and we only care about a real document. @@ -1340,7 +1363,7 @@ nsresult nsWindowWatcher::OpenWindowInternal( } if (doc) { auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc); - aLoadState->SetReferrerInfo(referrerInfo); + loadState->SetReferrerInfo(referrerInfo); } } @@ -1349,7 +1372,7 @@ nsresult nsWindowWatcher::OpenWindowInternal( if (win) { nsCOMPtr<nsIPolicyContainer> policyContainer = win->GetPolicyContainer(); - aLoadState->SetPolicyContainer(policyContainer); + loadState->SetPolicyContainer(policyContainer); } } } @@ -1379,10 +1402,10 @@ nsresult nsWindowWatcher::OpenWindowInternal( if (obsSvc) { RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); - if (aUri) { + if (uriToLoad) { // The url notified in the webNavigation.onCreatedNavigationTarget // event. - props->SetPropertyAsACString(u"url"_ns, aUri->GetSpecOrDefault()); + props->SetPropertyAsACString(u"url"_ns, uriToLoad->GetSpecOrDefault()); } props->SetPropertyAsInterface(u"sourceTabDocShell"_ns, parentDocShell); @@ -1395,7 +1418,7 @@ nsresult nsWindowWatcher::OpenWindowInternal( } } - if (aLoadState) { + if (loadState) { uint32_t loadFlags = nsIWebNavigation::LOAD_FLAGS_NONE; if (windowIsNew) { loadFlags |= nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD; @@ -1412,11 +1435,11 @@ nsresult nsWindowWatcher::OpenWindowInternal( loadFlags |= nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; } } - aLoadState->SetLoadFlags(loadFlags); - aLoadState->SetFirstParty(true); + loadState->SetLoadFlags(loadFlags); + loadState->SetFirstParty(true); // Should this pay attention to errors returned by LoadURI? - targetBC->LoadURI(aLoadState); + targetBC->LoadURI(loadState); } if (windowIsModal) { diff --git a/toolkit/content/tests/browser/browser_bug1572798.js b/toolkit/content/tests/browser/browser_bug1572798.js @@ -1,5 +1,8 @@ add_task(async function test_bug_1572798() { let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); BrowserTestUtils.startLoadingURIString( tab.linkedBrowser, "https://example.com/browser/toolkit/content/tests/browser/file_document_open_audio.html" diff --git a/toolkit/content/tests/browser/browser_cancel_starting_autoscrolling_requested_by_background_tab.js b/toolkit/content/tests/browser/browser_cancel_starting_autoscrolling_requested_by_background_tab.js @@ -30,13 +30,19 @@ add_task(async function testStopStartingAutoScroll() { content.document.documentElement.scrollTop; // Flush layout. const iframe = content.document.querySelector("iframe"); // If the test page has an iframe, we need to ensure it has loaded. - if (!iframe || iframe.contentDocument?.readyState == "complete") { + if ( + !iframe || + (iframe.contentDocument?.readyState == "complete" && + !iframe.contentDocument?.isUncommittedInitialDocument) + ) { return; } // It's too late to check "load" event. Let's check // Document#readyState instead. await ContentTaskUtils.waitForCondition( - () => iframe.contentDocument?.readyState == "complete", + () => + iframe.contentDocument?.readyState == "complete" && + !iframe.contentDocument?.isUncommittedInitialDocument, "Waiting for loading the subdocument" ); }); diff --git a/toolkit/content/tests/widgets/test_videocontrols_standalone.html b/toolkit/content/tests/widgets/test_videocontrols_standalone.html @@ -8,7 +8,7 @@ <script type="text/javascript" src="head.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> - <body> + <body onload="runTest();"> <p id="display"></p> <script class="testbody" type="text/javascript"> @@ -17,36 +17,40 @@ const videoWidth = 320; const videoHeight = 240; + var popup = null; + function getMediaElement(aWindow) { return aWindow.document.getElementsByTagName("video")[0]; } - var popup = window.open("seek_with_sound.webm"); - popup.addEventListener( - "load", - function () { - var video = getMediaElement(popup); - - is( - popup.document.activeElement, - video, - "Document should load with focus moved to the video element." - ); + function runTest() { + popup = window.open("seek_with_sound.webm"); + popup.addEventListener( + "load", + function () { + var video = getMediaElement(popup); - if (!video.paused) { - runTestVideo(video); - } else { - video.addEventListener( - "play", - function () { - runTestVideo(video); - }, - { once: true } + is( + popup.document.activeElement, + video, + "Document should load with focus moved to the video element." ); - } - }, - { once: true } - ); + + if (!video.paused) { + runTestVideo(video); + } else { + video.addEventListener( + "play", + function () { + runTestVideo(video); + }, + { once: true } + ); + } + }, + { once: true } + ); + } function runTestVideo(aVideo) { var condition = function () { diff --git a/toolkit/modules/HiddenFrame.sys.mjs b/toolkit/modules/HiddenFrame.sys.mjs @@ -127,9 +127,7 @@ export class HiddenFrame { this.#listener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT ); - let docShell = this.#browser.docShell; let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); - docShell.createAboutBlankDocumentViewer(systemPrincipal, systemPrincipal); let browsingContext = this.#browser.browsingContext; browsingContext.useGlobalHistory = false; let loadURIOptions = { diff --git a/toolkit/modules/SubDialog.sys.mjs b/toolkit/modules/SubDialog.sys.mjs @@ -861,7 +861,9 @@ SubDialog.prototype = { if (includeLoad) { this._window.removeEventListener("DOMFrameContentLoaded", this, true); this._frame.removeEventListener("load", this, true); - this._frame.contentWindow.removeEventListener("dialogclosing", this); + if (this._frame.contentWindow) { + this._frame.contentWindow.removeEventListener("dialogclosing", this); + } } this._window.removeEventListener("keydown", this, true); diff --git a/tools/profiler/tests/browser/browser_test_feature_js_sourceindex.js b/tools/profiler/tests/browser/browser_test_feature_js_sourceindex.js @@ -109,6 +109,11 @@ add_task(async function test_profile_js_sources_with_tracing() { "The profiler is not currently active" ); + // sync about:blank (bug 543435) somehow causes a + // CycleCollectedJSContext::EndExecutionTracingAsync runnable to leak. See bug 2000283 + await TestUtils.waitForTick(); + await TestUtils.waitForTick(); + const url = BASE_URL + "tracing.html"; await BrowserTestUtils.withNewTab("about:blank", async contentBrowser => { // Start profiling with tracing to capture JS tracer frames diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h @@ -136,6 +136,9 @@ class nsDocLoader : public nsIDocumentLoader, nsresult aStatus, const nsAString& aHost, nsAString& aRetVal, mozilla::StaticRefPtr<mozilla::intl::Localization>& aL10n); + void FireOnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsIURI* aUri, uint32_t aFlags); + protected: explicit nsDocLoader(bool aNotifyAboutBackgroundRequests); virtual ~nsDocLoader(); @@ -181,9 +184,6 @@ class nsDocLoader : public nsIDocumentLoader, void FireOnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage); - void FireOnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, - nsIURI* aUri, uint32_t aFlags); - [[nodiscard]] bool RefreshAttempted(nsIWebProgress* aWebProgress, nsIURI* aURI, uint32_t aDelay, bool aSameURI); diff --git a/widget/nsIBaseWindow.idl b/widget/nsIBaseWindow.idl @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +#include "nsIPrincipal.idl" #include "nsrootidl.idl" [ptr] native nsIWidget(nsIWidget); @@ -29,36 +30,6 @@ native DimensionKind(mozilla::DimensionKind); interface nsIBaseWindow : nsISupports { /* - Allows a client to initialize an object implementing this interface with - the usually required window setup information. - It is possible to pass null for parentWidget, but only docshells support - this. - - @param parentWidget - This allows a system to pass in the parenting widget. - This allows some objects to optimize themselves and rely on the view - system for event flow rather than creating numerous native windows. If - one of these is not available, nullptr should be passed. - - @param x - This is the x co-ordinate relative to the parent to place the - window. - - @param y - This is the y co-ordinate relative to the parent to place the - window. - - @param cx - This is the width for the window to be. - - @param cy - This is the height for the window to be. - - @return NS_OK - Window Init succeeded without a problem. - NS_ERROR_UNEXPECTED - Call was unexpected at this time. Perhaps - initWindow() had already been called. - NS_ERROR_INVALID_ARG - controls that require a parentWidget may return - invalid arg when they do not receive what they are needing. - */ - [noscript]void initWindow( - in nsIWidget parentWidget, in long x, in long y, in long cx, in long cy); - - /* Tell the window that it should destroy itself. This call should not be necessary as it will happen implictly when final release occurs on the object. If for some reaons you want the window destroyed prior to release diff --git a/widget/tests/test_alwaysontop_focus.xhtml b/widget/tests/test_alwaysontop_focus.xhtml @@ -19,7 +19,7 @@ null ); await new Promise(resolve => { - newWin.addEventListener("load", resolve, { once: true }); + setTimeout(resolve, 0); }); // Wait one tick of the event loop to give the window a chance to focus. diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp @@ -161,7 +161,8 @@ NS_INTERFACE_MAP_END nsresult AppWindow::Initialize(nsIAppWindow* aParent, nsIAppWindow* aOpener, int32_t aInitialWidth, int32_t aInitialHeight, bool aIsHiddenWindow, - widget::InitData& widgetInitData) { + widget::InitData& widgetInitData, + nsIOpenWindowInfo* aOpenWindowInfo) { nsresult rv; nsCOMPtr<nsIWidget> parentWidget; @@ -238,9 +239,9 @@ nsresult AppWindow::Initialize(nsIAppWindow* aParent, nsIAppWindow* aOpener, mDocShell->SetTreeOwner(mChromeTreeOwner); r.MoveTo(0, 0); - NS_ENSURE_SUCCESS( - mDocShell->InitWindow(mWindow, r.X(), r.Y(), r.Width(), r.Height()), - NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(mDocShell->InitWindow(mWindow, r.X(), r.Y(), r.Width(), + r.Height(), aOpenWindowInfo, nullptr), + NS_ERROR_FAILURE); // Attach a WebProgress listener.during initialization... mDocShell->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_NETWORK); @@ -500,13 +501,6 @@ NS_IMETHODIMP AppWindow::RollupAllPopups() { // AppWindow::nsIBaseWindow //***************************************************************************** -NS_IMETHODIMP AppWindow::InitWindow(nsIWidget* parentWidget, int32_t x, - int32_t y, int32_t cx, int32_t cy) { - // XXX First Check In - NS_ASSERTION(false, "Not Yet Implemented"); - return NS_OK; -} - NS_IMETHODIMP AppWindow::Destroy() { nsCOMPtr<nsIAppWindow> kungFuDeathGrip(this); diff --git a/xpfe/appshell/AppWindow.h b/xpfe/appshell/AppWindow.h @@ -40,6 +40,7 @@ class nsAtom; class nsXULTooltipListener; +class nsIOpenWindowInfo; namespace mozilla { class PresShell; @@ -134,7 +135,8 @@ class AppWindow final : public nsIBaseWindow, // AppWindow methods... nsresult Initialize(nsIAppWindow* aParent, nsIAppWindow* aOpener, int32_t aInitialWidth, int32_t aInitialHeight, - bool aIsHiddenWindow, widget::InitData& widgetInitData); + bool aIsHiddenWindow, widget::InitData& widgetInitData, + nsIOpenWindowInfo* aOpenWindowInfo); nsDocShell* GetDocShell() { return mDocShell; } diff --git a/xpfe/appshell/nsAppShellService.cpp b/xpfe/appshell/nsAppShellService.cpp @@ -13,6 +13,7 @@ #include "nsPIWindowWatcher.h" #include "nsPIDOMWindow.h" #include "AppWindow.h" +#include "nsOpenWindowInfo.h" #include "mozilla/widget/InitData.h" #include "nsWidgetsCID.h" @@ -27,6 +28,7 @@ #include "nsIWebNavigation.h" #include "nsIWindowlessBrowser.h" +#include "mozilla/NullPrincipal.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StartupTimeline.h" @@ -424,11 +426,21 @@ nsAppShellService::CreateWindowlessBrowser(bool aIsChrome, uint32_t aChromeMask, browsingContext->SetPrivateBrowsing(true); } + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + if (aIsChrome) { + openWindowInfo->mPrincipalToInheritForAboutBlank = + nsContentUtils::GetSystemPrincipal(); + } else { + openWindowInfo->mPrincipalToInheritForAboutBlank = + NullPrincipal::CreateWithoutOriginAttributes(); + } + /* Next, we create an instance of nsWebBrowser. Instances of this class have * an associated doc shell, which is what we're interested in. */ - nsCOMPtr<nsIWebBrowser> browser = nsWebBrowser::Create( - stub, widget, browsingContext, nullptr /* initialWindowChild */); + nsCOMPtr<nsIWebBrowser> browser = + nsWebBrowser::Create(stub, widget, browsingContext, + nullptr /* initialWindowChild */, openWindowInfo); if (NS_WARN_IF(!browser)) { NS_ERROR("Couldn't create instance of nsWebBrowser!"); @@ -585,9 +597,30 @@ nsresult nsAppShellService::JustCreateTopWindow( } widgetInitData.mIsPrivate = isPrivateBrowsingWindow; - nsresult rv = - window->Initialize(parent, center ? aParent : nullptr, aInitialWidth, - aInitialHeight, aIsHiddenWindow, widgetInitData); + RefPtr<nsOpenWindowInfo> openWindowInfo = new nsOpenWindowInfo(); + // Eagerly create an about:blank content viewer with the right principal + // here, rather than letting it happen in the upcoming call to + // SetInitialPrincipal. This avoids creating the about:blank document and + // then blowing it away with a second one, which can cause problems for the + // top-level chrome window case. See bug 789773. + // Toplevel chrome windows always have a system principal, so ensure the + // initial window is created with that principal. + // We need to do this even when creating a chrome window to load a content + // window, see bug 799348 comment 13 for details about what previously + // happened here due to it using the subject principal. + if (nsContentUtils::IsInitialized()) { // Sometimes this happens really + // early. See bug 793370. + MOZ_DIAGNOSTIC_ASSERT( + nsContentUtils::LegacyIsCallerChromeOrNativeCode(), + "Previously, this method would use the subject principal rather than " + "hardcoding the system principal"); + openWindowInfo->mPrincipalToInheritForAboutBlank = + nsContentUtils::GetSystemPrincipal(); + } + + nsresult rv = window->Initialize( + parent, center ? aParent : nullptr, aInitialWidth, aInitialHeight, + aIsHiddenWindow, widgetInitData, openWindowInfo); NS_ENSURE_SUCCESS(rv, rv); @@ -611,36 +644,6 @@ nsresult nsAppShellService::JustCreateTopWindow( docShell->SetRemoteSubframes(aChromeMask & nsIWebBrowserChrome::CHROME_FISSION_WINDOW); - // Eagerly create an about:blank content viewer with the right principal - // here, rather than letting it happen in the upcoming call to - // SetInitialPrincipal. This avoids creating the about:blank document and - // then blowing it away with a second one, which can cause problems for the - // top-level chrome window case. See bug 789773. - // Toplevel chrome windows always have a system principal, so ensure the - // initial window is created with that principal. - // We need to do this even when creating a chrome window to load a content - // window, see bug 799348 comment 13 for details about what previously - // happened here due to it using the subject principal. - if (nsContentUtils::IsInitialized()) { // Sometimes this happens really - // early. See bug 793370. - MOZ_DIAGNOSTIC_ASSERT( - nsContentUtils::LegacyIsCallerChromeOrNativeCode(), - "Previously, this method would use the subject principal rather than " - "hardcoding the system principal"); - // Use the system principal as the storage principal too until the new - // window finishes navigating and gets a real storage principal. - rv = docShell->CreateAboutBlankDocumentViewer( - nsContentUtils::GetSystemPrincipal(), - nsContentUtils::GetSystemPrincipal(), - /* aPolicyContainer = */ nullptr, /* aBaseURI = */ nullptr, - /* aIsInitialDocument = */ true); - NS_ENSURE_SUCCESS(rv, rv); - RefPtr<dom::Document> doc = docShell->GetDocument(); - NS_ENSURE_TRUE(!!doc, NS_ERROR_FAILURE); - MOZ_ASSERT(doc->IsInitialDocument(), - "Document should be an initial document"); - } - // Begin loading the URL provided. if (aUrl) { RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aUrl); @@ -715,8 +718,15 @@ nsAppShellService::RegisterTopLevelWindow(nsIAppWindow* aWindow) { nsContentUtils::LegacyIsCallerChromeOrNativeCode(), "Previously, this method would use the subject principal rather than " "hardcoding the system principal"); - domWindow->SetInitialPrincipal(nsContentUtils::GetSystemPrincipal(), nullptr, - Nothing()); +#ifdef DEBUG + mozilla::dom::Document* doc = domWindow->GetDoc(); + if (doc) { + MOZ_ASSERT(doc->GetPrincipal() == nsContentUtils::GetSystemPrincipal(), + "Wrong principal!"); + } else { + MOZ_ASSERT(false, "How come there was no doc?"); + } +#endif // tell the window mediator about the new window nsCOMPtr<nsIWindowMediator> mediator( diff --git a/xpfe/appshell/nsChromeTreeOwner.cpp b/xpfe/appshell/nsChromeTreeOwner.cpp @@ -233,14 +233,6 @@ nsChromeTreeOwner::GetHasPrimaryContent(bool* aResult) { // nsChromeTreeOwner::nsIBaseWindow //***************************************************************************** -NS_IMETHODIMP nsChromeTreeOwner::InitWindow(nsIWidget* parentWidget, int32_t x, - int32_t y, int32_t cx, int32_t cy) { - // Ignore widget parents for now. Don't think those are a valid thing to - // call. - NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE); - return NS_OK; -} - NS_IMETHODIMP nsChromeTreeOwner::Destroy() { NS_ENSURE_STATE(mAppWindow); return mAppWindow->Destroy(); diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp @@ -334,16 +334,6 @@ NS_IMETHODIMP nsContentTreeOwner::IsWindowModal(bool* _retval) { // nsContentTreeOwner::nsIBaseWindow //***************************************************************************** -NS_IMETHODIMP nsContentTreeOwner::InitWindow(nsIWidget* parentWidget, int32_t x, - int32_t y, int32_t cx, - int32_t cy) { - // Ignore wigdet parents for now. Don't think those are a vaild thing to - // call. - NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE); - - return NS_OK; -} - NS_IMETHODIMP nsContentTreeOwner::Destroy() { NS_ENSURE_STATE(mAppWindow); return mAppWindow->Destroy();