diff options
Diffstat (limited to 'chromium/content/browser/frame_host')
58 files changed, 4975 insertions, 3379 deletions
diff --git a/chromium/content/browser/frame_host/DEPS b/chromium/content/browser/frame_host/DEPS index 59ac961d695..6ce53ea9e8a 100644 --- a/chromium/content/browser/frame_host/DEPS +++ b/chromium/content/browser/frame_host/DEPS @@ -5,6 +5,7 @@ include_rules = [ "-content/public/browser/web_contents.h", "-content/public/browser/web_contents_delegate.h", "-content/public/browser/web_contents_view.h", + "-content/public/browser/web_contents_observer.h", ] specific_include_rules = { @@ -12,17 +13,7 @@ specific_include_rules = { "+content/browser/web_contents", "+content/public/browser/web_contents.h", "+content/public/browser/web_contents_delegate.h", - ], - ".*file_chooser_impl\.cc": [ - # TODO(1076947): There is a layer violation to fix in this file. - "+content/public/browser/web_contents.h", - ], - ".*interstitial_page_impl\.cc": [ - # TODO(nasko): This should be removed once we remove - # WebContentsObserver as the method of telling interstitial pages to - # clean themselves up. - "+content/browser/web_contents", - "+content/public/browser/web_contents_delegate.h", + "+content/public/browser/web_contents_observer.h", ], "popup_menu_helper_mac.mm": [ "+content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h", diff --git a/chromium/content/browser/frame_host/ancestor_throttle.cc b/chromium/content/browser/frame_host/ancestor_throttle.cc index 4542a24bbd3..8630ace7189 100644 --- a/chromium/content/browser/frame_host/ancestor_throttle.cc +++ b/chromium/content/browser/frame_host/ancestor_throttle.cc @@ -199,69 +199,8 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( return NavigationThrottle::PROCEED; } - std::string header_value; - HeaderDisposition disposition = - ParseHeader(request->GetResponseHeaders(), &header_value); - - switch (disposition) { - case HeaderDisposition::CONFLICT: - if (logging == LoggingDisposition::LOG_TO_CONSOLE) - ParseError(header_value, disposition); - RecordXFrameOptionsUsage(XFrameOptionsHistogram::CONFLICT); - return NavigationThrottle::BLOCK_RESPONSE; - - case HeaderDisposition::INVALID: - if (logging == LoggingDisposition::LOG_TO_CONSOLE) - ParseError(header_value, disposition); - RecordXFrameOptionsUsage(XFrameOptionsHistogram::INVALID); - // TODO(mkwst): Consider failing here. - break; - - case HeaderDisposition::DENY: - if (logging == LoggingDisposition::LOG_TO_CONSOLE) - ConsoleError(disposition); - RecordXFrameOptionsUsage(XFrameOptionsHistogram::DENY); - return NavigationThrottle::BLOCK_RESPONSE; - - case HeaderDisposition::SAMEORIGIN: { - // Block the request when any ancestor is not same-origin. - RenderFrameHostImpl* parent = request->GetParentFrame(); - url::Origin current_origin = - url::Origin::Create(navigation_handle()->GetURL()); - while (parent) { - if (!parent->GetLastCommittedOrigin().IsSameOriginWith( - current_origin)) { - RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN_BLOCKED); - if (logging == LoggingDisposition::LOG_TO_CONSOLE) - ConsoleError(disposition); - - // TODO(mkwst): Stop recording this metric once we convince other - // vendors to follow our lead with XFO: SAMEORIGIN processing. - // - // https://crbug.com/250309 - if (parent->GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith( - current_origin)) { - RecordXFrameOptionsUsage( - XFrameOptionsHistogram::SAMEORIGIN_WITH_BAD_ANCESTOR_CHAIN); - } - - return NavigationThrottle::BLOCK_RESPONSE; - } - parent = parent->GetParent(); - } - RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN); - break; - } - - case HeaderDisposition::NONE: - RecordXFrameOptionsUsage(XFrameOptionsHistogram::NONE); - break; - case HeaderDisposition::BYPASS: - RecordXFrameOptionsUsage(XFrameOptionsHistogram::BYPASS); - break; - case HeaderDisposition::ALLOWALL: - RecordXFrameOptionsUsage(XFrameOptionsHistogram::ALLOWALL); - break; + if (EvaluateXFrameOptions(logging) == CheckResult::BLOCK) { + return NavigationThrottle::BLOCK_RESPONSE; } // X-Frame-Option is checked on both redirect and final responses. However, @@ -269,8 +208,15 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( if (!is_response_check) return NavigationThrottle::PROCEED; - return EvaluateContentSecurityPolicy( - request->response()->parsed_headers->content_security_policy); + const std::vector<network::mojom::ContentSecurityPolicyPtr>& + content_security_policies = + request->response()->parsed_headers->content_security_policy; + + if (EvaluateFrameAncestors(content_security_policies) == CheckResult::BLOCK) { + return NavigationThrottle::BLOCK_RESPONSE; + } + + return NavigationThrottle::PROCEED; } const char* AncestorThrottle::GetNameForLogging() { @@ -280,8 +226,8 @@ const char* AncestorThrottle::GetNameForLogging() { AncestorThrottle::AncestorThrottle(NavigationHandle* handle) : NavigationThrottle(handle) {} -void AncestorThrottle::ParseError(const std::string& value, - HeaderDisposition disposition) { +void AncestorThrottle::ParseXFrameOptionsError(const std::string& value, + HeaderDisposition disposition) { DCHECK(disposition == HeaderDisposition::CONFLICT || disposition == HeaderDisposition::INVALID); if (!navigation_handle()->GetRenderFrameHost()) @@ -309,7 +255,8 @@ void AncestorThrottle::ParseError(const std::string& value, blink::mojom::ConsoleMessageLevel::kError, message); } -void AncestorThrottle::ConsoleError(HeaderDisposition disposition) { +void AncestorThrottle::ConsoleErrorXFrameOptions( + HeaderDisposition disposition) { DCHECK(disposition == HeaderDisposition::DENY || disposition == HeaderDisposition::SAMEORIGIN); if (!navigation_handle()->GetRenderFrameHost()) @@ -329,8 +276,77 @@ void AncestorThrottle::ConsoleError(HeaderDisposition disposition) { blink::mojom::ConsoleMessageLevel::kError, message); } -NavigationThrottle::ThrottleAction -AncestorThrottle::EvaluateContentSecurityPolicy( +AncestorThrottle::CheckResult AncestorThrottle::EvaluateXFrameOptions( + LoggingDisposition logging) { + std::string header_value; + NavigationRequest* request = NavigationRequest::From(navigation_handle()); + HeaderDisposition disposition = + ParseXFrameOptionsHeader(request->GetResponseHeaders(), &header_value); + + switch (disposition) { + case HeaderDisposition::CONFLICT: + if (logging == LoggingDisposition::LOG_TO_CONSOLE) + ParseXFrameOptionsError(header_value, disposition); + RecordXFrameOptionsUsage(XFrameOptionsHistogram::CONFLICT); + return CheckResult::BLOCK; + + case HeaderDisposition::INVALID: + if (logging == LoggingDisposition::LOG_TO_CONSOLE) + ParseXFrameOptionsError(header_value, disposition); + RecordXFrameOptionsUsage(XFrameOptionsHistogram::INVALID); + // TODO(mkwst): Consider failing here. + return CheckResult::PROCEED; + + case HeaderDisposition::DENY: + if (logging == LoggingDisposition::LOG_TO_CONSOLE) + ConsoleErrorXFrameOptions(disposition); + RecordXFrameOptionsUsage(XFrameOptionsHistogram::DENY); + return CheckResult::BLOCK; + + case HeaderDisposition::SAMEORIGIN: { + // Block the request when any ancestor is not same-origin. + RenderFrameHostImpl* parent = ParentForAncestorThrottle( + request->frame_tree_node()->current_frame_host()); + url::Origin current_origin = + url::Origin::Create(navigation_handle()->GetURL()); + while (parent) { + if (!parent->GetLastCommittedOrigin().IsSameOriginWith( + current_origin)) { + RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN_BLOCKED); + if (logging == LoggingDisposition::LOG_TO_CONSOLE) + ConsoleErrorXFrameOptions(disposition); + + // TODO(mkwst): Stop recording this metric once we convince other + // vendors to follow our lead with XFO: SAMEORIGIN processing. + // + // https://crbug.com/250309 + if (parent->GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith( + current_origin)) { + RecordXFrameOptionsUsage( + XFrameOptionsHistogram::SAMEORIGIN_WITH_BAD_ANCESTOR_CHAIN); + } + + return CheckResult::BLOCK; + } + parent = ParentForAncestorThrottle(parent); + } + RecordXFrameOptionsUsage(XFrameOptionsHistogram::SAMEORIGIN); + return CheckResult::PROCEED; + } + + case HeaderDisposition::NONE: + RecordXFrameOptionsUsage(XFrameOptionsHistogram::NONE); + return CheckResult::PROCEED; + case HeaderDisposition::BYPASS: + RecordXFrameOptionsUsage(XFrameOptionsHistogram::BYPASS); + return CheckResult::PROCEED; + case HeaderDisposition::ALLOWALL: + RecordXFrameOptionsUsage(XFrameOptionsHistogram::ALLOWALL); + return CheckResult::PROCEED; + } +} + +AncestorThrottle::CheckResult AncestorThrottle::EvaluateFrameAncestors( const std::vector<network::mojom::ContentSecurityPolicyPtr>& content_security_policy) { // TODO(lfg): If the initiating document is known and correspond to the @@ -359,15 +375,15 @@ AncestorThrottle::EvaluateContentSecurityPolicy( true /* is_response_check */, empty_source_location, network::CSPContext::CheckCSPDisposition::CHECK_ALL_CSP, navigation_handle()->IsFormSubmission())) { - return NavigationThrottle::BLOCK_RESPONSE; + return CheckResult::BLOCK; } parent = ParentForAncestorThrottle(parent); } - return NavigationThrottle::PROCEED; + return CheckResult::PROCEED; } -AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader( +AncestorThrottle::HeaderDisposition AncestorThrottle::ParseXFrameOptionsHeader( const net::HttpResponseHeaders* headers, std::string* header_value) { DCHECK(header_value); @@ -411,12 +427,13 @@ AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader( // https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options if (result != HeaderDisposition::NONE && result != HeaderDisposition::ALLOWALL && + // TODO(antoniosartori): Use the already parsed CSP header instead of the + // raw headers here as soon as we remove + // network::features::kOutOfBlinkFrameAncestors HeadersContainFrameAncestorsCSP(headers, false)) { - // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should - // handle it here instead. Until then, don't block the request, and let - // Blink handle it. https://crbug.com/555418 return HeaderDisposition::BYPASS; } + return result; } diff --git a/chromium/content/browser/frame_host/ancestor_throttle.h b/chromium/content/browser/frame_host/ancestor_throttle.h index 2fdd685f822..a12995a2263 100644 --- a/chromium/content/browser/frame_host/ancestor_throttle.h +++ b/chromium/content/browser/frame_host/ancestor_throttle.h @@ -45,6 +45,7 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle { private: enum class LoggingDisposition { LOG_TO_CONSOLE, DO_NOT_LOG_TO_CONSOLE }; + enum class CheckResult { BLOCK, PROCEED }; FRIEND_TEST_ALL_PREFIXES(AncestorThrottleTest, ParsingXFrameOptions); FRIEND_TEST_ALL_PREFIXES(AncestorThrottleTest, ErrorsParsingXFrameOptions); @@ -55,17 +56,20 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle { NavigationThrottle::ThrottleCheckResult ProcessResponseImpl( LoggingDisposition logging, bool is_response_check); - void ParseError(const std::string& value, HeaderDisposition disposition); - void ConsoleError(HeaderDisposition disposition); - NavigationThrottle::ThrottleAction EvaluateContentSecurityPolicy( + void ParseXFrameOptionsError(const std::string& value, + HeaderDisposition disposition); + void ConsoleErrorXFrameOptions(HeaderDisposition disposition); + CheckResult EvaluateXFrameOptions(LoggingDisposition logging); + CheckResult EvaluateFrameAncestors( const std::vector<network::mojom::ContentSecurityPolicyPtr>& content_security_policy); // Parses an 'X-Frame-Options' header. If the result is either CONFLICT // or INVALID, |header_value| will be populated with the value which caused // the parse error. - HeaderDisposition ParseHeader(const net::HttpResponseHeaders* headers, - std::string* header_value); + HeaderDisposition ParseXFrameOptionsHeader( + const net::HttpResponseHeaders* headers, + std::string* header_value); DISALLOW_COPY_AND_ASSIGN(AncestorThrottle); }; diff --git a/chromium/content/browser/frame_host/ancestor_throttle_browsertest.cc b/chromium/content/browser/frame_host/ancestor_throttle_browsertest.cc index 1cfaabc82c7..aa917ef6fb9 100644 --- a/chromium/content/browser/frame_host/ancestor_throttle_browsertest.cc +++ b/chromium/content/browser/frame_host/ancestor_throttle_browsertest.cc @@ -31,12 +31,7 @@ namespace { class AncestorThrottleTest : public ContentBrowserTest, public ::testing::WithParamInterface<bool> { public: - AncestorThrottleTest() { - if (GetParam()) { - feature_list_.InitAndEnableFeature( - network::features::kOutOfBlinkFrameAncestors); - } - } + AncestorThrottleTest() = default; protected: void SetUpOnMainThread() override { @@ -49,7 +44,7 @@ class AncestorThrottleTest : public ContentBrowserTest, }; // Tests that an iframe navigation with frame-anecestors 'none' is blocked. -IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, FailedCSP) { +IN_PROC_BROWSER_TEST_F(AncestorThrottleTest, FailedCSP) { GURL parent_url( embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); GURL iframe_url( @@ -73,7 +68,7 @@ IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, FailedCSP) { // Tests that redirecting on a forbidden frame-ancestors will still commit if // the final response does not have a CSP policy that prevents the navigation. -IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, RedirectCommitsIfNoCSP) { +IN_PROC_BROWSER_TEST_F(AncestorThrottleTest, RedirectCommitsIfNoCSP) { GURL parent_url( embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); GURL iframe_url( @@ -98,7 +93,7 @@ IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, RedirectCommitsIfNoCSP) { // Tests that redirecting on a forbidden frame-ancestors will not commit if // the final response does have a CSP policy that prevents the navigation. -IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, RedirectFails) { +IN_PROC_BROWSER_TEST_F(AncestorThrottleTest, RedirectFails) { GURL parent_url( embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); GURL iframe_url(embedded_test_server()->GetURL( @@ -121,7 +116,7 @@ IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, RedirectFails) { } // Check that we don't process CSP for 204 responses. -IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, Response204CSP) { +IN_PROC_BROWSER_TEST_F(AncestorThrottleTest, Response204CSP) { GURL parent_url( embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); GURL iframe_url(embedded_test_server()->GetURL( @@ -170,7 +165,7 @@ class AncestorThrottleSXGTest : public AncestorThrottleTest { SignedExchangeBrowserTestHelper sxg_test_helper_; }; -IN_PROC_BROWSER_TEST_P(AncestorThrottleSXGTest, SXGWithCSP) { +IN_PROC_BROWSER_TEST_F(AncestorThrottleSXGTest, SXGWithCSP) { sxg_test_helper_.InstallMockCert(mock_cert_verifier_.mock_cert_verifier()); sxg_test_helper_.InstallMockCertChainInterceptor(); @@ -188,9 +183,6 @@ IN_PROC_BROWSER_TEST_P(AncestorThrottleSXGTest, SXGWithCSP) { iframe_node->current_frame_host()->GetLastCommittedOrigin().opaque()); } -INSTANTIATE_TEST_SUITE_P(All, AncestorThrottleTest, ::testing::Bool()); -INSTANTIATE_TEST_SUITE_P(All, AncestorThrottleSXGTest, ::testing::Bool()); - } // namespace } // namespace content diff --git a/chromium/content/browser/frame_host/ancestor_throttle_unittest.cc b/chromium/content/browser/frame_host/ancestor_throttle_unittest.cc index 605d8a4e207..0acf57b221f 100644 --- a/chromium/content/browser/frame_host/ancestor_throttle_unittest.cc +++ b/chromium/content/browser/frame_host/ancestor_throttle_unittest.cc @@ -89,7 +89,7 @@ TEST_F(AncestorThrottleTest, ParsingXFrameOptions) { GetAncestorHeaders(test.header, nullptr); std::string header_value; EXPECT_EQ(test.expected, - throttle.ParseHeader(headers.get(), &header_value)); + throttle.ParseXFrameOptionsHeader(headers.get(), &header_value)); EXPECT_EQ(test.value, header_value); } } @@ -124,69 +124,9 @@ TEST_F(AncestorThrottleTest, ErrorsParsingXFrameOptions) { GetAncestorHeaders(test.header, nullptr); std::string header_value; EXPECT_EQ(test.expected, - throttle.ParseHeader(headers.get(), &header_value)); + throttle.ParseXFrameOptionsHeader(headers.get(), &header_value)); EXPECT_EQ(test.failure, header_value); } } -TEST_F(AncestorThrottleTest, IgnoreWhenFrameAncestorsPresent) { - // When OutOfBlinkFrameAncestors is enabled frame-ancestors is processed in - // the AncestorThrottle and XFO will not be parsed. - if (base::FeatureList::IsEnabled( - network::features::kOutOfBlinkFrameAncestors)) { - return; - } - - struct TestCase { - const char* csp; - AncestorThrottle::HeaderDisposition expected; - } cases[] = { - {"", HeaderDisposition::DENY}, - {"frame-ancestors 'none'", HeaderDisposition::BYPASS}, - {"frame-ancestors *", HeaderDisposition::BYPASS}, - {"frame-ancestors 'self'", HeaderDisposition::BYPASS}, - {"frame-ancestors https://example.com", HeaderDisposition::BYPASS}, - {"fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, - {"directive1; frame-ancestors 'none'", HeaderDisposition::BYPASS}, - {"directive1; frame-ancestors *", HeaderDisposition::BYPASS}, - {"directive1; frame-ancestors 'self'", HeaderDisposition::BYPASS}, - {"directive1; frame-ancestors https://example.com", - HeaderDisposition::BYPASS}, - {"directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, - {"policy, frame-ancestors 'none'", HeaderDisposition::BYPASS}, - {"policy, frame-ancestors *", HeaderDisposition::BYPASS}, - {"policy, frame-ancestors 'self'", HeaderDisposition::BYPASS}, - {"policy, frame-ancestors https://example.com", - HeaderDisposition::BYPASS}, - {"policy, frame-ancestors 'none'", HeaderDisposition::BYPASS}, - {"policy, directive1; frame-ancestors *", HeaderDisposition::BYPASS}, - {"policy, directive1; frame-ancestors 'self'", HeaderDisposition::BYPASS}, - {"policy, directive1; frame-ancestors https://example.com", - HeaderDisposition::BYPASS}, - {"policy, directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, - {"policy, directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, - - {"not-frame-ancestors *", HeaderDisposition::DENY}, - {"frame-ancestors-are-lovely", HeaderDisposition::DENY}, - {"directive1; not-frame-ancestors *", HeaderDisposition::DENY}, - {"directive1; frame-ancestors-are-lovely", HeaderDisposition::DENY}, - {"policy, not-frame-ancestors *", HeaderDisposition::DENY}, - {"policy, frame-ancestors-are-lovely", HeaderDisposition::DENY}, - {"policy, directive1; not-frame-ancestors *", HeaderDisposition::DENY}, - {"policy, directive1; frame-ancestors-are-lovely", - HeaderDisposition::DENY}, - }; - - AncestorThrottle throttle(nullptr); - for (const auto& test : cases) { - SCOPED_TRACE(test.csp); - scoped_refptr<net::HttpResponseHeaders> headers = - GetAncestorHeaders("DENY", test.csp); - std::string header_value; - EXPECT_EQ(test.expected, - throttle.ParseHeader(headers.get(), &header_value)); - EXPECT_EQ("DENY", header_value); - } -} - } // namespace content diff --git a/chromium/content/browser/frame_host/back_forward_cache_can_store_document_result.cc b/chromium/content/browser/frame_host/back_forward_cache_can_store_document_result.cc index 9775b3c38d4..32c21da74e6 100644 --- a/chromium/content/browser/frame_host/back_forward_cache_can_store_document_result.cc +++ b/chromium/content/browser/frame_host/back_forward_cache_can_store_document_result.cc @@ -118,6 +118,13 @@ std::string BackForwardCacheCanStoreDocumentResult::NotRestoredReasonToString( return "navigation entry is not the most recent one for this document"; case Reason::kServiceWorkerClaim: return "service worker claim is called"; + case Reason::kIgnoreEventAndEvict: + return "IsInactiveAndDisallowReactivation() was called for the frame in " + "bfcache"; + case Reason::kHaveInnerContents: + return "RenderFrameHost has inner WebContents attached"; + case Reason::kTimeoutPuttingInCache: + return "Timed out while waiting for page to acknowledge freezing"; } } diff --git a/chromium/content/browser/frame_host/back_forward_cache_impl.cc b/chromium/content/browser/frame_host/back_forward_cache_impl.cc index 1120a52463d..aa0d3ab525c 100644 --- a/chromium/content/browser/frame_host/back_forward_cache_impl.cc +++ b/chromium/content/browser/frame_host/back_forward_cache_impl.cc @@ -17,6 +17,7 @@ #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/common/content_navigation_policy.h" #include "content/common/page_messages.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/visibility.h" #include "net/http/http_request_headers.h" #include "net/http/http_status_code.h" @@ -152,7 +153,13 @@ uint64_t GetDisallowedFeatures(RenderFrameHostImpl* rfh) { FeatureToBit(WebSchedulerTrackedFeature::kAppBanner) | FeatureToBit(WebSchedulerTrackedFeature::kPrinting) | FeatureToBit(WebSchedulerTrackedFeature::kWebDatabase) | - FeatureToBit(WebSchedulerTrackedFeature::kPictureInPicture); + FeatureToBit(WebSchedulerTrackedFeature::kPictureInPicture) | + FeatureToBit(WebSchedulerTrackedFeature::kPortal) | + FeatureToBit(WebSchedulerTrackedFeature::kSpeechRecognizer) | + FeatureToBit(WebSchedulerTrackedFeature::kIdleManager) | + FeatureToBit(WebSchedulerTrackedFeature::kPaymentManager) | + FeatureToBit(WebSchedulerTrackedFeature::kSpeechSynthesis) | + FeatureToBit(WebSchedulerTrackedFeature::kKeyboardLock); uint64_t result = kAlwaysDisallowedFeatures; @@ -248,7 +255,6 @@ void RequestRecordTimeToVisible(RenderFrameHostImpl* rfh, if (rfh->delegate()->GetVisibility() != Visibility::HIDDEN) { rfh->GetView()->SetRecordContentToVisibleTimeRequest( navigation_start, base::Optional<bool>() /* destination_is_loaded */, - base::Optional<bool>() /* destination_is_frozen */, false /* show_reason_tab_switching */, false /* show_reason_unoccluded */, true /* show_reason_bfcache_restore */); @@ -333,8 +339,8 @@ BackForwardCacheCanStoreDocumentResult BackForwardCacheImpl::CanStoreDocument( if (rfh->last_http_method() != net::HttpRequestHeaders::kGetMethod) result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPMethodNotGET); - // Do not store main document with non HTTP/HTTPS URL scheme. In particular, - // this excludes the new tab page. + // Do not store main document with non HTTP/HTTPS URL scheme. Among other + // things, this excludes the new tab page and all WebUI pages. if (!rfh->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { result.No( BackForwardCacheMetrics::NotRestoredReason::kSchemeNotHTTPOrHTTPS); @@ -391,6 +397,10 @@ void BackForwardCacheImpl::CanStoreRenderFrameHost( BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating); } + // Do not store documents if they have inner WebContents. + if (rfh->IsOuterDelegateFrame()) + result->No(BackForwardCacheMetrics::NotRestoredReason::kHaveInnerContents); + for (size_t i = 0; i < rfh->child_count(); i++) CanStoreRenderFrameHost(result, rfh->child_at(i)->current_frame_host()); } @@ -492,8 +502,8 @@ void BackForwardCacheImpl::EvictFramesInRelatedSiteInstances( } void BackForwardCacheImpl::PostTaskToDestroyEvictedFrames() { - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&BackForwardCacheImpl::DestroyEvictedFrames, + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BackForwardCacheImpl::DestroyEvictedFrames, weak_factory_.GetWeakPtr())); } diff --git a/chromium/content/browser/frame_host/back_forward_cache_metrics.h b/chromium/content/browser/frame_host/back_forward_cache_metrics.h index 9c595fd2f52..16c4319bba2 100644 --- a/chromium/content/browser/frame_host/back_forward_cache_metrics.h +++ b/chromium/content/browser/frame_host/back_forward_cache_metrics.h @@ -72,7 +72,10 @@ class BackForwardCacheMetrics kRenderFrameHostReused_CrossSite = 28, kNotMostRecentNavigationEntry = 29, kServiceWorkerClaim = 30, - kMaxValue = kServiceWorkerClaim, + kIgnoreEventAndEvict = 31, + kHaveInnerContents = 32, + kTimeoutPuttingInCache = 33, + kMaxValue = kTimeoutPuttingInCache, }; using NotRestoredReasons = diff --git a/chromium/content/browser/frame_host/cookie_utils.cc b/chromium/content/browser/frame_host/cookie_utils.cc index 6896e4ccdf8..c1ed5558c2c 100644 --- a/chromium/content/browser/frame_host/cookie_utils.cc +++ b/chromium/content/browser/frame_host/cookie_utils.cc @@ -10,19 +10,17 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/cookie_access_details.h" #include "content/public/common/content_client.h" -#include "content/public/common/content_features.h" -#include "net/cookies/cookie_util.h" +#include "net/cookies/cookie_inclusion_status.h" #include "services/metrics/public/cpp/ukm_builders.h" namespace content { namespace { -void RecordContextDowngradeUKM( - RenderFrameHost* rfh, - CookieAccessDetails::Type access_type, - const net::CanonicalCookie::CookieInclusionStatus& status, - const GURL& url) { +void RecordContextDowngradeUKM(RenderFrameHost* rfh, + CookieAccessDetails::Type access_type, + const net::CookieInclusionStatus& status, + const GURL& url) { DCHECK(rfh); ukm::SourceId source_id = rfh->GetPageUkmSourceId(); @@ -59,8 +57,7 @@ void SplitCookiesIntoAllowedAndBlocked( for (auto& cookie_and_status : cookie_details->cookie_list) { if (cookie_and_status.status.HasExclusionReason( - net::CanonicalCookie::CookieInclusionStatus:: - EXCLUDE_USER_PREFERENCES)) { + net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES)) { blocked->cookie_list.push_back(std::move(cookie_and_status.cookie)); } else if (cookie_and_status.status.IsInclude()) { allowed->cookie_list.push_back(std::move(cookie_and_status.cookie)); @@ -78,53 +75,33 @@ void EmitSameSiteCookiesDeprecationWarning( bool samesite_treated_as_lax_cookies = false; bool samesite_none_insecure_cookies = false; - - bool messages_disabled_by_cmdline = - base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine( - features::kCookieDeprecationMessages.name, - base::FeatureList::OVERRIDE_DISABLE_FEATURE); - bool breaking_context_downgrade = false; for (const net::CookieWithStatus& excluded_cookie : cookie_details->cookie_list) { - std::string cookie_url = - net::cookie_util::CookieOriginToURL(excluded_cookie.cookie.Domain(), - excluded_cookie.cookie.IsSecure()) - .possibly_invalid_spec(); - if (excluded_cookie.status.ShouldWarn()) { - if (excluded_cookie.status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus:: - WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT)) { - samesite_treated_as_lax_cookies = true; - } - - if (excluded_cookie.status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus:: - WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE)) { - samesite_treated_as_lax_cookies = true; - } - - if (excluded_cookie.status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus:: - WARN_SAMESITE_NONE_INSECURE)) { - samesite_none_insecure_cookies = true; - } + samesite_treated_as_lax_cookies = + samesite_treated_as_lax_cookies || + excluded_cookie.status.HasWarningReason( + net::CookieInclusionStatus:: + WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT) || + excluded_cookie.status.HasWarningReason( + net::CookieInclusionStatus:: + WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE); + + samesite_none_insecure_cookies = + samesite_none_insecure_cookies || + excluded_cookie.status.HasWarningReason( + net::CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE); + devtools_instrumentation::ReportSameSiteCookieIssue( root_frame_host, excluded_cookie, cookie_details->url, cookie_details->site_for_cookies, cookie_details->type == CookieAccessDetails::Type::kRead - ? blink::mojom::SameSiteCookieOperation::ReadCookie - : blink::mojom::SameSiteCookieOperation::SetCookie, + ? blink::mojom::SameSiteCookieOperation::kReadCookie + : blink::mojom::SameSiteCookieOperation::kSetCookie, cookie_details->devtools_request_id); } - if (!messages_disabled_by_cmdline) { - root_frame_host->AddSameSiteCookieDeprecationMessage( - cookie_url, excluded_cookie.status, - net::cookie_util::IsSameSiteByDefaultCookiesEnabled(), - net::cookie_util::IsCookiesWithoutSameSiteMustBeSecureEnabled()); - } breaking_context_downgrade = breaking_context_downgrade || excluded_cookie.status.HasDowngradeWarning(); @@ -136,8 +113,6 @@ void EmitSameSiteCookiesDeprecationWarning( } } - // TODO(crbug.com/990439): Do we need separate UseCounter metrics for - // Lax-allow-unsafe? We already have histograms in CanonicalCookie. if (samesite_treated_as_lax_cookies) { GetContentClient()->browser()->LogWebFeatureForCurrentPage( rfh, blink::mojom::WebFeature::kCookieNoSameSite); diff --git a/chromium/content/browser/frame_host/cookie_utils.h b/chromium/content/browser/frame_host/cookie_utils.h index 09042230acc..f578cf59c22 100644 --- a/chromium/content/browser/frame_host/cookie_utils.h +++ b/chromium/content/browser/frame_host/cookie_utils.h @@ -17,6 +17,8 @@ void SplitCookiesIntoAllowedAndBlocked( CookieAccessDetails* allowed, CookieAccessDetails* blocked); +// Logs SameSite cookie warnings to DevTools Issues Panel and logs event to +// UseCounters and UKM. Does not log to the JS console. // TODO(crbug.com/977040): Remove when no longer needed. void EmitSameSiteCookiesDeprecationWarning( RenderFrameHostImpl* rfh, diff --git a/chromium/content/browser/frame_host/cross_process_frame_connector.cc b/chromium/content/browser/frame_host/cross_process_frame_connector.cc index babd12facb8..385b190c5bf 100644 --- a/chromium/content/browser/frame_host/cross_process_frame_connector.cc +++ b/chromium/content/browser/frame_host/cross_process_frame_connector.cc @@ -48,12 +48,13 @@ CrossProcessFrameConnector::CrossProcessFrameConnector( RenderFrameProxyHost* frame_proxy_in_parent_renderer) : FrameConnectorDelegate(IsUseZoomForDSFEnabled()), frame_proxy_in_parent_renderer_(frame_proxy_in_parent_renderer) { - // At this point, SetView() has not been called and so the associated RenderWidgetHost doesn't - // have a view yet. That means calling GetScreenInfo() on the associated RenderWidgetHost will - // just default to the primary display, which may not be appropriate. So instead we call - // GetScreenInfo() on the root RenderWidgetHost, which will be guaranteed to be on the correct - // display. All subsequent updates to |screen_info_| ultimately come from the root, so it makes - // sense to do it here as well. + // At this point, SetView() has not been called and so the associated + // RenderWidgetHost doesn't have a view yet. That means calling + // GetScreenInfo() on the associated RenderWidgetHost will just default to the + // primary display, which may not be appropriate. So instead we call + // GetScreenInfo() on the root RenderWidgetHost, which will be guaranteed to + // be on the correct display. All subsequent updates to |screen_info_| + // ultimately come from the root, so it makes sense to do it here as well. RootRenderFrameHost(current_child_frame_host()) ->GetRenderWidgetHost() ->GetScreenInfo(&screen_info_); diff --git a/chromium/content/browser/frame_host/debug_urls.cc b/chromium/content/browser/frame_host/debug_urls.cc index 3d2441c30e0..284233157e8 100644 --- a/chromium/content/browser/frame_host/debug_urls.cc +++ b/chromium/content/browser/frame_host/debug_urls.cc @@ -14,7 +14,6 @@ #include "base/sanitizer_buildflags.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" -#include "base/task/post_task.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "cc/base/switches.h" @@ -166,9 +165,9 @@ bool HandleDebugURL(const GURL& url, if (url == kChromeUIDelayedBrowserUIHang) { // Webdriver-safe url to hang the ui thread. Webdriver waits for the onload // event in javascript which needs a little more time to fire. - base::PostDelayedTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&HangCurrentThread), - base::TimeDelta::FromSeconds(2)); + GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, base::BindOnce(&HangCurrentThread), + base::TimeDelta::FromSeconds(2)); return true; } @@ -215,8 +214,8 @@ bool HandleDebugURL(const GURL& url, } if (url == kChromeUIPpapiFlashCrashURL || url == kChromeUIPpapiFlashHangURL) { - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&HandlePpapiFlashDebugURL, url)); + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&HandlePpapiFlashDebugURL, url)); return true; } diff --git a/chromium/content/browser/frame_host/embedding_token_browsertest.cc b/chromium/content/browser/frame_host/embedding_token_browsertest.cc index e371a638fb9..103641833aa 100644 --- a/chromium/content/browser/frame_host/embedding_token_browsertest.cc +++ b/chromium/content/browser/frame_host/embedding_token_browsertest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/test/scoped_feature_list.h" #include "base/unguessable_token.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/web_contents/web_contents_impl.h" @@ -24,6 +25,14 @@ class EmbeddingTokenBrowserTest : public ContentBrowserTest { EmbeddingTokenBrowserTest() = default; void SetUpCommandLine(base::CommandLine* command_line) override { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + features::kBackForwardCache, + { + // Set a very long TTL before expiration (longer than the test + // timeout) so tests that are expecting deletion don't pass when + // they shouldn't. + {"TimeToLiveInBackForwardCacheInSeconds", "3600"}, + }); ContentBrowserTest::SetUpCommandLine(command_line); IsolateAllSitesForTesting(command_line); } @@ -45,37 +54,41 @@ class EmbeddingTokenBrowserTest : public ContentBrowserTest { delete; private: - FrameTreeVisualizer visualizer_; + base::test::ScopedFeatureList scoped_feature_list_; }; -IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, NoEmbeddingTokenOnMainFrame) { +IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, EmbeddingTokenOnMainFrame) { GURL a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); GURL b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); // Starts without an embedding token. EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); - // Embedding tokens don't get added to the main frame. + // Embedding tokens should get added to the main frame. EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto first_token = top_frame_host()->GetEmbeddingToken().value(); EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_NE(top_frame_host()->GetEmbeddingToken().value(), first_token); } IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, - EmbeddingTokensAddedToCrossSiteIFrames) { + EmbeddingTokensAddedToCrossDocumentIFrames) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b(a),c,a)"))); ASSERT_EQ(3U, top_frame_host()->child_count()); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token = top_frame_host()->GetEmbeddingToken().value(); // Child 0 (b) should have an embedding token. auto child_0_token = top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); ASSERT_TRUE(child_0_token.has_value()); EXPECT_NE(base::UnguessableToken::Null(), child_0_token); + EXPECT_NE(top_token, child_0_token); // Child 0 (a) of Child 0 (b) should have an embedding token. ASSERT_EQ(1U, top_frame_host()->child_at(0)->child_count()); @@ -86,6 +99,7 @@ IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, ->GetEmbeddingToken(); ASSERT_TRUE(child_0_0_token.has_value()); EXPECT_NE(base::UnguessableToken::Null(), child_0_0_token); + EXPECT_NE(top_token, child_0_0_token); EXPECT_NE(child_0_token, child_0_0_token); // Child 1 (c) should have an embedding token. @@ -93,34 +107,51 @@ IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, top_frame_host()->child_at(1)->current_frame_host()->GetEmbeddingToken(); ASSERT_TRUE(child_1_token.has_value()); EXPECT_NE(base::UnguessableToken::Null(), child_1_token); + EXPECT_NE(top_token, child_1_token); EXPECT_NE(child_0_token, child_1_token); EXPECT_NE(child_0_0_token, child_1_token); - // Child 2 (a) shouldn't have an embedding token as it is same site. - EXPECT_FALSE(top_frame_host() - ->child_at(2) - ->current_frame_host() - ->GetEmbeddingToken() - .has_value()); + // Child 2 (a) should have an embedding token. + auto child_2_token = + top_frame_host()->child_at(2)->current_frame_host()->GetEmbeddingToken(); + ASSERT_TRUE(child_2_token.has_value()); + EXPECT_NE(base::UnguessableToken::Null(), child_2_token); + EXPECT_NE(top_token, child_2_token); + EXPECT_NE(child_0_token, child_2_token); + EXPECT_NE(child_0_0_token, child_2_token); // TODO(ckitagawa): Somehow assert that the parent and child have matching // embedding tokens in parent HTMLOwnerElement and child LocalFrame. } IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, - EmbeddingTokensSwapOnCrossSiteNavigations) { + EmbeddingTokenSwapsOnCrossDocumentNavigation) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)"))); ASSERT_EQ(1U, top_frame_host()->child_count()); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token = top_frame_host()->GetEmbeddingToken().value(); // Child 0 (b) should have an embedding token. RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); auto child_0_token = target->GetEmbeddingToken(); ASSERT_TRUE(child_0_token.has_value()); EXPECT_NE(base::UnguessableToken::Null(), child_0_token); + EXPECT_NE(top_token, child_0_token); + + // Navigate child 0 (b) to same-site the token should swap. + NavigateIframeToURL(shell()->web_contents(), "child-0", + embedded_test_server() + ->GetURL("b.com", "/site_isolation/") + .Resolve("blank.html")); + auto same_site_new_child_0_token = + top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); + ASSERT_TRUE(same_site_new_child_0_token.has_value()); + EXPECT_NE(base::UnguessableToken::Null(), same_site_new_child_0_token); + EXPECT_NE(top_token, same_site_new_child_0_token); + EXPECT_NE(child_0_token, same_site_new_child_0_token); // Navigate child 0 (b) to another site (cross-process) the token should swap. { @@ -131,24 +162,36 @@ IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, .Resolve("blank.html")); deleted_observer.WaitUntilDeleted(); } - auto new_child_0_token = + auto new_site_child_0_token = top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); - ASSERT_TRUE(new_child_0_token.has_value()); - EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); - EXPECT_NE(child_0_token, new_child_0_token); + ASSERT_TRUE(same_site_new_child_0_token.has_value()); + EXPECT_NE(base::UnguessableToken::Null(), new_site_child_0_token); + EXPECT_NE(top_token, new_site_child_0_token); + EXPECT_NE(child_0_token, new_site_child_0_token); + EXPECT_NE(same_site_new_child_0_token, new_site_child_0_token); // TODO(ckitagawa): Somehow assert that the parent and child have matching // embedding tokens in parent HTMLOwnerElement and child LocalFrame. } -IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, - EmbeddingTokensDoNotSwapOnSameSiteNavigations) { +IN_PROC_BROWSER_TEST_F( + EmbeddingTokenBrowserTest, + EmbeddingTokenNotChangedOnSubframeSameDocumentNavigation) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(a)"))); ASSERT_EQ(1U, top_frame_host()->child_count()); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token = top_frame_host()->GetEmbeddingToken().value(); + + // Child 0 (a) should have an embedding token. + RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); + auto child_0_token = target->GetEmbeddingToken(); + ASSERT_TRUE(child_0_token.has_value()); + EXPECT_NE(base::UnguessableToken::Null(), child_0_token); + EXPECT_NE(top_token, child_0_token); + auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); // Navigate child 0 to another site (cross-process) a token should be created. { @@ -158,94 +201,137 @@ IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, deleted_observer.WaitUntilDeleted(); } - // Child 0 (b) should have an embedding token. - auto child_0_token = + // Child 0 (b) should have a new embedding token. + auto new_child_0_token = top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); ASSERT_TRUE(child_0_token.has_value()); - EXPECT_NE(base::UnguessableToken::Null(), child_0_token); + EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); + EXPECT_NE(top_token, new_child_0_token); + EXPECT_NE(child_0_token, new_child_0_token); - // Navigate child 0 (b) to same origin the token should not swap. - NavigateIframeToURL(web_contents(), "child-0", b_url.Resolve("valid.html")); - auto new_child_0_token = + // Navigate child 0 (b) to same document the token should not swap. + NavigateIframeToURL(web_contents(), "child-0", + b_url.Resolve("blank.html#foo")); + auto same_document_new_child_0_token = top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); - ASSERT_TRUE(new_child_0_token.has_value()); - // If we are creating a new frame even for same-site navigations then we would - // expect a different token. - if (CreateNewHostForSameSiteSubframe()) { - EXPECT_NE(child_0_token, new_child_0_token); - } else { - EXPECT_EQ(child_0_token, new_child_0_token); - } + ASSERT_TRUE(same_document_new_child_0_token.has_value()); + EXPECT_EQ(new_child_0_token, same_document_new_child_0_token); // TODO(ckitagawa): Somehow assert that the parent and child have matching // embedding tokens in parent HTMLOwnerElement and child LocalFrame. } IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, - EmbeddingTokenRemovedOnSubframeNavigationToSameOrigin) { + EmbeddingTokenChangedOnSubframeNavigationToNewDocument) { auto a_url = embedded_test_server()->GetURL("a.com", "/"); EXPECT_TRUE(NavigateToURL( shell(), a_url.Resolve("cross_site_iframe_factory.html?a(b)"))); ASSERT_EQ(1U, top_frame_host()->child_count()); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token = top_frame_host()->GetEmbeddingToken().value(); // Child 0 (b) should have an embedding token. RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); auto child_0_token = target->GetEmbeddingToken(); ASSERT_TRUE(child_0_token.has_value()); EXPECT_NE(base::UnguessableToken::Null(), child_0_token); + EXPECT_NE(top_token, child_0_token); - // Navigate child 0 (b) to the same site as the main frame. This shouldn't - // create an embedding token as the child is now local. + // Navigate child 0 (b) to the same site as the main frame. This should create + // an embedding token. { RenderFrameDeletedObserver deleted_observer(target); NavigateIframeToURL(web_contents(), "child-0", a_url.Resolve("site_isolation/").Resolve("blank.html")); deleted_observer.WaitUntilDeleted(); } - ASSERT_FALSE(top_frame_host() - ->child_at(0) - ->current_frame_host() - ->GetEmbeddingToken() - .has_value()); + + auto new_child_0_token = + top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); + ASSERT_TRUE(new_child_0_token.has_value()); + EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); + EXPECT_NE(top_token, new_child_0_token); + EXPECT_NE(child_0_token, new_child_0_token); // TODO(ckitagawa): Somehow assert that the parent and child have matching // embedding tokens in parent HTMLOwnerElement and child LocalFrame. } -IN_PROC_BROWSER_TEST_F( - EmbeddingTokenBrowserTest, - EmbeddingTokenAddedOnSubframeNavigationAwayFromSameOrigin) { - auto a_url = embedded_test_server()->GetURL("a.com", "/"); - EXPECT_TRUE(NavigateToURL( - shell(), a_url.Resolve("cross_site_iframe_factory.html?a(a)"))); +IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, + BackForwardCacheCrossDocument) { + auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); + auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); + EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); - ASSERT_EQ(1U, top_frame_host()->child_count()); - EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); - // Child shouldn't have an embedding token. - RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); - auto child_0_token = target->GetEmbeddingToken(); - EXPECT_FALSE(child_0_token.has_value()); + EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_b = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_NE(top_token_a, top_token_b); + + // Navigate back to the first origin. The back forward cache should keep + // the embedding token. + web_contents()->GetController().GoBack(); + EXPECT_TRUE(content::WaitForLoadStop(web_contents())); + + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_EQ(top_token_a, top_token_a_prime); +} - // Navigate child 0 to a different site. This should now have an embedding - // token. - { - RenderFrameDeletedObserver deleted_observer(target); - NavigateIframeToURL(web_contents(), "child-0", - embedded_test_server() - ->GetURL("b.com", "/site_isolation/") - .Resolve("blank.html")); - deleted_observer.WaitUntilDeleted(); - } - auto new_child_0_token = - top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); - ASSERT_TRUE(new_child_0_token.has_value()); - EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); +IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, + BackForwardCacheCrossDocumentAfterSameDocument) { + auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); + auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); + EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); - // TODO(ckitagawa): Somehow assert that the parent and child have matching - // embedding tokens in parent HTMLOwnerElement and child LocalFrame. + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); + + EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html#foo"))); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + EXPECT_EQ(top_frame_host()->GetEmbeddingToken().value(), top_token_a); + + EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_b = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_NE(top_token_a, top_token_b); + + // Navigate back to the first origin. The back forward cache should keep + // the embedding token even when the embedding token is not present in the + // most recent navigation. + web_contents()->GetController().GoBack(); + EXPECT_TRUE(content::WaitForLoadStop(web_contents())); + + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_EQ(top_token_a, top_token_a_prime); +} + +IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, + SameDocumentHistoryPreservesTokens) { + auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); + EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); + + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); + + EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html#foo"))); + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_EQ(top_token_a, top_token_a_prime); + + // Navigate back to before the fragment was added. This should preserve the + // embedding token. + web_contents()->GetController().GoBack(); + EXPECT_TRUE(content::WaitForLoadStop(web_contents())); + + EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); + auto top_token_a_prime_prime = top_frame_host()->GetEmbeddingToken().value(); + EXPECT_EQ(top_token_a, top_token_a_prime_prime); } } // namespace content diff --git a/chromium/content/browser/frame_host/file_chooser_impl.cc b/chromium/content/browser/frame_host/file_chooser_impl.cc deleted file mode 100644 index add104f1de3..00000000000 --- a/chromium/content/browser/frame_host/file_chooser_impl.cc +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/browser/frame_host/file_chooser_impl.h" - -#include "content/browser/child_process_security_policy_impl.h" -#include "content/browser/frame_host/render_frame_host_delegate.h" -#include "content/browser/frame_host/render_frame_host_impl.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/storage_partition.h" -#include "content/public/browser/web_contents.h" -#include "mojo/public/cpp/bindings/self_owned_receiver.h" - -namespace content { - -FileChooserImpl::FileSelectListenerImpl::~FileSelectListenerImpl() { -#if DCHECK_IS_ON() - DCHECK(was_file_select_listener_function_called_) - << "Must call either FileSelectListener::FileSelected() or " - "FileSelectListener::FileSelectionCanceled()"; - // TODO(avi): Turn on the DCHECK on the following line. This cannot yet be - // done because I can't say for sure that I know who all the callers who bind - // blink::mojom::FileChooser are. https://crbug.com/1054811 - /* DCHECK(was_fullscreen_block_set_) << "The fullscreen block was not set"; */ -#endif - if (owner_) - owner_->ResetListenerImpl(); -} - -void FileChooserImpl::FileSelectListenerImpl::SetFullscreenBlock( - base::ScopedClosureRunner fullscreen_block) { -#if DCHECK_IS_ON() - DCHECK(!was_fullscreen_block_set_) - << "Fullscreen block must only be set once"; - was_fullscreen_block_set_ = true; -#endif - fullscreen_block_ = std::move(fullscreen_block); -} - -void FileChooserImpl::FileSelectListenerImpl::FileSelected( - std::vector<blink::mojom::FileChooserFileInfoPtr> files, - const base::FilePath& base_dir, - blink::mojom::FileChooserParams::Mode mode) { -#if DCHECK_IS_ON() - DCHECK(!was_file_select_listener_function_called_) - << "Must not call both of FileSelectListener::FileSelected() and " - "FileSelectListener::FileSelectionCanceled()"; - was_file_select_listener_function_called_ = true; -#endif - if (owner_) - owner_->FileSelected(std::move(files), base_dir, mode); -} - -void FileChooserImpl::FileSelectListenerImpl::FileSelectionCanceled() { -#if DCHECK_IS_ON() - DCHECK(!was_file_select_listener_function_called_) - << "Should not call both of FileSelectListener::FileSelected() and " - "FileSelectListener::FileSelectionCanceled()"; - was_file_select_listener_function_called_ = true; -#endif - if (owner_) - owner_->FileSelectionCanceled(); -} - -void FileChooserImpl::FileSelectListenerImpl:: - SetListenerFunctionCalledTrueForTesting() { -#if DCHECK_IS_ON() - was_file_select_listener_function_called_ = true; -#endif -} - -// static -void FileChooserImpl::Create( - RenderFrameHostImpl* render_frame_host, - mojo::PendingReceiver<blink::mojom::FileChooser> receiver) { - mojo::MakeSelfOwnedReceiver( - std::make_unique<FileChooserImpl>(render_frame_host), - std::move(receiver)); -} - -FileChooserImpl::FileChooserImpl(RenderFrameHostImpl* render_frame_host) - : render_frame_host_(render_frame_host) { - Observe(WebContents::FromRenderFrameHost(render_frame_host)); -} - -FileChooserImpl::~FileChooserImpl() { - if (listener_impl_) - listener_impl_->ResetOwner(); -} - -void FileChooserImpl::OpenFileChooser(blink::mojom::FileChooserParamsPtr params, - OpenFileChooserCallback callback) { - if (listener_impl_ || !render_frame_host_) { - std::move(callback).Run(nullptr); - return; - } - callback_ = std::move(callback); - auto listener = std::make_unique<FileSelectListenerImpl>(this); - listener_impl_ = listener.get(); - // Do not allow messages with absolute paths in them as this can permit a - // renderer to coerce the browser to perform I/O on a renderer controlled - // path. - if (params->default_file_name != params->default_file_name.BaseName()) { - mojo::ReportBadMessage( - "FileChooser: The default file name must not be an absolute path."); - listener->FileSelectionCanceled(); - return; - } - render_frame_host_->delegate()->RunFileChooser(render_frame_host_, - std::move(listener), *params); -} - -void FileChooserImpl::EnumerateChosenDirectory( - const base::FilePath& directory_path, - EnumerateChosenDirectoryCallback callback) { - if (listener_impl_ || !render_frame_host_) { - std::move(callback).Run(nullptr); - return; - } - callback_ = std::move(callback); - auto listener = std::make_unique<FileSelectListenerImpl>(this); - listener_impl_ = listener.get(); - auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); - if (policy->CanReadFile(render_frame_host_->GetProcess()->GetID(), - directory_path)) { - render_frame_host_->delegate()->EnumerateDirectory( - render_frame_host_, std::move(listener), directory_path); - } else { - listener->FileSelectionCanceled(); - } -} - -void FileChooserImpl::FileSelected( - std::vector<blink::mojom::FileChooserFileInfoPtr> files, - const base::FilePath& base_dir, - blink::mojom::FileChooserParams::Mode mode) { - listener_impl_ = nullptr; - if (!render_frame_host_) - return; - storage::FileSystemContext* file_system_context = nullptr; - const int pid = render_frame_host_->GetProcess()->GetID(); - auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); - // Grant the security access requested to the given files. - for (const auto& file : files) { - if (mode == blink::mojom::FileChooserParams::Mode::kSave) { - policy->GrantCreateReadWriteFile(pid, file->get_native_file()->file_path); - } else { - if (file->is_file_system()) { - if (!file_system_context) { - file_system_context = - BrowserContext::GetStoragePartition( - render_frame_host_->GetProcess()->GetBrowserContext(), - render_frame_host_->GetSiteInstance()) - ->GetFileSystemContext(); - } - policy->GrantReadFileSystem( - pid, file_system_context->CrackURL(file->get_file_system()->url) - .mount_filesystem_id()); - } else { - policy->GrantReadFile(pid, file->get_native_file()->file_path); - } - } - } - std::move(callback_).Run(FileChooserResult::New(std::move(files), base_dir)); -} - -void FileChooserImpl::FileSelectionCanceled() { - listener_impl_ = nullptr; - if (!render_frame_host_) - return; - std::move(callback_).Run(nullptr); -} - -void FileChooserImpl::ResetListenerImpl() { - listener_impl_ = nullptr; -} - -void FileChooserImpl::RenderFrameHostChanged(RenderFrameHost* old_host, - RenderFrameHost* new_host) { - if (old_host == render_frame_host_) - render_frame_host_ = nullptr; -} - -void FileChooserImpl::RenderFrameDeleted(RenderFrameHost* render_frame_host) { - if (render_frame_host == render_frame_host_) - render_frame_host_ = nullptr; -} - -void FileChooserImpl::WebContentsDestroyed() { - render_frame_host_ = nullptr; -} - -} // namespace content diff --git a/chromium/content/browser/frame_host/file_chooser_impl.h b/chromium/content/browser/frame_host/file_chooser_impl.h deleted file mode 100644 index 223364352d6..00000000000 --- a/chromium/content/browser/frame_host/file_chooser_impl.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_BROWSER_FRAME_HOST_FILE_CHOOSER_IMPL_H_ -#define CONTENT_BROWSER_FRAME_HOST_FILE_CHOOSER_IMPL_H_ - -#include "base/callback_helpers.h" -#include "content/common/content_export.h" -#include "content/public/browser/file_select_listener.h" -#include "content/public/browser/web_contents_observer.h" -#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" - -namespace content { - -class RenderFrameHostImpl; - -// An implementation of blink::mojom::FileChooser and FileSelectListener -// associated to RenderFrameHost. -class CONTENT_EXPORT FileChooserImpl : public blink::mojom::FileChooser, - public content::WebContentsObserver { - using FileChooserResult = blink::mojom::FileChooserResult; - - public: - class CONTENT_EXPORT FileSelectListenerImpl - : public content::FileSelectListener { - public: - explicit FileSelectListenerImpl(FileChooserImpl* owner) : owner_(owner) {} - ~FileSelectListenerImpl() override; - void SetFullscreenBlock(base::ScopedClosureRunner fullscreen_block); - void ResetOwner() { owner_ = nullptr; } - - // FileSelectListener overrides: - - void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files, - const base::FilePath& base_dir, - blink::mojom::FileChooserParams::Mode mode) override; - - void FileSelectionCanceled() override; - - protected: - // This sets |was_file_select_listener_function_called_| to true so that - // tests can pass with mocked overrides of this class. - void SetListenerFunctionCalledTrueForTesting(); - - private: - FileChooserImpl* owner_; - base::ScopedClosureRunner fullscreen_block_; -#if DCHECK_IS_ON() - bool was_file_select_listener_function_called_ = false; - bool was_fullscreen_block_set_ = false; -#endif - }; - - static void Create(RenderFrameHostImpl* render_frame_host, - mojo::PendingReceiver<blink::mojom::FileChooser> receiver); - - explicit FileChooserImpl(RenderFrameHostImpl* render_frame_host); - - ~FileChooserImpl() override; - - void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files, - const base::FilePath& base_dir, - blink::mojom::FileChooserParams::Mode mode); - - void FileSelectionCanceled(); - - void ResetListenerImpl(); - - // content::WebContentsObserver overrides: - - void OpenFileChooser(blink::mojom::FileChooserParamsPtr params, - OpenFileChooserCallback callback) override; - - void EnumerateChosenDirectory( - const base::FilePath& directory_path, - EnumerateChosenDirectoryCallback callback) override; - - private: - // content::WebContentsObserver overrides: - - void RenderFrameHostChanged(RenderFrameHost* old_host, - RenderFrameHost* new_host) override; - - void RenderFrameDeleted(RenderFrameHost* render_frame_host) override; - - void WebContentsDestroyed() override; - - RenderFrameHostImpl* render_frame_host_; - FileSelectListenerImpl* listener_impl_ = nullptr; - base::OnceCallback<void(blink::mojom::FileChooserResultPtr)> callback_; -}; - -} // namespace content - -#endif // CONTENT_BROWSER_FRAME_HOST_FILE_CHOOSER_IMPL_H_ diff --git a/chromium/content/browser/frame_host/frame_navigation_entry.cc b/chromium/content/browser/frame_host/frame_navigation_entry.cc index d2f9113b663..719526b6cc2 100644 --- a/chromium/content/browser/frame_host/frame_navigation_entry.cc +++ b/chromium/content/browser/frame_host/frame_navigation_entry.cc @@ -30,7 +30,8 @@ FrameNavigationEntry::FrameNavigationEntry( const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info) : frame_unique_name_(frame_unique_name), item_sequence_number_(item_sequence_number), document_sequence_number_(document_sequence_number), @@ -44,7 +45,8 @@ FrameNavigationEntry::FrameNavigationEntry( bindings_(kInvalidBindings), method_(method), post_id_(post_id), - blob_url_loader_factory_(std::move(blob_url_loader_factory)) { + blob_url_loader_factory_(std::move(blob_url_loader_factory)), + web_bundle_navigation_info_(std::move(web_bundle_navigation_info)) { if (origin) committed_origin_ = *origin; } @@ -59,7 +61,8 @@ scoped_refptr<FrameNavigationEntry> FrameNavigationEntry::Clone() const { document_sequence_number_, site_instance_.get(), nullptr, url_, committed_origin_, referrer_, initiator_origin_, redirect_chain_, page_state_, method_, post_id_, - nullptr /* blob_url_loader_factory */); + nullptr /* blob_url_loader_factory */, + nullptr /* web_bundle_navigation_info */); // |bindings_| gets only updated through the SetBindings API, not through // UpdateEntry, so make a copy of it explicitly here as part of cloning. copy->bindings_ = bindings_; @@ -80,7 +83,8 @@ void FrameNavigationEntry::UpdateEntry( const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) { + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info) { frame_unique_name_ = frame_unique_name; item_sequence_number_ = item_sequence_number; document_sequence_number_ = document_sequence_number; @@ -95,6 +99,7 @@ void FrameNavigationEntry::UpdateEntry( method_ = method; post_id_ = post_id; blob_url_loader_factory_ = std::move(blob_url_loader_factory); + web_bundle_navigation_info_ = std::move(web_bundle_navigation_info); } void FrameNavigationEntry::set_item_sequence_number( diff --git a/chromium/content/browser/frame_host/frame_navigation_entry.h b/chromium/content/browser/frame_host/frame_navigation_entry.h index f96f1dbbd0d..cbd17aa489b 100644 --- a/chromium/content/browser/frame_host/frame_navigation_entry.h +++ b/chromium/content/browser/frame_host/frame_navigation_entry.h @@ -54,7 +54,8 @@ class CONTENT_EXPORT FrameNavigationEntry const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory); + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info); // Creates a copy of this FrameNavigationEntry that can be modified // independently from the original. @@ -75,7 +76,8 @@ class CONTENT_EXPORT FrameNavigationEntry const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory); + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info); // The unique name of the frame this entry is for. This is a stable name for // the frame based on its position in the tree and relation to other named diff --git a/chromium/content/browser/frame_host/frame_tree.cc b/chromium/content/browser/frame_host/frame_tree.cc index 100cd8079a0..8a21369714a 100644 --- a/chromium/content/browser/frame_host/frame_tree.cc +++ b/chromium/content/browser/frame_host/frame_tree.cc @@ -94,7 +94,8 @@ FrameTree::NodeRange::NodeRange(FrameTreeNode* root, FrameTreeNode* root_of_subtree_to_skip) : root_(root), root_of_subtree_to_skip_(root_of_subtree_to_skip) {} -FrameTree::FrameTree(Navigator* navigator, +FrameTree::FrameTree(NavigationControllerImpl* navigation_controller, + NavigatorDelegate* navigator_delegate, RenderFrameHostDelegate* render_frame_delegate, RenderViewHostDelegate* render_view_delegate, RenderWidgetHostDelegate* render_widget_delegate, @@ -103,8 +104,8 @@ FrameTree::FrameTree(Navigator* navigator, render_view_delegate_(render_view_delegate), render_widget_delegate_(render_widget_delegate), manager_delegate_(manager_delegate), + navigator_(navigation_controller, navigator_delegate), root_(new FrameTreeNode(this, - navigator, nullptr, // The top-level frame must always be in a // document scope. @@ -203,9 +204,8 @@ FrameTreeNode* FrameTree::AddFrame( return nullptr; std::unique_ptr<FrameTreeNode> new_node = base::WrapUnique(new FrameTreeNode( - this, parent->frame_tree_node()->navigator(), parent, scope, frame_name, - frame_unique_name, is_created_by_script, devtools_frame_token, - frame_owner_properties, owner_type)); + this, parent, scope, frame_name, frame_unique_name, is_created_by_script, + devtools_frame_token, frame_owner_properties, owner_type)); // Set sandbox flags and container policy and make them effective immediately, // since initial sandbox flags and feature policy should apply to the initial @@ -237,11 +237,8 @@ FrameTreeNode* FrameTree::AddFrame( // same |frame_unique_name|, since we don't remove FrameNavigationEntries if // their frames are deleted. If there is a stale one, remove it to avoid // conflicts on future updates. - NavigationEntryImpl* last_committed_entry = - static_cast<NavigationEntryImpl*>(parent->frame_tree_node() - ->navigator() - ->GetController() - ->GetLastCommittedEntry()); + NavigationEntryImpl* last_committed_entry = static_cast<NavigationEntryImpl*>( + navigator_.GetController()->GetLastCommittedEntry()); if (last_committed_entry) { last_committed_entry->RemoveEntryForFrame( added_node, /* only_if_different_position = */ true); @@ -452,7 +449,7 @@ void FrameTree::UpdateLoadProgress(double progress) { load_progress_ = progress; // Notify the WebContents. - root_->navigator()->GetDelegate()->DidChangeLoadProgress(); + root_->navigator().GetDelegate()->DidChangeLoadProgress(); } void FrameTree::ResetLoadProgress() { diff --git a/chromium/content/browser/frame_host/frame_tree.h b/chromium/content/browser/frame_host/frame_tree.h index b2050b910de..a843f64a7d2 100644 --- a/chromium/content/browser/frame_host/frame_tree.h +++ b/chromium/content/browser/frame_host/frame_tree.h @@ -16,7 +16,9 @@ #include "base/containers/queue.h" #include "base/gtest_prod_util.h" #include "base/macros.h" -#include "content/browser/frame_host/frame_tree_node.h" +#include "content/browser/frame_host/navigator.h" +#include "content/browser/frame_host/navigator_delegate.h" +#include "content/browser/frame_host/render_frame_host_manager.h" #include "content/common/content_export.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "services/service_manager/public/mojom/interface_provider.mojom.h" @@ -29,7 +31,7 @@ struct FramePolicy; namespace content { -class Navigator; +class NavigationControllerImpl; class RenderFrameHostDelegate; class RenderViewHostDelegate; class RenderViewHostImpl; @@ -96,7 +98,8 @@ class CONTENT_EXPORT FrameTree { // RenderFrameHostManagers. // TODO(creis): This set of delegates will change as we move things to // Navigator. - FrameTree(Navigator* navigator, + FrameTree(NavigationControllerImpl* navigation_controller, + NavigatorDelegate* navigator_delegate, RenderFrameHostDelegate* render_frame_delegate, RenderViewHostDelegate* render_view_delegate, RenderWidgetHostDelegate* render_widget_delegate, @@ -279,6 +282,9 @@ class CONTENT_EXPORT FrameTree { const url::Origin& previously_visited_origin, NavigationRequest* navigation_request_to_exclude); + NavigationControllerImpl* controller() { return navigator_.controller(); } + Navigator& navigator() { return navigator_; } + private: friend class FrameTreeTest; FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplBrowserTest, RemoveFocusedFrame); @@ -295,6 +301,10 @@ class CONTENT_EXPORT FrameTree { RenderWidgetHostDelegate* render_widget_delegate_; RenderFrameHostManager::Delegate* manager_delegate_; + // The Navigator object responsible for managing navigations on this frame + // tree. + Navigator navigator_; + // Map of SiteInstance ID to RenderViewHost. This allows us to look up the // RenderViewHost for a given SiteInstance when creating RenderFrameHosts. // Each RenderViewHost maintains a refcount and is deleted when there are no diff --git a/chromium/content/browser/frame_host/frame_tree_node.cc b/chromium/content/browser/frame_host/frame_tree_node.cc index d4864979d3f..a1c72899b94 100644 --- a/chromium/content/browser/frame_host/frame_tree_node.cc +++ b/chromium/content/browser/frame_host/frame_tree_node.cc @@ -17,7 +17,6 @@ #include "base/stl_util.h" #include "base/strings/string_util.h" #include "content/browser/devtools/devtools_instrumentation.h" -#include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/navigation_controller_impl.h" #include "content/browser/frame_host/navigation_request.h" #include "content/browser/frame_host/navigator.h" @@ -106,7 +105,6 @@ FrameTreeNode* FrameTreeNode::From(RenderFrameHost* rfh) { FrameTreeNode::FrameTreeNode( FrameTree* frame_tree, - Navigator* navigator, RenderFrameHostImpl* parent, blink::mojom::TreeScopeType scope, const std::string& name, @@ -116,7 +114,6 @@ FrameTreeNode::FrameTreeNode( const blink::mojom::FrameOwnerProperties& frame_owner_properties, blink::mojom::FrameOwnerElementType owner_type) : frame_tree_(frame_tree), - navigator_(navigator), render_manager_(this, frame_tree->manager_delegate()), frame_tree_node_id_(next_frame_tree_node_id_++), parent_(parent), @@ -136,7 +133,7 @@ FrameTreeNode::FrameTreeNode( std::vector<uint32_t>() /* hashes of hosts for insecure request upgrades */, false /* is a potentially trustworthy unique origin */, - false /* has received a user gesture */, + false /* has an active user gesture */, false /* has received a user gesture before nav */, owner_type), is_created_by_script_(is_created_by_script), @@ -164,7 +161,7 @@ FrameTreeNode::~FrameTreeNode() { // See also https://crbug.com/784356. if (is_created_by_script_ && parent_) { NavigationEntryImpl* nav_entry = static_cast<NavigationEntryImpl*>( - navigator()->GetController()->GetLastCommittedEntry()); + navigator().GetController()->GetLastCommittedEntry()); if (nav_entry) { nav_entry->RemoveEntryForFrame(this, /* only_if_different_position = */ false); @@ -515,7 +512,7 @@ void FrameTreeNode::DidStartLoading(bool to_different_document, // Notify the WebContents. if (!was_previously_loading) - navigator()->GetDelegate()->DidStartLoading(this, to_different_document); + navigator().GetDelegate()->DidStartLoading(this, to_different_document); // Set initial load progress and update overall progress. This will notify // the WebContents of the load progress change. @@ -537,7 +534,7 @@ void FrameTreeNode::DidStopLoading() { // Notify the WebContents. if (!frame_tree_->IsLoading()) - navigator()->GetDelegate()->DidStopLoading(); + navigator().GetDelegate()->DidStopLoading(); } void FrameTreeNode::DidChangeLoadProgress(double load_progress) { @@ -595,7 +592,7 @@ bool FrameTreeNode::NotifyUserActivation() { rfh->DidReceiveFirstUserActivation(); rfh->frame_tree_node()->user_activation_state_.Activate(); } - replication_state_.has_received_user_gesture = true; + replication_state_.has_active_user_gesture = true; // See the "Same-origin Visibility" section in |UserActivationState| class // doc. @@ -612,7 +609,7 @@ bool FrameTreeNode::NotifyUserActivation() { } NavigationControllerImpl* controller = - static_cast<NavigationControllerImpl*>(navigator()->GetController()); + static_cast<NavigationControllerImpl*>(navigator().GetController()); if (controller) controller->NotifyUserActivation(); @@ -623,12 +620,14 @@ bool FrameTreeNode::ConsumeTransientUserActivation() { bool was_active = user_activation_state_.IsActive(); for (FrameTreeNode* node : frame_tree()->Nodes()) node->user_activation_state_.ConsumeIfActive(); + replication_state_.has_active_user_gesture = false; return was_active; } bool FrameTreeNode::ClearUserActivation() { for (FrameTreeNode* node : frame_tree()->SubtreeNodes(this)) node->user_activation_state_.Clear(); + replication_state_.has_active_user_gesture = false; return true; } diff --git a/chromium/content/browser/frame_host/frame_tree_node.h b/chromium/content/browser/frame_host/frame_tree_node.h index 6df7a88eb74..60ba1670c9a 100644 --- a/chromium/content/browser/frame_host/frame_tree_node.h +++ b/chromium/content/browser/frame_host/frame_tree_node.h @@ -14,7 +14,9 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/frame_tree_node_blame_context.h" +#include "content/browser/frame_host/navigator.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/render_frame_host_manager.h" #include "content/common/content_export.h" @@ -32,9 +34,7 @@ namespace content { -class FrameTree; class NavigationRequest; -class Navigator; class RenderFrameHostImpl; class NavigationEntryImpl; @@ -77,7 +77,6 @@ class CONTENT_EXPORT FrameTreeNode { // calling the constructor. FrameTreeNode( FrameTree* frame_tree, - Navigator* navigator, RenderFrameHostImpl* parent, blink::mojom::TreeScopeType scope, const std::string& name, @@ -102,7 +101,8 @@ class CONTENT_EXPORT FrameTreeNode { void ResetForNavigation(); FrameTree* frame_tree() const { return frame_tree_; } - Navigator* navigator() { return navigator_.get(); } + Navigator& navigator() { return frame_tree()->navigator(); } + RenderFrameHostManager* render_manager() { return &render_manager_; } int frame_tree_node_id() const { return frame_tree_node_id_; } const std::string& frame_name() const { return replication_state_.name; } @@ -361,11 +361,6 @@ class CONTENT_EXPORT FrameTreeNode { network::mojom::WebSandboxFlags sandbox_flags, const blink::ParsedFeaturePolicy& parsed_header); - // Returns whether the frame received a user gesture. - bool has_received_user_gesture() const { - return replication_state_.has_received_user_gesture; - } - // Returns whether the frame received a user gesture on a previous navigation // on the same eTLD+1. bool has_received_user_gesture_before_nav() const { @@ -443,10 +438,6 @@ class CONTENT_EXPORT FrameTreeNode { // The FrameTree that owns us. FrameTree* frame_tree_; // not owned. - // The Navigator object responsible for managing navigations at this node - // of the frame tree. - scoped_refptr<Navigator> navigator_; - // Manages creation and swapping of RenderFrameHosts for this frame. RenderFrameHostManager render_manager_; diff --git a/chromium/content/browser/frame_host/frame_tree_node_blame_context.cc b/chromium/content/browser/frame_host/frame_tree_node_blame_context.cc index d2b8a334503..1485f4d4f86 100644 --- a/chromium/content/browser/frame_host/frame_tree_node_blame_context.cc +++ b/chromium/content/browser/frame_host/frame_tree_node_blame_context.cc @@ -7,7 +7,7 @@ #include "base/process/process_handle.h" #include "base/strings/stringprintf.h" #include "base/trace_event/traced_value.h" -#include "content/browser/frame_host/frame_tree.h" +#include "content/browser/frame_host/frame_tree_node.h" #include "url/gurl.h" namespace content { diff --git a/chromium/content/browser/frame_host/ipc_utils.cc b/chromium/content/browser/frame_host/ipc_utils.cc index b3315d25e34..16a236529ce 100644 --- a/chromium/content/browser/frame_host/ipc_utils.cc +++ b/chromium/content/browser/frame_host/ipc_utils.cc @@ -6,6 +6,7 @@ #include <utility> +#include "base/optional.h" #include "content/browser/bad_message.h" #include "content/browser/blob_storage/chrome_blob_storage_context.h" #include "content/browser/child_process_security_policy_impl.h" @@ -22,19 +23,14 @@ namespace content { namespace { -bool VerifyBlobToken(int process_id, - mojo::MessagePipeHandle received_token, - const GURL& received_url, - mojo::PendingRemote<blink::mojom::BlobURLToken>* - out_blob_url_token_remote) { +// Validates that |received_token| is non-null iff associated with a blob: URL. +bool VerifyBlobToken( + int process_id, + const mojo::PendingRemote<blink::mojom::BlobURLToken>& received_token, + const GURL& received_url) { DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id); - DCHECK(out_blob_url_token_remote); - mojo::ScopedMessagePipeHandle blob_url_token_handle( - std::move(received_token)); - mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token_remote( - std::move(blob_url_token_handle), blink::mojom::BlobURLToken::Version_); - if (blob_url_token_remote) { + if (received_token) { if (!received_url.SchemeIsBlob()) { bad_message::ReceivedBadMessage( process_id, bad_message::BLOB_URL_TOKEN_FOR_NON_BLOB_URL); @@ -42,7 +38,6 @@ bool VerifyBlobToken(int process_id, } } - *out_blob_url_token_remote = std::move(blob_url_token_remote); return true; } @@ -74,38 +69,32 @@ bool VerifyInitiatorOrigin(int process_id, } // namespace bool VerifyDownloadUrlParams(SiteInstance* site_instance, - blink::mojom::DownloadURLParams* params, - mojo::PendingRemote<blink::mojom::BlobURLToken>* - out_blob_url_token_remote) { + const blink::mojom::DownloadURLParams& params) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(site_instance); - DCHECK(out_blob_url_token_remote); RenderProcessHost* process = site_instance->GetProcess(); int process_id = process->GetID(); - // Verify |params.blob_url_token| and populate |out_blob_url_token_remote|. - if (params->blob_url_token && - !VerifyBlobToken(process_id, params->blob_url_token.release(), - params->url, out_blob_url_token_remote)) { + // Verifies |params.blob_url_token| is appropriately set. + if (!VerifyBlobToken(process_id, params.blob_url_token, params.url)) return false; - } // Verify |params.initiator_origin|. - if (params->initiator_origin && - !VerifyInitiatorOrigin(process_id, *params->initiator_origin)) + if (params.initiator_origin && + !VerifyInitiatorOrigin(process_id, *params.initiator_origin)) return false; - // For large data URLs, they are passed through |params.data_url_blob| and - // |params.url| should be empty. - if (!params->url.is_valid()) - return params->data_url_blob.is_valid(); + // If |params.url| is not set, this must be a large data URL being passed + // through |params.data_url_blob|. + if (!params.url.is_valid() && !params.data_url_blob.is_valid()) + return false; // Verification succeeded. return true; } bool VerifyOpenURLParams(SiteInstance* site_instance, - const FrameHostMsg_OpenURL_Params& params, + const mojom::OpenURLParamsPtr& params, GURL* out_validated_url, scoped_refptr<network::SharedURLLoaderFactory>* out_blob_url_loader_factory) { @@ -117,31 +106,32 @@ bool VerifyOpenURLParams(SiteInstance* site_instance, int process_id = process->GetID(); // Verify |params.url| and populate |out_validated_url|. - *out_validated_url = params.url; + *out_validated_url = params->url; process->FilterURL(false, out_validated_url); // Verify |params.blob_url_token| and populate |out_blob_url_loader_factory|. - mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token_remote; - if (!VerifyBlobToken(process_id, params.blob_url_token, params.url, - &blob_url_token_remote)) { + mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token( + mojo::ScopedMessagePipeHandle(std::move(params->blob_url_token)), + blink::mojom::BlobURLToken::Version_); + if (!VerifyBlobToken(process_id, blob_url_token, params->url)) return false; - } - if (blob_url_token_remote) { + + if (blob_url_token.is_valid()) { *out_blob_url_loader_factory = ChromeBlobStorageContext::URLLoaderFactoryForToken( - process->GetBrowserContext(), std::move(blob_url_token_remote)); + process->GetBrowserContext(), std::move(blob_url_token)); } // Verify |params.post_body|. auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); - if (!policy->CanReadRequestBody(site_instance, params.post_body)) { + if (!policy->CanReadRequestBody(site_instance, params->post_body)) { bad_message::ReceivedBadMessage(process, bad_message::ILLEGAL_UPLOAD_PARAMS); return false; } // Verify |params.initiator_origin|. - if (!VerifyInitiatorOrigin(process_id, params.initiator_origin)) + if (!VerifyInitiatorOrigin(process_id, params->initiator_origin)) return false; // Verification succeeded. diff --git a/chromium/content/browser/frame_host/ipc_utils.h b/chromium/content/browser/frame_host/ipc_utils.h index 12e2b13db09..7c57ea5d127 100644 --- a/chromium/content/browser/frame_host/ipc_utils.h +++ b/chromium/content/browser/frame_host/ipc_utils.h @@ -12,8 +12,6 @@ #include "third_party/blink/public/mojom/frame/frame.mojom.h" #include "url/gurl.h" -struct FrameHostMsg_OpenURL_Params; - namespace content { class SiteInstance; @@ -21,19 +19,14 @@ class SiteInstance; // Verifies that |params| are valid and can be accessed by the renderer process // associated with |site_instance|. // -// Returns true if the |params| are valid. In the case |params->blob_url_token| -// is non-null, it gets deserialized and |out_blob_url_token_remote| is -// populated. |params| is a mojo Ptr instead const& to make it clear to callees -// of its mutable nature. +// If the |params| are valid, returns true. // -// Terminates the renderer with the given |process_id| and returns false if the -// |params| are invalid. +// Otherwise, terminates the renderer associated with |site_instance| and +// returns false. // // This function has to be called on the UI thread. -bool VerifyDownloadUrlParams( - SiteInstance* site_instance, - blink::mojom::DownloadURLParams* params, - mojo::PendingRemote<blink::mojom::BlobURLToken>* out_blob_url_token_remote); +bool VerifyDownloadUrlParams(SiteInstance* site_instance, + const blink::mojom::DownloadURLParams& params); // Verifies that |params| are valid and can be accessed by the renderer process // associated with |site_instance|. @@ -46,7 +39,7 @@ bool VerifyDownloadUrlParams( // // This function has to be called on the UI thread. bool VerifyOpenURLParams(SiteInstance* site_instance, - const FrameHostMsg_OpenURL_Params& params, + const mojom::OpenURLParamsPtr& params, GURL* out_validated_url, scoped_refptr<network::SharedURLLoaderFactory>* out_blob_url_loader_factory); diff --git a/chromium/content/browser/frame_host/keep_alive_handle_factory.cc b/chromium/content/browser/frame_host/keep_alive_handle_factory.cc index 9d702d0356c..67b200d0d5c 100644 --- a/chromium/content/browser/frame_host/keep_alive_handle_factory.cc +++ b/chromium/content/browser/frame_host/keep_alive_handle_factory.cc @@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/metrics/field_trial.h" #include "base/metrics/field_trial_params.h" -#include "base/task/post_task.h" #include "content/common/frame.mojom.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -43,9 +42,8 @@ class KeepAliveHandleFactory::Context final : public base::RefCounted<Context> { void DetachLater(base::TimeDelta timeout) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - base::PostDelayedTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&Context::Detach, AsWeakPtr()), - timeout); + GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, base::BindOnce(&Context::Detach, AsWeakPtr()), timeout); } void AddReceiver(std::unique_ptr<mojom::KeepAliveHandle> impl, diff --git a/chromium/content/browser/frame_host/mixed_content_navigation_throttle.cc b/chromium/content/browser/frame_host/mixed_content_navigation_throttle.cc index f2fc465d812..b647b00c723 100644 --- a/chromium/content/browser/frame_host/mixed_content_navigation_throttle.cc +++ b/chromium/content/browser/frame_host/mixed_content_navigation_throttle.cc @@ -77,6 +77,8 @@ void UpdateRendererOnMixedContentFound(NavigationRequest* navigation_request, params.request_context_type = navigation_request->request_context_type(); params.request_destination = navigation_request->request_destination(); params.was_allowed = was_allowed; + DCHECK(!navigation_request->GetRedirectChain().empty()); + params.url_before_redirects = navigation_request->GetRedirectChain()[0]; params.had_redirect = for_redirect; params.source_location = *(navigation_request->common_params().source_location.get()); @@ -207,7 +209,6 @@ bool MixedContentNavigationThrottle::ShouldBlockNavigation(bool for_redirect) { allowed = should_ask_delegate && frame_host_delegate->ShouldAllowRunningInsecureContent( - navigation_handle()->GetWebContents(), prefs.allow_running_insecure_content, mixed_content_frame->GetLastCommittedOrigin(), request->GetURL()); if (allowed) { diff --git a/chromium/content/browser/frame_host/navigation_controller_impl.cc b/chromium/content/browser/frame_host/navigation_controller_impl.cc index 005d9dcf49a..d6c77f00ee6 100644 --- a/chromium/content/browser/frame_host/navigation_controller_impl.cc +++ b/chromium/content/browser/frame_host/navigation_controller_impl.cc @@ -155,12 +155,7 @@ bool ShouldTreatNavigationAsReload(const GURL& url, bool is_post, bool is_reload, bool is_navigation_to_existing_entry, - bool has_interstitial, NavigationEntryImpl* last_committed_entry) { - // Don't convert when an interstitial is showing. - if (has_interstitial) - return false; - // Only convert main frame navigations to a new entry. if (!is_main_frame || is_reload || is_navigation_to_existing_entry) return false; @@ -478,7 +473,8 @@ std::unique_ptr<NavigationEntry> NavigationController::CreateNavigationEntry( return NavigationControllerImpl::CreateNavigationEntry( url, referrer, std::move(initiator_origin), nullptr /* source_site_instance */, transition, is_renderer_initiated, - extra_headers, browser_context, std::move(blob_url_loader_factory)); + extra_headers, browser_context, std::move(blob_url_loader_factory), + false /* should_replace_entry */); } // static @@ -492,7 +488,8 @@ NavigationControllerImpl::CreateNavigationEntry( bool is_renderer_initiated, const std::string& extra_headers, BrowserContext* browser_context, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) { + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + bool should_replace_entry) { GURL url_to_load; GURL virtual_url; bool reverse_on_redirect = false; @@ -514,6 +511,7 @@ NavigationControllerImpl::CreateNavigationEntry( entry->set_user_typed_url(virtual_url); entry->set_update_virtual_url_with_url(reverse_on_redirect); entry->set_extra_headers(extra_headers); + entry->set_should_replace_entry(should_replace_entry); return entry; } @@ -538,6 +536,17 @@ base::Time NavigationControllerImpl::TimeSmoother::GetSmoothedTime( return t; } +NavigationControllerImpl::ScopedShowRepostDialogForTesting:: + ScopedShowRepostDialogForTesting() + : was_disallowed_(g_check_for_repost) { + g_check_for_repost = true; +} + +NavigationControllerImpl::ScopedShowRepostDialogForTesting:: + ~ScopedShowRepostDialogForTesting() { + g_check_for_repost = was_disallowed_; +} + NavigationControllerImpl::NavigationControllerImpl( NavigationControllerDelegate* delegate, BrowserContext* browser_context) @@ -592,18 +601,6 @@ void NavigationControllerImpl::Restore( void NavigationControllerImpl::Reload(ReloadType reload_type, bool check_for_repost) { DCHECK_NE(ReloadType::NONE, reload_type); - - if (transient_entry_index_ != -1) { - // If an interstitial is showing, treat a reload as a navigation to the - // transient entry's URL. - NavigationEntryImpl* transient_entry = GetTransientEntry(); - if (!transient_entry) - return; - LoadURL(transient_entry->GetURL(), Referrer(), ui::PAGE_TRANSITION_RELOAD, - transient_entry->extra_headers()); - return; - } - NavigationEntryImpl* entry = nullptr; int current_index = -1; @@ -722,16 +719,12 @@ void NavigationControllerImpl::SetPendingEntry( } NavigationEntryImpl* NavigationControllerImpl::GetActiveEntry() { - if (transient_entry_index_ != -1) - return entries_[transient_entry_index_].get(); if (pending_entry_) return pending_entry_; return GetLastCommittedEntry(); } NavigationEntryImpl* NavigationControllerImpl::GetVisibleEntry() { - if (transient_entry_index_ != -1) - return entries_[transient_entry_index_].get(); // The pending entry is safe to return for new (non-history), browser- // initiated navigations. Most renderer-initiated navigations should not // show the pending entry, to prevent URL spoof attacks. @@ -760,8 +753,6 @@ NavigationEntryImpl* NavigationControllerImpl::GetVisibleEntry() { } int NavigationControllerImpl::GetCurrentEntryIndex() { - if (transient_entry_index_ != -1) - return transient_entry_index_; if (pending_entry_index_ != -1) return pending_entry_index_; return last_committed_entry_index_; @@ -784,16 +775,14 @@ bool NavigationControllerImpl::CanViewSource() { int NavigationControllerImpl::GetLastCommittedEntryIndex() { // The last committed entry index must always be less than the number of - // entries. If there are no entries, it must be -1. However, there may be a - // transient entry while the last committed entry index is still -1. + // entries. If there are no entries, it must be -1. DCHECK_LT(last_committed_entry_index_, GetEntryCount()); DCHECK(GetEntryCount() || last_committed_entry_index_ == -1); return last_committed_entry_index_; } int NavigationControllerImpl::GetEntryCount() { - DCHECK_LE(entries_.size(), - max_entry_count() + (transient_entry_index_ == -1 ? 0 : 1)); + DCHECK_LE(entries_.size(), max_entry_count()); return static_cast<int>(entries_.size()); } @@ -906,17 +895,6 @@ void NavigationControllerImpl::GoToIndex(int index, return; } - if (transient_entry_index_ != -1) { - if (index == transient_entry_index_) { - // Nothing to do when navigating to the transient. - return; - } - if (index > transient_entry_index_) { - // Removing the transient is goint to shift all entries by 1. - index--; - } - } - DiscardNonCommittedEntries(); DCHECK_EQ(nullptr, pending_entry_); @@ -1411,7 +1389,8 @@ void NavigationControllerImpl::RendererDidNavigateToNewPage( params.document_sequence_number, rfh->GetSiteInstance(), nullptr, params.url, (params.url_is_unreachable) ? nullptr : ¶ms.origin, params.referrer, initiator_origin, params.redirects, params.page_state, - params.method, params.post_id, nullptr /* blob_url_loader_factory */); + params.method, params.post_id, nullptr /* blob_url_loader_factory */, + nullptr /* web_bundle_navigation_info */); new_entry = GetLastCommittedEntry()->CloneAndReplace( frame_entry, true, rfh->frame_tree_node(), @@ -1725,7 +1704,10 @@ void NavigationControllerImpl::RendererDidNavigateToExistingPage( params.document_sequence_number, rfh->GetSiteInstance(), nullptr, params.url, GetCommittedOriginForFrameEntry(params), params.referrer, initiator_origin, params.redirects, params.page_state, params.method, - params.post_id, nullptr /* blob_url_loader_factory */); + params.post_id, nullptr /* blob_url_loader_factory */, + request->web_bundle_navigation_info() + ? request->web_bundle_navigation_info()->Clone() + : nullptr); // The redirected to page should not inherit the favicon from the previous // page. @@ -1743,8 +1725,7 @@ void NavigationControllerImpl::RendererDidNavigateToExistingPage( if (!keep_pending_entry) DiscardNonCommittedEntries(); - // If a transient entry was removed, the indices might have changed, so we - // have to query the entry index again. + // Update the last committed index to reflect the committed entry. last_committed_entry_index_ = GetIndexOfEntry(entry); } @@ -1802,7 +1783,10 @@ void NavigationControllerImpl::RendererDidNavigateToSamePage( params.document_sequence_number, rfh->GetSiteInstance(), nullptr, params.url, GetCommittedOriginForFrameEntry(params), params.referrer, initiator_origin, params.redirects, params.page_state, params.method, - params.post_id, nullptr /* blob_url_loader_factory */); + params.post_id, nullptr /* blob_url_loader_factory */, + request->web_bundle_navigation_info() + ? request->web_bundle_navigation_info()->Clone() + : nullptr); DiscardNonCommittedEntries(); } @@ -1839,7 +1823,10 @@ void NavigationControllerImpl::RendererDidNavigateNewSubframe( params.document_sequence_number, rfh->GetSiteInstance(), nullptr, params.url, (params.url_is_unreachable) ? nullptr : ¶ms.origin, params.referrer, initiator_origin, params.redirects, params.page_state, - params.method, params.post_id, nullptr /* blob_url_loader_factory */); + params.method, params.post_id, nullptr /* blob_url_loader_factory */, + request->web_bundle_navigation_info() + ? request->web_bundle_navigation_info()->Clone() + : nullptr); std::unique_ptr<NavigationEntryImpl> new_entry = GetLastCommittedEntry()->CloneAndReplace( @@ -1917,7 +1904,10 @@ bool NavigationControllerImpl::RendererDidNavigateAutoSubframe( params.document_sequence_number, rfh->GetSiteInstance(), nullptr, params.url, GetCommittedOriginForFrameEntry(params), params.referrer, initiator_origin, params.redirects, params.page_state, params.method, - params.post_id, nullptr /* blob_url_loader_factory */); + params.post_id, nullptr /* blob_url_loader_factory */, + request->web_bundle_navigation_info() + ? request->web_bundle_navigation_info()->Clone() + : nullptr); return send_commit_notification; } @@ -2037,9 +2027,8 @@ void NavigationControllerImpl::CopyStateFromAndPrune(NavigationController* temp, if (!replace_entry) source->PruneOldestSkippableEntryIfFull(); - // Insert the entries from source. Don't use source->GetCurrentEntryIndex as - // we don't want to copy over the transient entry. Ignore any pending entry, - // since it has not committed in source. + // Insert the entries from source. Ignore any pending entry, since it has not + // committed in source. int max_source_index = source->last_committed_entry_index_; if (max_source_index == -1) max_source_index = source->GetEntryCount(); @@ -2075,10 +2064,6 @@ bool NavigationControllerImpl::CanPruneAllButLastCommitted() { if (pending_entry_index_ != -1) return false; - // We should not prune if we are currently showing a transient entry. - if (transient_entry_index_ != -1) - return false; - return true; } @@ -2236,7 +2221,7 @@ bool NavigationControllerImpl::StartHistoryNavigationInNewSubframe( request->SetNavigationClient(std::move(*navigation_client)); - render_frame_host->frame_tree_node()->navigator()->Navigate( + render_frame_host->frame_tree_node()->navigator().Navigate( std::move(request), ReloadType::NONE, RestoreType::NONE); return true; @@ -2271,6 +2256,11 @@ void NavigationControllerImpl::NavigateFromFrameProxy( FrameTreeNode* node = render_frame_host->frame_tree_node(); + // Don't allow an entry replacement if there is no entry to replace. + // http://crbug.com/457149 + if (GetEntryCount() == 0) + should_replace_current_entry = false; + // Create a NavigationEntry for the transfer, without making it the pending // entry. Subframe transfers should have a clone of the last committed entry // with a FrameNavigationEntry for the target frame. Main frame transfers @@ -2296,31 +2286,25 @@ void NavigationControllerImpl::NavigateFromFrameProxy( GURL(url::kAboutBlankURL), referrer, initiator_origin, source_site_instance, page_transition, is_renderer_initiated, extra_headers, browser_context_, - nullptr /* blob_url_loader_factory */)); + nullptr /* blob_url_loader_factory */, should_replace_current_entry)); } entry->AddOrUpdateFrameEntry( node, -1, -1, nullptr, static_cast<SiteInstanceImpl*>(source_site_instance), url, base::nullopt /* commit_origin */, referrer, initiator_origin, - std::vector<GURL>(), PageState(), method, -1, blob_url_loader_factory); + std::vector<GURL>(), PageState(), method, -1, blob_url_loader_factory, + nullptr /* web_bundle_navigation_info */); } else { // Main frame case. entry = NavigationEntryImpl::FromNavigationEntry(CreateNavigationEntry( url, referrer, initiator_origin, source_site_instance, page_transition, is_renderer_initiated, extra_headers, browser_context_, - blob_url_loader_factory)); + blob_url_loader_factory, should_replace_current_entry)); entry->root_node()->frame_entry->set_source_site_instance( static_cast<SiteInstanceImpl*>(source_site_instance)); entry->root_node()->frame_entry->set_method(method); } - // Don't allow an entry replacement if there is no entry to replace. - // http://crbug.com/457149 - if (GetEntryCount() == 0) - should_replace_current_entry = false; - - entry->set_should_replace_entry(should_replace_current_entry); - bool override_user_agent = false; if (GetLastCommittedEntry() && GetLastCommittedEntry()->GetIsOverridingUserAgent()) { @@ -2340,7 +2324,8 @@ void NavigationControllerImpl::NavigateFromFrameProxy( node->unique_name(), -1, -1, nullptr, static_cast<SiteInstanceImpl*>(source_site_instance), url, nullptr /* origin */, referrer, initiator_origin, std::vector<GURL>(), - PageState(), method, -1, blob_url_loader_factory); + PageState(), method, -1, blob_url_loader_factory, + nullptr /* web_bundle_navigation_info */); } LoadURLParams params(url); @@ -2386,8 +2371,8 @@ void NavigationControllerImpl::NavigateFromFrameProxy( // remains of a cancelled browser initiated navigation to avoid URL spoofs. DiscardNonCommittedEntries(); - node->navigator()->Navigate(std::move(request), ReloadType::NONE, - RestoreType::NONE); + node->navigator().Navigate(std::move(request), ReloadType::NONE, + RestoreType::NONE); } void NavigationControllerImpl::SetSessionStorageNamespace( @@ -2482,18 +2467,8 @@ void NavigationControllerImpl::SetNeedsReload(NeedsReloadType type) { void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) { DCHECK_LT(index, GetEntryCount()); DCHECK_NE(index, last_committed_entry_index_); - - const bool was_transient = index == transient_entry_index_; - DiscardNonCommittedEntries(); - if (was_transient) { - // There's nothing left to do if the index referred to a transient entry - // that we just discarded. - DCHECK(!GetTransientEntry()); - return; - } - entries_.erase(entries_.begin() + index); if (last_committed_entry_index_ > index) last_committed_entry_index_--; @@ -2610,6 +2585,7 @@ void NavigationControllerImpl::NavigateToExistingPendingEntry( pending_entry_ == entry_replaced_by_post_commit_error_.get()); } DCHECK(!IsRendererDebugURL(pending_entry_->GetURL())); + bool is_forced_reload = needs_reload_; needs_reload_ = false; FrameTreeNode* root = delegate_->GetFrameTree()->root(); int nav_entry_id = pending_entry_->GetUniqueID(); @@ -2623,8 +2599,8 @@ void NavigationControllerImpl::NavigateToExistingPendingEntry( root, pending_entry_, pending_entry_->GetFrameEntry(root), ReloadType::NONE, false /* is_same_document_history_load */, false /* is_history_navigation_in_new_child */); - root->navigator()->Navigate(std::move(navigation_request), ReloadType::NONE, - RestoreType::NONE); + root->navigator().Navigate(std::move(navigation_request), ReloadType::NONE, + RestoreType::NONE); return; } @@ -2674,31 +2650,44 @@ void NavigationControllerImpl::NavigateToExistingPendingEntry( // navigated. std::vector<std::unique_ptr<NavigationRequest>> same_document_loads; std::vector<std::unique_ptr<NavigationRequest>> different_document_loads; - if (GetLastCommittedEntry()) { - FindFramesToNavigate(root, reload_type, &same_document_loads, - &different_document_loads); - } + FindFramesToNavigate(root, reload_type, &same_document_loads, + &different_document_loads); if (same_document_loads.empty() && different_document_loads.empty()) { - // If we don't have any frames to navigate at this point, either - // (1) there is no previous history entry to compare against, or - // (2) we were unable to match any frames by name. In the first case, - // doing a different document navigation to the root item is the only valid - // thing to do. In the second case, we should have been able to find a - // frame to navigate based on names if this were a same document - // navigation, so we can safely assume this is the different document case. + // We were unable to match any frames to navigate. This can happen if a + // history navigation targets a subframe that no longer exists + // (https://crbug.com/705550). In this case, we need to update the current + // history entry to the pending one but keep the main document loaded. We + // also need to ensure that observers are informed about the updated + // current history entry (e.g., for greying out back/forward buttons), and + // that renderer processes update their history offsets. The easiest way + // to do all that is to schedule a "redundant" same-document navigation in + // the main frame. + // + // Note that we don't want to remove this history entry, as it might still + // be valid later, since a frame that it's targeting may be recreated. + // + // TODO(alexmos, creis): This behavior isn't ideal, as the user would + // need to repeat history navigations until finding the one that works. + // Consider changing this behavior to keep looking for the first valid + // history entry that finds frames to navigate. std::unique_ptr<NavigationRequest> navigation_request = CreateNavigationRequestFromEntry( root, pending_entry_, pending_entry_->GetFrameEntry(root), - reload_type, false /* is_same_document_history_load */, + ReloadType::NONE /* reload_type */, + true /* is_same_document_history_load */, false /* is_history_navigation_in_new_child */); if (!navigation_request) { - // This navigation cannot start (e.g. the URL is invalid), delete the - // pending NavigationEntry. + // If this navigation cannot start, delete the pending NavigationEntry. DiscardPendingEntry(false); return; } - different_document_loads.push_back(std::move(navigation_request)); + same_document_loads.push_back(std::move(navigation_request)); + + // Sanity check that we never take this branch for any kinds of reloads, + // as those should've queued a different-document load in the main frame. + DCHECK(!is_forced_reload); + DCHECK_EQ(reload_type, ReloadType::NONE); } // If |sandboxed_source_frame_node_id| is valid, then track whether this @@ -2748,83 +2737,160 @@ void NavigationControllerImpl::NavigateToExistingPendingEntry( // Send all the same document frame loads before the different document loads. for (auto& item : same_document_loads) { FrameTreeNode* frame = item->frame_tree_node(); - frame->navigator()->Navigate(std::move(item), reload_type, - pending_entry_->restore_type()); + frame->navigator().Navigate(std::move(item), reload_type, + pending_entry_->restore_type()); } for (auto& item : different_document_loads) { FrameTreeNode* frame = item->frame_tree_node(); - frame->navigator()->Navigate(std::move(item), reload_type, - pending_entry_->restore_type()); + frame->navigator().Navigate(std::move(item), reload_type, + pending_entry_->restore_type()); } in_navigate_to_pending_entry_ = false; } +NavigationControllerImpl::HistoryNavigationAction +NavigationControllerImpl::DetermineActionForHistoryNavigation( + FrameTreeNode* frame, + ReloadType reload_type) { + // Only active frames can navigate: + // - If the frame is in pending deletion, the browser already committed to + // destroying this RenderFrameHost. See https://crbug.com/930278. + // - If the frame is in back-forward cache, it's not allowed to navigate as it + // should remain frozen. Ignore the request and evict the document from + // back-forward cache. + // + // If the frame is inactive, there's no need to recurse into subframes, which + // should all be inactive as well. + if (frame->current_frame_host()->IsInactiveAndDisallowReactivation()) + return HistoryNavigationAction::kStopLooking; + + // If there's no last committed entry, there is no previous history entry to + // compare against, so fall back to a different-document load. Note that we + // should only reach this case for the root frame and not descend further + // into subframes. + if (!GetLastCommittedEntry()) { + DCHECK(frame->IsMainFrame()); + return HistoryNavigationAction::kDifferentDocument; + } + + // Reloads should result in a different-document load. Note that reloads may + // also happen via the |needs_reload_| mechanism where the reload_type is + // NONE, so detect this by comparing whether we're going to the same + // entry that we're currently on. Similarly to above, only main frames + // should reach this. Note that subframes support reloads, but that's done + // via a different path that doesn't involve FindFramesToNavigate (see + // RenderFrameHost::Reload()). + if (reload_type != ReloadType::NONE || + pending_entry_index_ == last_committed_entry_index_) { + DCHECK(frame->IsMainFrame()); + return HistoryNavigationAction::kDifferentDocument; + } + + // If there is no new FrameNavigationEntry for the frame, ignore the + // load. For example, this may happen when going back to an entry before a + // frame was created. Suppose we commit a same-document navigation that also + // results in adding a new subframe somewhere in the tree. If we go back, + // the new subframe will be missing a FrameNavigationEntry in the previous + // NavigationEntry, but we shouldn't delete or change what's loaded in + // it. + // + // Note that in this case, there is no need to keep looking for navigations + // in subframes, which would be missing FrameNavigationEntries as well. + // + // It's important to check this before checking |old_item| below, since both + // might be null, and in that case we still shouldn't change what's loaded in + // this frame. Note that scheduling any loads assumes that |new_item| is + // non-null. See https://crbug.com/1088354. + FrameNavigationEntry* new_item = pending_entry_->GetFrameEntry(frame); + if (!new_item) + return HistoryNavigationAction::kStopLooking; + + // If there is no old FrameNavigationEntry, schedule a different-document + // load. + // + // TODO(creis): Store the last committed FrameNavigationEntry to use here, + // rather than assuming the NavigationEntry has up to date info on subframes. + // Note that this may require sharing FrameNavigationEntries between + // NavigationEntries (https://crbug.com/373041) as a prerequisite, since + // otherwise the stored FrameNavigationEntry might get stale (e.g., after + // subframe navigations, or in the case where we don't find any frames to + // navigate and ignore a back/forward navigation while moving to a different + // NavigationEntry, as in https://crbug.com/705550). + FrameNavigationEntry* old_item = + GetLastCommittedEntry()->GetFrameEntry(frame); + if (!old_item) + return HistoryNavigationAction::kDifferentDocument; + + // If the new item is not in the same SiteInstance, schedule a + // different-document load. Newly restored items may not have a SiteInstance + // yet, in which case it will be assigned on first commit. + if (new_item->site_instance() && + new_item->site_instance() != old_item->site_instance()) + return HistoryNavigationAction::kDifferentDocument; + + // Schedule a different-document load if the current RenderFrameHost is not + // live. This case can happen for Ctrl+Back or after + // a renderer crash. + if (!frame->current_frame_host()->IsRenderFrameLive()) + return HistoryNavigationAction::kDifferentDocument; + + if (new_item->item_sequence_number() != old_item->item_sequence_number()) { + // Same document loads happen if the previous item has the same document + // sequence number but different item sequence number. + if (new_item->document_sequence_number() == + old_item->document_sequence_number()) + return HistoryNavigationAction::kSameDocument; + + // Otherwise, if both item and document sequence numbers differ, this + // should be a different document load. + return HistoryNavigationAction::kDifferentDocument; + } + + // If the item sequence numbers match, there is no need to navigate this + // frame. Keep looking for navigations in this frame's children. + DCHECK_EQ(new_item->document_sequence_number(), + old_item->document_sequence_number()); + return HistoryNavigationAction::kKeepLooking; +} + void NavigationControllerImpl::FindFramesToNavigate( FrameTreeNode* frame, ReloadType reload_type, std::vector<std::unique_ptr<NavigationRequest>>* same_document_loads, std::vector<std::unique_ptr<NavigationRequest>>* different_document_loads) { - // A frame pending deletion is not allowed to navigate anymore. It has been - // deleted and the browser already committed to destroying this - // RenderFrameHost. See https://crbug.com/930278. - if (!frame->current_frame_host()->is_active()) - return; - DCHECK(pending_entry_); - DCHECK_GE(last_committed_entry_index_, 0); FrameNavigationEntry* new_item = pending_entry_->GetFrameEntry(frame); - // TODO(creis): Store the last committed FrameNavigationEntry to use here, - // rather than assuming the NavigationEntry has up to date info on subframes. - FrameNavigationEntry* old_item = - GetLastCommittedEntry()->GetFrameEntry(frame); - if (!new_item) - return; - // Schedule a load in this frame if the new item isn't for the same item - // sequence number in the same SiteInstance. Newly restored items may not have - // a SiteInstance yet, in which case it will be assigned on first commit. - if (!old_item || - new_item->item_sequence_number() != old_item->item_sequence_number() || - (new_item->site_instance() && - new_item->site_instance() != old_item->site_instance())) { - // Same document loads happen if the previous item has the same document - // sequence number. Note that we should treat them as different document if - // the destination RenderFrameHost (which is necessarily the current - // RenderFrameHost for same document navigations) doesn't have a last - // committed page. This case can happen for Ctrl+Back or after a renderer - // crash. - if (old_item && - new_item->document_sequence_number() == - old_item->document_sequence_number() && - !frame->current_frame_host()->GetLastCommittedURL().is_empty()) { - std::unique_ptr<NavigationRequest> navigation_request = - CreateNavigationRequestFromEntry( - frame, pending_entry_, new_item, reload_type, - true /* is_same_document_history_load */, - false /* is_history_navigation_in_new_child */); - if (navigation_request) { - // Only add the request if was properly created. It's possible for the - // creation to fail in certain cases, e.g. when the URL is invalid. - same_document_loads->push_back(std::move(navigation_request)); - } + auto action = DetermineActionForHistoryNavigation(frame, reload_type); - // TODO(avi, creis): This is a bug; we should not return here. Rather, we - // should continue on and navigate all child frames which have also - // changed. This bug is the cause of <https://crbug.com/542299>, which is - // a NC_IN_PAGE_NAVIGATION renderer kill. - // - // However, this bug is a bandaid over a deeper and worse problem. Doing a - // pushState immediately after loading a subframe is a race, one that no - // web page author expects. If we fix this bug, many large websites break. - // For example, see <https://crbug.com/598043> and the spec discussion at - // <https://github.com/whatwg/html/issues/1191>. - // - // For now, we accept this bug, and hope to resolve the race in a - // different way that will one day allow us to fix this. - return; + if (action == HistoryNavigationAction::kSameDocument) { + std::unique_ptr<NavigationRequest> navigation_request = + CreateNavigationRequestFromEntry( + frame, pending_entry_, new_item, reload_type, + true /* is_same_document_history_load */, + false /* is_history_navigation_in_new_child */); + if (navigation_request) { + // Only add the request if was properly created. It's possible for the + // creation to fail in certain cases, e.g. when the URL is invalid. + same_document_loads->push_back(std::move(navigation_request)); } + // TODO(avi, creis): This is a bug; we should not return here. Rather, we + // should continue on and navigate all child frames which have also + // changed. This bug is the cause of <https://crbug.com/542299>, which is + // a NC_IN_PAGE_NAVIGATION renderer kill. + // + // However, this bug is a bandaid over a deeper and worse problem. Doing a + // pushState immediately after loading a subframe is a race, one that no + // web page author expects. If we fix this bug, many large websites break. + // For example, see <https://crbug.com/598043> and the spec discussion at + // <https://github.com/whatwg/html/issues/1191>. + // + // For now, we accept this bug, and hope to resolve the race in a + // different way that will one day allow us to fix this. + return; + } else if (action == HistoryNavigationAction::kDifferentDocument) { std::unique_ptr<NavigationRequest> navigation_request = CreateNavigationRequestFromEntry( frame, pending_entry_, new_item, reload_type, @@ -2838,6 +2904,8 @@ void NavigationControllerImpl::FindFramesToNavigate( // For a different document, the subframes will be destroyed, so there's // no need to consider them. return; + } else if (action == HistoryNavigationAction::kStopLooking) { + return; } for (size_t i = 0; i < frame->child_count(); i++) { @@ -2935,7 +3003,6 @@ void NavigationControllerImpl::NavigateWithoutEntry( params.load_type == NavigationController::LOAD_TYPE_HTTP_POST /* is_post */, false /* is_reload */, false /* is_navigation_to_existing_entry */, - transient_entry_index_ != -1 /* has_interstitial */, GetLastCommittedEntry())) { reload_type = ReloadType::NORMAL; } @@ -2971,8 +3038,8 @@ void NavigationControllerImpl::NavigateWithoutEntry( // function. std::unique_ptr<PendingEntryRef> pending_entry_ref = ReferencePendingEntry(); - node->navigator()->Navigate(std::move(request), reload_type, - RestoreType::NONE); + node->navigator().Navigate(std::move(request), reload_type, + RestoreType::NONE); in_navigate_to_pending_entry_ = false; } @@ -2988,7 +3055,12 @@ void NavigationControllerImpl::HandleRendererDebugURL( DiscardNonCommittedEntries(); return; } - frame_tree_node->render_manager()->InitializeRenderFrameForImmediateUse(); + // The current frame is always a main frame. If IsInitialNavigation() is + // true then there have been no navigations and any frames of this tab must + // be in the same renderer process. If that has crashed then the only frame + // that can be revived is the main frame. + frame_tree_node->render_manager() + ->InitializeMainRenderFrameForImmediateUse(); } frame_tree_node->current_frame_host()->HandleRendererDebugURL(url); } @@ -3028,21 +3100,22 @@ NavigationControllerImpl::CreateNavigationEntryFromLoadParams( GURL(url::kAboutBlankURL), params.referrer, params.initiator_origin, params.source_site_instance.get(), params.transition_type, params.is_renderer_initiated, extra_headers_crlf, browser_context_, - blob_url_loader_factory)); + blob_url_loader_factory, should_replace_current_entry)); } entry->AddOrUpdateFrameEntry( node, -1, -1, nullptr, static_cast<SiteInstanceImpl*>(params.source_site_instance.get()), params.url, base::nullopt, params.referrer, params.initiator_origin, - params.redirect_chain, PageState(), "GET", -1, blob_url_loader_factory); + params.redirect_chain, PageState(), "GET", -1, blob_url_loader_factory, + nullptr /* web_bundle_navigation_info */); } else { // Otherwise, create a pending entry for the main frame. entry = NavigationEntryImpl::FromNavigationEntry(CreateNavigationEntry( params.url, params.referrer, params.initiator_origin, params.source_site_instance.get(), params.transition_type, params.is_renderer_initiated, extra_headers_crlf, browser_context_, - blob_url_loader_factory)); + blob_url_loader_factory, should_replace_current_entry)); entry->set_source_site_instance( static_cast<SiteInstanceImpl*>(params.source_site_instance.get())); entry->SetRedirectChain(params.redirect_chain); @@ -3050,7 +3123,6 @@ NavigationControllerImpl::CreateNavigationEntryFromLoadParams( // Set the FTN ID (only used in non-site-per-process, for tests). entry->set_frame_tree_node_id(node->frame_tree_node_id()); - entry->set_should_replace_entry(should_replace_current_entry); entry->set_should_clear_history_list(params.should_clear_history_list); entry->SetIsOverridingUserAgent(override_user_agent); entry->set_has_user_gesture(has_user_gesture); @@ -3227,12 +3299,13 @@ NavigationControllerImpl::CreateNavigationRequestFromLoadParams( #if defined(OS_ANDROID) std::string(), /* data_url_as_string */ #endif - false, /* is_browser_initiated */ + !params.is_renderer_initiated, /* is_browser_initiated */ network::mojom::IPAddressSpace::kUnknown, GURL() /* web_bundle_physical_url */, GURL() /* base_url_override_for_web_bundle */, node->pending_frame_policy(), - std::vector<std::string>() /* force_enabled_origin_trials */); + std::vector<std::string>() /* force_enabled_origin_trials */, + false /* origin_isolation_restricted */); #if defined(OS_ANDROID) if (ValidateDataURLAsString(params.data_url_as_string)) { commit_params->data_url_as_string = params.data_url_as_string->data(); @@ -3268,6 +3341,7 @@ NavigationControllerImpl::CreateNavigationRequestFromEntry( ReloadType reload_type, bool is_same_document_history_load, bool is_history_navigation_in_new_child_frame) { + DCHECK(frame_entry); GURL dest_url = frame_entry->url(); base::Optional<url::Origin> origin_to_commit = frame_entry->committed_origin(); @@ -3430,10 +3504,16 @@ void NavigationControllerImpl::LoadPostCommitErrorPage( const GURL& url, const std::string& error_page_html, net::Error error) { - // A frame pending deletion is not allowed to navigate, the browser is already - // committed to destroying this frame so ignore loading the error page. - if (!static_cast<RenderFrameHostImpl*>(render_frame_host)->is_active()) + // Only active frames can load post-commit error pages: + // - If the frame is in pending deletion, the browser already committed to + // destroying this RenderFrameHost so ignore loading the error page. + // - If the frame is in back-forward cache, it's not allowed to navigate as it + // should remain frozen. Ignore the request and evict the document from + // back-forward cache. + if (static_cast<RenderFrameHostImpl*>(render_frame_host) + ->IsInactiveAndDisallowReactivation()) { return; + } FrameTreeNode* node = static_cast<RenderFrameHostImpl*>(render_frame_host)->frame_tree_node(); @@ -3444,6 +3524,11 @@ void NavigationControllerImpl::LoadPostCommitErrorPage( mojom::CommitNavigationParamsPtr commit_params = CreateCommitNavigationParams(); + // Error pages have a fully permissive FramePolicy. + // TODO(arthursonzogni): Consider providing the minimal capabilities to the + // error pages. + commit_params->frame_policy = blink::FramePolicy(); + std::unique_ptr<NavigationRequest> navigation_request = NavigationRequest::CreateBrowserInitiated( node, std::move(common_params), std::move(commit_params), @@ -3478,28 +3563,14 @@ void NavigationControllerImpl::DiscardNonCommittedEntries() { // Avoid sending a notification if there is nothing to discard. // TODO(mthiesse): Temporarily checking failed_pending_entry_id_ to help // diagnose https://bugs.chromium.org/p/chromium/issues/detail?id=1007570. - if (!pending_entry_ && transient_entry_index_ == -1 && - failed_pending_entry_id_ == 0) { + if (!pending_entry_ && failed_pending_entry_id_ == 0) { return; } - DiscardPendingEntry(false); - DiscardTransientEntry(); if (delegate_) delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_ALL); } -void NavigationControllerImpl::DiscardTransientEntry() { - if (transient_entry_index_ == -1) - return; - entries_.erase(entries_.begin() + transient_entry_index_); - if (last_committed_entry_index_ > transient_entry_index_) - last_committed_entry_index_--; - if (pending_entry_index_ > transient_entry_index_) - pending_entry_index_--; - transient_entry_index_ = -1; -} - int NavigationControllerImpl::GetEntryIndexWithUniqueID( int nav_entry_id) const { for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) { @@ -3509,27 +3580,6 @@ int NavigationControllerImpl::GetEntryIndexWithUniqueID( return -1; } -NavigationEntryImpl* NavigationControllerImpl::GetTransientEntry() { - if (transient_entry_index_ == -1) - return nullptr; - return entries_[transient_entry_index_].get(); -} - -void NavigationControllerImpl::SetTransientEntry( - std::unique_ptr<NavigationEntry> entry) { - // Discard any current transient entry, we can only have one at a time. - DiscardTransientEntry(); - int index = 0; - if (last_committed_entry_index_ != -1) - index = last_committed_entry_index_ + 1; - entries_.insert(entries_.begin() + index, - NavigationEntryImpl::FromNavigationEntry(std::move(entry))); - if (pending_entry_index_ >= index) - pending_entry_index_++; - transient_entry_index_ = index; - delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_ALL); -} - void NavigationControllerImpl::InsertEntriesFrom( NavigationControllerImpl* source, int max_index) { @@ -3639,8 +3689,6 @@ void NavigationControllerImpl::PendingEntryRefDeleted(PendingEntryRef* ref) { // Do not leave the pending entry visible if it has an invalid URL, since this // might be formatted in an unexpected or unsafe way. // TODO(creis): Block navigations to invalid URLs in https://crbug.com/850824. - // - // Note: don't touch the transient entry, since an interstitial may exist. bool should_preserve_entry = (pending_entry_ == GetVisibleEntry()) && pending_entry_->GetURL().is_valid() && diff --git a/chromium/content/browser/frame_host/navigation_controller_impl.h b/chromium/content/browser/frame_host/navigation_controller_impl.h index e7ec7e7e204..47e2c5a0e7b 100644 --- a/chromium/content/browser/frame_host/navigation_controller_impl.h +++ b/chromium/content/browser/frame_host/navigation_controller_impl.h @@ -93,8 +93,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { void DiscardNonCommittedEntries() override; NavigationEntryImpl* GetPendingEntry() override; int GetPendingEntryIndex() override; - NavigationEntryImpl* GetTransientEntry() override; - void SetTransientEntry(std::unique_ptr<NavigationEntry> entry) override; void LoadURL(const GURL& url, const Referrer& referrer, ui::PageTransition type, @@ -317,8 +315,8 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { // requests corresponding to the current pending entry. std::unique_ptr<PendingEntryRef> ReferencePendingEntry(); - // Like NavigationController::CreateNavigationEntry, but takes an extra - // |source_site_instance| argument. + // Like NavigationController::CreateNavigationEntry, but takes extra arguments + // like |source_site_instance| and |should_replace_entry|. static std::unique_ptr<NavigationEntryImpl> CreateNavigationEntry( const GURL& url, Referrer referrer, @@ -328,7 +326,8 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { bool is_renderer_initiated, const std::string& extra_headers, BrowserContext* browser_context, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory); + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + bool should_replace_entry); private: friend class RestoreHelper; @@ -337,6 +336,17 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { FRIEND_TEST_ALL_PREFIXES(TimeSmoother, SingleDuplicate); FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ManyDuplicates); FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ClockBackwardsJump); + FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest, + PostThenReplaceStateThenReload); + + // Defines possible actions that are returned by + // DetermineActionForHistoryNavigation(). + enum class HistoryNavigationAction { + kStopLooking, + kKeepLooking, + kSameDocument, + kDifferentDocument, + }; // Helper class to smooth out runs of duplicate timestamps while still // allowing time to jump backwards. @@ -352,6 +362,23 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { base::Time high_water_mark_; }; + // The repost dialog is suppressed during testing. However, it should be shown + // in some tests. This allows a test to elect to allow the repost dialog to + // show for a scoped duration. + class CONTENT_EXPORT ScopedShowRepostDialogForTesting { + public: + ScopedShowRepostDialogForTesting(); + ~ScopedShowRepostDialogForTesting(); + + ScopedShowRepostDialogForTesting(const ScopedShowRepostDialogForTesting&) = + delete; + ScopedShowRepostDialogForTesting& operator=( + const ScopedShowRepostDialogForTesting&) = delete; + + private: + const bool was_disallowed_; + }; + // Navigates in session history to the given index. If // |sandbox_frame_tree_node_id| is valid, then this request came // from a sandboxed iframe with top level navigation disallowed. This @@ -365,6 +392,13 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { void NavigateToExistingPendingEntry(ReloadType reload_type, int sandboxed_source_frame_tree_node_id); + // Helper function used by FindFramesToNavigate to determine the appropriate + // action to take for a particular frame while navigating to + // |pending_entry_|. + HistoryNavigationAction DetermineActionForHistoryNavigation( + FrameTreeNode* frame, + ReloadType reload_type); + // Recursively identifies which frames need to be navigated for a navigation // to |pending_entry_|, starting at |frame| and exploring its children. // |same_document_loads| and |different_document_loads| will be filled with @@ -507,9 +541,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { // Removes the entry at |index|, as long as it is not the current entry. void RemoveEntryAtIndexInternal(int index); - // Discards only the transient entry. - void DiscardTransientEntry(); - // If we have the maximum number of entries, remove the oldest entry that is // marked to be skipped on back/forward button, in preparation to add another. // If no entry is skippable, then the oldest entry will be pruned. @@ -524,8 +555,7 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { // Inserts up to |max_index| entries from |source| into this. This does NOT // adjust any of the members that reference entries_ - // (last_committed_entry_index_, pending_entry_index_ or - // transient_entry_index_). + // (last_committed_entry_index_ or pending_entry_index_) void InsertEntriesFrom(NavigationControllerImpl* source, int max_index); // Returns the navigation index that differs from the current entry by the @@ -603,13 +633,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { // pending_entry_ is a new entry (created by LoadURL). int pending_entry_index_ = -1; - // The index for the entry that is shown until a navigation occurs. This is - // used for interstitial pages. -1 if there are no such entry. - // Note that this entry really appears in the list of entries, but only - // temporarily (until the next navigation). Any index pointing to an entry - // after the transient entry will become invalid if you navigate forward. - int transient_entry_index_ = -1; - // The delegate associated with the controller. Possibly NULL during // setup. NavigationControllerDelegate* delegate_; diff --git a/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc index df0eef5387b..e6e3721a6eb 100644 --- a/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc +++ b/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc @@ -19,7 +19,6 @@ #include "base/sequenced_task_runner.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "base/task/post_task.h" #include "base/test/bind_test_util.h" #include "base/test/scoped_feature_list.h" #include "base/threading/sequenced_task_runner_handle.h" @@ -608,10 +607,27 @@ namespace { // current entry. bool RendererLocationReplace(Shell* shell, const GURL& url) { WebContents* web_contents = shell->web_contents(); + NavigationControllerImpl& controller = + static_cast<NavigationControllerImpl&>(web_contents->GetController()); WaitForLoadStop(web_contents); - TestNavigationObserver same_tab_observer(web_contents, 1); + TestNavigationManager navigation_manager(web_contents, url); + const GURL& current_url = web_contents->GetMainFrame()->GetLastCommittedURL(); EXPECT_TRUE(ExecJs(shell, JsReplace("window.location.replace($1)", url))); - same_tab_observer.Wait(); + // Observe pending entry if it's not a same-document navigation. We can't + // observe same-document navigations because it might finish in the renderer, + // only telling the browser side at the end. + if (!current_url.EqualsIgnoringRef(url)) { + EXPECT_TRUE(navigation_manager.WaitForRequestStart()); + // Both should_replace_entry (in the pending NavigationEntry) and + // should_replace_current_entry (in NavigationRequest params) should be + // true. + EXPECT_TRUE(controller.GetPendingEntry()->should_replace_entry()); + EXPECT_TRUE( + NavigationRequest::From(navigation_manager.GetNavigationHandle()) + ->common_params() + .should_replace_current_entry); + } + navigation_manager.WaitForNavigationFinished(); if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL)) return false; return web_contents->GetLastCommittedURL() == url; @@ -1086,8 +1102,8 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, ErrorPageReplacement) { NavigationController& controller = shell()->web_contents()->GetController(); GURL error_url = embedded_test_server()->GetURL("/close-socket"); - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); EXPECT_EQ(1, controller.GetEntryCount()); @@ -6686,15 +6702,15 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, PostInSubframe) { } // Tests that POST body is not lost when decidePolicyForNavigation tells the -// renderer to route the request via FrameHostMsg_OpenURL sent to the browser. +// renderer to route the request via OpenURL mojo method sent to the browser. // See also https://crbug.com/344348. IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, PostViaOpenUrlMsg) { GURL main_url( embedded_test_server()->GetURL("/form_that_posts_to_echoall.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); - // Ask the renderer to go through OpenURL FrameHostMsg_OpenURL IPC message. - // Without this, the test wouldn't repro https://crbug.com/344348. + // Ask the renderer to go through OpenURL Mojo method. Without this, the test + // wouldn't repro https://crbug.com/344348. shell() ->web_contents() ->GetMutableRendererPrefs() @@ -10597,4 +10613,571 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, } } +// Verify that if a history navigation only affects a subframe that was +// removed, the main frame should not be reloaded. See +// httos://crbug.com/705550. This test checks the case where the attempted +// subframe navigation was same-document. +// +// TODO(alexmos, creis): Consider changing this behavior to auto-traverse +// history to the first entry which finds a frame to navigate. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + GoBackSameDocumentInRemovedSubframe) { + GURL main_url = embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b,c)"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + + NavigationControllerImpl& controller = contents()->GetController(); + ASSERT_EQ(1, controller.GetEntryCount()); + + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = ftn_a->child_at(0); + FrameTreeNode* ftn_c = ftn_a->child_at(1); + + // Set some state in the main frame that we can check later to make sure it + // wasn't reloaded. + EXPECT_TRUE(ExecJs(ftn_a, "window.state = 'a';")); + + // history.pushState() in the main frame. + GURL ps2_url(embedded_test_server()->GetURL("a.com", "/ps2.html")); + { + FrameNavigateParamsCapturer capturer(ftn_a); + ASSERT_TRUE( + ExecuteScript(ftn_a, "history.pushState({}, 'page 2', 'ps2.html')")); + capturer.Wait(); + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(ps2_url, controller.GetLastCommittedEntry()->GetURL()); + } + + // history.pushState() twice in the c subframe. + { + FrameNavigateParamsCapturer capturer(ftn_c); + ASSERT_TRUE( + ExecuteScript(ftn_c, "history.pushState({}, 'page 3', 'ps3.html')")); + capturer.Wait(); + EXPECT_EQ(3, controller.GetEntryCount()); + } + { + FrameNavigateParamsCapturer capturer(ftn_c); + ASSERT_TRUE( + ExecuteScript(ftn_c, "history.pushState({}, 'page 4', 'ps4.html')")); + capturer.Wait(); + EXPECT_EQ(4, controller.GetEntryCount()); + } + + // Navigate frame b cross-document. + GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_b)); + navigation_observer.WaitForNavigationFinished(); + EXPECT_EQ(5, controller.GetEntryCount()); + + // Go back. This navigates frame b back, and we should be at next-to-last + // entry (of 5 total) after that's done. + EXPECT_EQ(4, controller.GetCurrentEntryIndex()); + { + TestNavigationObserver navigation_observer(shell()->web_contents()); + shell()->GoBackOrForward(-1); + navigation_observer.Wait(); + } + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(3, controller.GetCurrentEntryIndex()); + + // Last committed entry's URL should be ps2.html, corresponding to latest + // navigation in the main frame. + EXPECT_EQ(ps2_url, controller.GetLastCommittedEntry()->GetURL()); + + // Set some state in frame b that we can check later to make sure it wasn't + // reloaded. + EXPECT_TRUE(ExecJs(ftn_b, "window.state='b';")); + + // Remove the c subframe. + RenderFrameDeletedObserver deleted_observer(ftn_c->current_frame_host()); + EXPECT_TRUE(ExecJs(ftn_a, + "var f = document.querySelectorAll('iframe')[1];" + "f.parentNode.removeChild(f);")); + deleted_observer.WaitUntilDeleted(); + + // Try going back. The target of this navigation had been a same-document + // navigation in subframe c (to ps3.html), but since c has been removed, this + // shouldn't reload frames a or b. Frame |a| also shouldn't fire redundant + // popstate events. + EXPECT_TRUE(ExecJs(ftn_a, "window.popstateCalled = false")); + EXPECT_TRUE(ExecJs( + ftn_a, "window.onpopstate = () => { window.popstateCalled = true; }")); + shell()->GoBackOrForward(-1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(ps2_url, controller.GetLastCommittedEntry()->GetURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ("b", EvalJs(ftn_b, "window.state")); + EXPECT_EQ(false, EvalJs(ftn_a, "window.popstateCalled")); + + // The corresponding NavigationEntry should now be the current one. + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + + // Try going back again. Similarly, this would've gone back to c's original + // URL when it was loaded from main_url, but since c is removed, this + // shouldn't reload frames a or b or fire popstate events. + shell()->GoBackOrForward(-1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(ps2_url, controller.GetLastCommittedEntry()->GetURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ("b", EvalJs(ftn_b, "window.state")); + EXPECT_EQ(false, EvalJs(ftn_a, "window.popstateCalled")); + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + + // Going back now should result in a same-document navigation in the main + // frame to main_url. + shell()->GoBackOrForward(-1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(main_url, controller.GetLastCommittedEntry()->GetURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ("b", EvalJs(ftn_b, "window.state")); + EXPECT_EQ(true, EvalJs(ftn_a, "window.popstateCalled")); + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(0, controller.GetCurrentEntryIndex()); + + // Go forward. This should navigate the main frame same-document to + // ps2.html. + { + FrameNavigateParamsCapturer capturer(ftn_a); + shell()->GoBackOrForward(1); + capturer.Wait(); + } + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + EXPECT_EQ(ps2_url, ftn_a->current_frame_host()->GetLastCommittedURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ("b", EvalJs(ftn_b, "window.state")); + + // Go forward three steps. This should navigate the subframe b + // cross-document to url_b (erasing its window.state). + { + FrameNavigateParamsCapturer capturer(ftn_b); + shell()->GoBackOrForward(3); + capturer.Wait(); + } + EXPECT_EQ(url_b, ftn_b->current_frame_host()->GetLastCommittedURL()); + EXPECT_EQ(5, controller.GetEntryCount()); + EXPECT_EQ(4, controller.GetCurrentEntryIndex()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(nullptr, EvalJs(ftn_b, "window.state")); +} + +// Verify that if a history navigation only affects a subframe that was +// removed, the main frame should not be reloaded. See +// httos://crbug.com/705550. This test is similar to the one above, but checks +// the case where the attempted subframe navigation was cross-document rather +// than same-document. +// +// TODO(alexmos, creis): Consider changing this behavior to auto-traverse +// history to the first entry which finds a frame to navigate. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + GoBackCrossDocumentInRemovedSubframe) { + GURL main_url = embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b)"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + + NavigationControllerImpl& controller = contents()->GetController(); + ASSERT_EQ(1, controller.GetEntryCount()); + + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = ftn_a->child_at(0); + + // Set some state in the main frame that we can check later to make sure it + // wasn't reloaded. + EXPECT_TRUE(ExecJs(ftn_a, "window.state = 'a';")); + + // Navigate frame b cross-document. + GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_b)); + navigation_observer.WaitForNavigationFinished(); + } + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + + // Delete frame b. + RenderFrameDeletedObserver deleted_observer( + ftn_a->child_at(0)->current_frame_host()); + EXPECT_TRUE(ExecJs(ftn_a, + "var f = document.querySelector('iframe');" + "f.parentNode.removeChild(f);")); + deleted_observer.WaitUntilDeleted(); + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + + // Go back. Since this history navigation targets a non-existent subframe, + // the main frame shouldn't be reloaded, and the corresponding + // NavigationEntry should become the current one. + shell()->GoBackOrForward(-1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(main_url, controller.GetLastCommittedEntry()->GetURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(0, controller.GetCurrentEntryIndex()); + EXPECT_FALSE(controller.CanGoBack()); + EXPECT_TRUE(controller.CanGoForward()); + + // Go forward and expect similar behavior. + shell()->GoBackOrForward(1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(main_url, controller.GetLastCommittedEntry()->GetURL()); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + EXPECT_TRUE(controller.CanGoBack()); + EXPECT_FALSE(controller.CanGoForward()); +} + +// This test is similar to the one above, but checks the case where the first +// attempted navigation after subframe removal is a forward navigation +// rather than a back navigation. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + GoForwardCrossDocumentInRemovedSubframe) { + GURL main_url = embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b)"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + + NavigationControllerImpl& controller = contents()->GetController(); + ASSERT_EQ(1, controller.GetEntryCount()); + + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = contents()->GetFrameTree()->root()->child_at(0); + GURL orig_subframe_url(ftn_b->current_frame_host()->GetLastCommittedURL()); + + // Set some state in the main frame that we can check later to make sure it + // wasn't reloaded. + EXPECT_TRUE(ExecJs(ftn_a, "window.state = 'a';")); + + // Navigate frame b cross-document twice. + GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_b)); + navigation_observer.WaitForNavigationFinished(); + } + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + GURL url_c(embedded_test_server()->GetURL("c.com", "/title2.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_c)); + navigation_observer.WaitForNavigationFinished(); + } + EXPECT_EQ(3, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + + // history.pushState() in the main frame. + GURL ps_url(embedded_test_server()->GetURL("a.com", "/ps.html")); + { + FrameNavigateParamsCapturer capturer(ftn_a); + ASSERT_TRUE( + ExecuteScript(ftn_a, "history.pushState({}, 'push state', 'ps.html')")); + capturer.Wait(); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(ps_url, controller.GetLastCommittedEntry()->GetURL()); + } + + // Go back three stops, bringing back the original URL in the subframe. + // (Note that going back by three steps all at once won't work as expected + // due to https://crbug.com/542299, where finding that the main frame needs + // to navigate same-document ignores any subframe navigations that should + // also be part of the history entry navigation.) + shell()->GoBackOrForward(-1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + EXPECT_EQ(main_url, controller.GetLastCommittedEntry()->GetURL()); + shell()->GoBackOrForward(-2); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(0, controller.GetCurrentEntryIndex()); + EXPECT_EQ(orig_subframe_url, + ftn_b->current_frame_host()->GetLastCommittedURL()); + + // Delete frame b. + RenderFrameDeletedObserver deleted_observer( + ftn_a->child_at(0)->current_frame_host()); + EXPECT_TRUE(ExecJs(ftn_a, + "var f = document.querySelector('iframe');" + "f.parentNode.removeChild(f);")); + deleted_observer.WaitUntilDeleted(); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(0, controller.GetCurrentEntryIndex()); + + // Go forward. Since this navigation attempt targets a non-existent + // subframe, the main frame shouldn't be reloaded, and the corresponding + // NavigationEntry should become current. There should be no redundant + // popstate events. + EXPECT_TRUE(ExecJs(ftn_a, "window.popstateCalled = false")); + EXPECT_TRUE(ExecJs( + ftn_a, "window.onpopstate = () => { window.popstateCalled = true; }")); + shell()->GoBackOrForward(1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(false, EvalJs(ftn_a, "window.popstateCalled")); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + + // Go forward again and expect similar behavior. + shell()->GoBackOrForward(1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(false, EvalJs(ftn_a, "window.popstateCalled")); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + + // Go forward yet again. This should result in a same-document navigation in + // the main frame to ps.html. + shell()->GoBackOrForward(1); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(true, EvalJs(ftn_a, "window.popstateCalled")); + EXPECT_EQ(4, controller.GetEntryCount()); + EXPECT_EQ(3, controller.GetCurrentEntryIndex()); + EXPECT_TRUE(controller.CanGoBack()); + EXPECT_FALSE(controller.CanGoForward()); + EXPECT_EQ(ps_url, controller.GetLastCommittedEntry()->GetURL()); +} + +// Check that if we ignore a history entry that targets a removed subframe, the +// entry still stays around and is used properly when the subframe gets +// recreated. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + RestoreRemovedSubframe) { + // Start on a page with a same-site iframe. It's important that this iframe + // isn't dynamically inserted for history navigations in this test. + GURL main_url = + embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + NavigationControllerImpl& controller = contents()->GetController(); + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = ftn_a->child_at(0); + EXPECT_EQ(embedded_test_server()->GetURL("a.com", "/title1.html"), + ftn_b->current_frame_host()->GetLastCommittedURL()); + + // Set some state in the main frame that we can check later to make sure it + // wasn't reloaded. + EXPECT_TRUE(ExecJs(ftn_a, "window.state = 'a';")); + + // Navigate subframe cross-site twice. + GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_b)); + navigation_observer.WaitForNavigationFinished(); + } + + GURL url_c(embedded_test_server()->GetURL("c.com", "/title3.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_c)); + navigation_observer.WaitForNavigationFinished(); + } + EXPECT_EQ(3, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + + // Remove subframe. + RenderFrameDeletedObserver deleted_observer( + ftn_a->child_at(0)->current_frame_host()); + EXPECT_TRUE(ExecJs(ftn_a, + "var f = document.querySelector('iframe');" + "f.parentNode.removeChild(f);")); + deleted_observer.WaitUntilDeleted(); + + // Go back. This normally attempts to navigate the subframe from url_c to + // url_b, but the subframe no longer exists. Check that the main frame isn't + // reloaded. + controller.GoBack(); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ("a", EvalJs(ftn_a, "window.state")); + EXPECT_EQ(3, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + + // Navigate main frame to another url. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/title2.html"))); + EXPECT_EQ(3, controller.GetEntryCount()); + EXPECT_EQ(2, controller.GetCurrentEntryIndex()); + + // Now navigate back. This should go back to |main_url|, reloading the + // subframe at |url_b|, which is its URL in the history entry that we ignored + // during the last back navigation above. + controller.GoBack(); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(3, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + EXPECT_EQ(url_b, + ftn_a->child_at(0)->current_frame_host()->GetLastCommittedURL()); +} + +// Check that when we go back in a subframe on a page that contains another +// frame which is crashed, we not only go back in the subframe but also reload +// the sad frame. This restores restore the state covered by the corresponding +// NavigationEntry more faithfully. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + ReloadSadFrameWithSubframeHistoryNavigation) { + // Ensure this test runs in full site-per-process mode so that we can get a + // sad frame on Android. + IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); + + // Start on a page with two iframes. + GURL main_url = embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b,c)"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + NavigationControllerImpl& controller = contents()->GetController(); + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = ftn_a->child_at(0); + FrameTreeNode* ftn_c = ftn_a->child_at(1); + GURL url_b(ftn_b->current_frame_host()->GetLastCommittedURL()); + GURL url_c(ftn_c->current_frame_host()->GetLastCommittedURL()); + + // Navigate first subframe cross-site. + GURL url_d(embedded_test_server()->GetURL("d.com", "/title2.html")); + { + TestNavigationObserver navigation_observer(contents()); + EXPECT_TRUE(NavigateFrameToURL(ftn_b, url_d)); + navigation_observer.WaitForNavigationFinished(); + } + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(1, controller.GetCurrentEntryIndex()); + EXPECT_EQ(url_d, ftn_b->current_frame_host()->GetLastCommittedURL()); + + // Crash second subframe. + RenderProcessHost* process_c = ftn_c->current_frame_host()->GetProcess(); + RenderProcessHostWatcher crash_observer( + process_c, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + process_c->Shutdown(0); + crash_observer.Wait(); + EXPECT_FALSE(ftn_c->current_frame_host()->IsRenderFrameLive()); + EXPECT_TRUE(ftn_c->current_frame_host()->GetLastCommittedURL().is_empty()); + + // Go back. This should navigate the first subframe back to b.com, and it + // should also restore the subframe in c.com. + controller.GoBack(); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ(2, controller.GetEntryCount()); + EXPECT_EQ(0, controller.GetCurrentEntryIndex()); + EXPECT_EQ(url_b, ftn_b->current_frame_host()->GetLastCommittedURL()); + EXPECT_EQ(url_c, ftn_c->current_frame_host()->GetLastCommittedURL()); + EXPECT_TRUE(ftn_c->current_frame_host()->IsRenderFrameLive()); +} + +// Regression test for https://crbug.com/1088354, where a different-document +// load was incorrectly scheduled for a history navigation in a subframe that +// had no existing and no target FrameNavigationEntry. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + SubframeGoesBackAndSiblingHasNoFrameEntry) { + // Start on a page with a same-site iframe. + GURL main_url = + embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + NavigationControllerImpl& controller = contents()->GetController(); + FrameTreeNode* ftn_a = contents()->GetFrameTree()->root(); + FrameTreeNode* ftn_b = ftn_a->child_at(0); + EXPECT_EQ(embedded_test_server()->GetURL("a.com", "/title1.html"), + ftn_b->current_frame_host()->GetLastCommittedURL()); + + // Add a second subframe dynamically and set some state on it to ensure it's + // not reloaded. Using a javascript: URL results in the renderer not sending + // a DidCommitNavigation IPC back for the new frame, leaving it without a + // FrameNavigationEntry. + EXPECT_TRUE(ExecJs(ftn_a, + "var f = document.createElement('iframe');" + "f.src = 'javascript:void(0)';" + "document.body.appendChild(f);")); + FrameTreeNode* ftn_c = ftn_a->child_at(1); + EXPECT_TRUE(ExecJs(ftn_c, "window.state='c';")); + + // Navigate first subframe same-document. + { + FrameNavigateParamsCapturer capturer(ftn_b); + EXPECT_TRUE(ExecJs(ftn_b, "location.hash = 'foo'")); + capturer.Wait(); + EXPECT_TRUE(capturer.is_same_document()); + } + + // Go back in the first subframe. This should navigate the first subframe + // back same-document, while the second subframe shouldn't be reloaded, and + // the history navigation shouldn't crash while processing it. + { + FrameNavigateParamsCapturer capturer(ftn_b); + controller.GoBack(); + capturer.Wait(); + EXPECT_TRUE(capturer.is_same_document()); + EXPECT_TRUE(WaitForLoadStop(contents())); + EXPECT_EQ("c", EvalJs(ftn_c, "window.state")); + } +} + +// Checks that a browser-initiated same-document navigation on a page which has +// a valid base URL preserves the base URL. +// See https://crbug.com/1082141. +IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, + LoadDataWithBaseURLSameDocumentNavigation) { + // LoadDataWithBaseURL is never subject to --site-per-process policy today + // (this API is only used by Android WebView [where OOPIFs have not shipped + // yet] and GuestView cases [which always hosts guests inside a renderer + // without an origin lock]). Therefore, skip the test in --site-per-process + // mode to avoid renderer kills which won't happen in practice as described + // above. + // + // TODO(https://crbug.com/962643): Consider enabling this test once Android + // Webview or WebView guests support OOPIFs and/or origin locks. + if (AreAllSitesIsolatedForTesting()) + return; + + const GURL base_url("http://baseurl"); + const GURL history_url("http://history"); + const std::string data = "<html><title>One</title><body>foo</body></html>"; + const GURL data_url = GURL("data:text/html;charset=utf-8," + data); + + NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( + shell()->web_contents()->GetController()); + + { + TestNavigationObserver same_tab_observer(shell()->web_contents(), 1); + shell()->LoadDataWithBaseURL(history_url, data, base_url); + same_tab_observer.Wait(); + } + + // Verify the last committed NavigationEntry. + NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); + EXPECT_EQ(base_url, entry->GetBaseURLForDataURL()); + EXPECT_EQ(history_url, entry->GetVirtualURL()); + EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL()); + EXPECT_EQ(data_url, entry->GetURL()); + + { + // Make a same-document navigation via history.pushState. + TestNavigationObserver same_tab_observer(shell()->web_contents(), 1); + EXPECT_TRUE(ExecuteScript(shell(), "history.pushState('', 'test', '#')")); + same_tab_observer.Wait(); + } + + // Verify the last committed NavigationEntry. + entry = controller.GetLastCommittedEntry(); + EXPECT_EQ(base_url, entry->GetBaseURLForDataURL()); + EXPECT_EQ(history_url, entry->GetVirtualURL()); + EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL()); + EXPECT_EQ(data_url, entry->GetURL()); + + { + // Go back. + TestNavigationObserver back_load_observer(shell()->web_contents()); + controller.GoBack(); + back_load_observer.Wait(); + } + + // Verify the last committed NavigationEntry. + entry = controller.GetLastCommittedEntry(); + EXPECT_EQ(base_url, entry->GetBaseURLForDataURL()); + EXPECT_EQ(history_url, entry->GetVirtualURL()); + EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL()); + EXPECT_EQ(data_url, entry->GetURL()); + EXPECT_EQ(base_url, EvalJs(shell(), "document.URL")); +} + } // namespace content diff --git a/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc b/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc index ea263eebfa0..2df43b5f120 100644 --- a/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc +++ b/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc @@ -2573,269 +2573,6 @@ TEST_F(NavigationControllerTest, RemoveEntryWithPending) { EXPECT_FALSE(controller.GetPendingEntry()); } -// Tests the transient entry, making sure it goes away with all navigations. -TEST_F(NavigationControllerTest, TransientEntry) { - NavigationControllerImpl& controller = controller_impl(); - - const GURL url0("http://foo/0"); - const GURL url1("http://foo/1"); - const GURL url2("http://foo/2"); - const GURL url3("http://foo/3"); - const GURL url3_ref("http://foo/3#bar"); - const GURL url4("http://foo/4"); - const GURL transient_url("http://foo/transient"); - - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url0); - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url1); - - navigation_entry_changed_counter_ = 0; - navigation_list_pruned_counter_ = 0; - - // Adding a transient with no pending entry. - std::unique_ptr<NavigationEntry> transient_entry(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - - // We should not have received any notifications. - EXPECT_EQ(0U, navigation_entry_changed_counter_); - EXPECT_EQ(0U, navigation_list_pruned_counter_); - - // Check our state. - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 3); - EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1); - EXPECT_EQ(controller.GetPendingEntryIndex(), -1); - EXPECT_TRUE(controller.GetLastCommittedEntry()); - EXPECT_FALSE(controller.GetPendingEntry()); - EXPECT_TRUE(controller.CanGoBack()); - EXPECT_FALSE(controller.CanGoForward()); - - // Navigate. - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2); - - // We should have navigated, transient entry should be gone. - EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 3); - - // Add a transient again, then navigate with no pending entry this time. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - NavigationSimulator::NavigateAndCommitFromDocument(url3, main_test_rfh()); - // Transient entry should be gone. - EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 4); - - // Initiate a navigation, add a transient then commit navigation. - auto navigation = - NavigationSimulator::CreateBrowserInitiated(url4, contents()); - navigation->Start(); - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - navigation->Commit(); - EXPECT_EQ(url4, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 5); - - // Add a transient and go back. This should simply remove the transient. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_TRUE(controller.CanGoBack()); - EXPECT_FALSE(controller.CanGoForward()); - controller.GoBack(); - // Transient entry should be gone. - EXPECT_EQ(url4, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 5); - - // Suppose the page requested a history navigation backward. - NavigationSimulator::GoBack(contents()); - - // Add a transient and go to an entry before the current one. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - controller.GoToIndex(1); - auto history_navigation1 = NavigationSimulator::CreateFromPending(contents()); - // The navigation should have been initiated, transient entry should be gone. - EXPECT_FALSE(controller.GetTransientEntry()); - EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL()); - // Visible entry does not update for history navigations until commit. - EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL()); - history_navigation1->Commit(); - EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL()); - - // Add a transient and go to an entry after the current one. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - controller.GoToIndex(3); - auto history_navigation2 = NavigationSimulator::CreateFromPending(contents()); - // The navigation should have been initiated, transient entry should be gone. - // Because of the transient entry that is removed, going to index 3 makes us - // land on url2 (which is visible after the commit). - EXPECT_EQ(url2, controller.GetPendingEntry()->GetURL()); - EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL()); - history_navigation2->Commit(); - EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL()); - - // Add a transient and go forward. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_TRUE(controller.CanGoForward()); - auto forward_navigation = - NavigationSimulator::CreateHistoryNavigation(1, contents()); - forward_navigation->Start(); - // We should have navigated, transient entry should be gone. - EXPECT_FALSE(controller.GetTransientEntry()); - EXPECT_EQ(url3, controller.GetPendingEntry()->GetURL()); - EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL()); - forward_navigation->Commit(); - EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL()); - - // Add a transient and do an in-page navigation, replacing the current entry. - transient_entry.reset(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - - main_test_rfh()->SendNavigate(0, false, url3_ref); - // Transient entry should be gone. - EXPECT_FALSE(controller.GetTransientEntry()); - EXPECT_EQ(url3_ref, controller.GetVisibleEntry()->GetURL()); - - // Ensure the URLs are correct. - EXPECT_EQ(controller.GetEntryCount(), 5); - EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0); - EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), url1); - EXPECT_EQ(controller.GetEntryAtIndex(2)->GetURL(), url2); - EXPECT_EQ(controller.GetEntryAtIndex(3)->GetURL(), url3_ref); - EXPECT_EQ(controller.GetEntryAtIndex(4)->GetURL(), url4); -} - -// Test that RemoveEntryAtIndex can handle an index that refers to a transient -// entry. -TEST_F(NavigationControllerTest, RemoveTransientByIndex) { - NavigationControllerImpl& controller = controller_impl(); - const GURL url0("http://foo/0"); - const GURL transient_url("http://foo/transient"); - - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url0); - - std::unique_ptr<NavigationEntry> transient_entry = - std::make_unique<NavigationEntryImpl>(); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 2); - EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0); - EXPECT_TRUE(controller.GetTransientEntry()); - EXPECT_EQ(controller.GetTransientEntry(), controller.GetEntryAtIndex(1)); - - EXPECT_TRUE(controller.RemoveEntryAtIndex(1)); - - EXPECT_EQ(controller.GetEntryCount(), 1); - EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0); - EXPECT_FALSE(controller.GetTransientEntry()); -} - -// Test that Reload initiates a new navigation to a transient entry's URL. -TEST_F(NavigationControllerTest, ReloadTransient) { - NavigationControllerImpl& controller = controller_impl(); - const GURL url0("http://foo/0"); - const GURL url1("http://foo/1"); - const GURL transient_url("http://foo/transient"); - - // Load |url0|, and start a pending navigation to |url1|. - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url0); - auto navigation = - NavigationSimulator::CreateBrowserInitiated(url1, contents()); - navigation->Start(); - - // A transient entry is added, interrupting the navigation. - std::unique_ptr<NavigationEntry> transient_entry(new NavigationEntryImpl); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - EXPECT_TRUE(controller.GetTransientEntry()); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - - // The page is reloaded, which should remove the pending entry for |url1| and - // the transient entry for |transient_url|, and start a navigation to - // |transient_url|. - controller.Reload(ReloadType::NORMAL, true); - EXPECT_FALSE(controller.GetTransientEntry()); - EXPECT_TRUE(controller.GetPendingEntry()); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - ASSERT_EQ(controller.GetEntryCount(), 1); - EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0); - - // Load of |transient_url| completes. - auto reload = NavigationSimulator::CreateFromPending(contents()); - reload->Commit(); - ASSERT_EQ(controller.GetEntryCount(), 2); - EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0); - EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), transient_url); -} - -// Ensure that adding a transient entry works when history is full. -TEST_F(NavigationControllerTest, TransientEntryWithFullHistory) { - NavigationControllerImpl& controller = controller_impl(); - - const GURL url0("http://foo/0"); - const GURL url1("http://foo/1"); - const GURL url2("http://foo/2"); - const GURL transient_url("http://foo/transient"); - - // Maximum count should be at least 2 or we will not be able to perform - // another navigation, since it would need to prune the last committed entry - // which is not safe. - controller.set_max_entry_count_for_testing(2); - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url0); - NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url1); - - // Add a transient entry beyond entry count limit. - auto transient_entry = std::make_unique<NavigationEntryImpl>(); - transient_entry->SetURL(transient_url); - controller.SetTransientEntry(std::move(transient_entry)); - - // Check our state. - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 3); - EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1); - EXPECT_EQ(controller.GetPendingEntryIndex(), -1); - EXPECT_TRUE(controller.GetLastCommittedEntry()); - EXPECT_FALSE(controller.GetPendingEntry()); - EXPECT_TRUE(controller.CanGoBack()); - EXPECT_FALSE(controller.CanGoForward()); - - // Go back, removing the transient entry. - controller.GoBack(); - EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 2); - - // Initiate a navigation, then add a transient entry with the pending entry - // present. - auto navigation = - NavigationSimulator::CreateBrowserInitiated(url2, contents()); - navigation->Start(); - auto another_transient = std::make_unique<NavigationEntryImpl>(); - another_transient->SetURL(transient_url); - controller.SetTransientEntry(std::move(another_transient)); - EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 3); - navigation->Commit(); - EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL()); - EXPECT_EQ(controller.GetEntryCount(), 2); -} - // Ensure that renderer initiated pending entries get replaced, so that we // don't show a stale virtual URL when a navigation commits. // See http://crbug.com/266922. @@ -2991,8 +2728,8 @@ TEST_F(NavigationControllerTest, ShowBrowserURLAfterFailUntilModified) { // Suppose it aborts before committing, if it's a 204 or download or due to a // stop or a new navigation from the user. The URL should remain visible. - static_cast<Navigator*>(main_test_rfh()->frame_tree_node()->navigator()) - ->CancelNavigation(main_test_rfh()->frame_tree_node()); + main_test_rfh()->frame_tree_node()->navigator().CancelNavigation( + main_test_rfh()->frame_tree_node()); EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL()); // If something else later modifies the contents of the about:blank page, then @@ -3350,25 +3087,6 @@ TEST_F(NavigationControllerTest, CloneAndReload) { EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex()); } -// Make sure that cloning a WebContentsImpl doesn't copy interstitials. -TEST_F(NavigationControllerTest, CloneOmitsInterstitials) { - NavigationControllerImpl& controller = controller_impl(); - const GURL url1("http://foo1"); - const GURL url2("http://foo2"); - - NavigateAndCommit(url1); - NavigateAndCommit(url2); - - // Add an interstitial entry. Should be deleted with controller. - NavigationEntryImpl* interstitial_entry = new NavigationEntryImpl(); - interstitial_entry->set_page_type(PAGE_TYPE_INTERSTITIAL); - controller.SetTransientEntry(base::WrapUnique(interstitial_entry)); - - std::unique_ptr<WebContents> clone(controller.GetWebContents()->Clone()); - - ASSERT_EQ(2, clone->GetController().GetEntryCount()); -} - // Test requesting and triggering a lazy reload. TEST_F(NavigationControllerTest, LazyReload) { NavigationControllerImpl& controller = controller_impl(); @@ -4361,38 +4079,36 @@ TEST_F(NavigationControllerTest, PostThenReplaceStateThenReload) { contents()->SetDelegate(delegate.get()); // Submit a form. - GURL url("http://foo"); - FrameHostMsg_DidCommitProvisionalLoad_Params params; - params.nav_entry_id = 0; - params.did_create_new_entry = true; - params.url = url; - params.origin = url::Origin::Create(url); - params.transition = ui::PAGE_TRANSITION_FORM_SUBMIT; - params.gesture = NavigationGestureUser; - params.page_state = PageState::CreateFromURL(url); - params.method = "POST"; - params.post_id = 2; - main_test_rfh()->SendRendererInitiatedNavigationRequest(url, false); - main_test_rfh()->PrepareForCommit(); - contents()->GetMainFrame()->SendNavigateWithParams(¶ms, false); + auto simulator = NavigationSimulatorImpl::CreateRendererInitiated( + GURL("http://foo"), main_test_rfh()); + simulator->SetIsFormSubmission(true); + simulator->SetIsPostWithId(123); + simulator->Commit(); + + // Now reload. We should show repost warning dialog. + { + NavigationControllerImpl::ScopedShowRepostDialogForTesting show_repost; + controller_impl().Reload(ReloadType::NORMAL, true); + } + const int expected_repost_form_warning_count = 1; + EXPECT_EQ(expected_repost_form_warning_count, + delegate->repost_form_warning_count()); // history.replaceState() is called. GURL replace_url("http://foo#foo"); - params.nav_entry_id = 0; - params.did_create_new_entry = false; - params.url = replace_url; - params.origin = url::Origin::Create(replace_url); - params.transition = ui::PAGE_TRANSITION_LINK; - params.gesture = NavigationGestureUser; - params.page_state = PageState::CreateFromURL(replace_url); - params.method = "GET"; - params.post_id = -1; - contents()->GetMainFrame()->SendNavigateWithParams(¶ms, true); + simulator = NavigationSimulatorImpl::CreateRendererInitiated( + GURL("http://foo#foo"), main_test_rfh()); + simulator->set_did_create_new_entry(false); + simulator->Commit(); // Now reload. replaceState overrides the POST, so we should not show a // repost warning dialog. - controller_impl().Reload(ReloadType::NORMAL, true); - EXPECT_EQ(0, delegate->repost_form_warning_count()); + { + NavigationControllerImpl::ScopedShowRepostDialogForTesting show_repost; + controller_impl().Reload(ReloadType::NORMAL, true); + } + EXPECT_EQ(expected_repost_form_warning_count, + delegate->repost_form_warning_count()); } TEST_F(NavigationControllerTest, UnreachableURLGivesErrorPage) { @@ -4409,6 +4125,7 @@ TEST_F(NavigationControllerTest, UnreachableURLGivesErrorPage) { params.method = "POST"; params.post_id = 2; params.url_is_unreachable = true; + params.embedding_token = base::UnguessableToken::Create(); // Navigate to new page. { @@ -4452,6 +4169,7 @@ TEST_F(NavigationControllerTest, UnreachableURLGivesErrorPage) { // Navigate without changing document. params.url = GURL("http://foo#foo"); params.transition = ui::PAGE_TRANSITION_LINK; + params.embedding_token = base::nullopt; { LoadCommittedDetailsObserver observer(contents()); main_test_rfh()->SendNavigateWithParams(¶ms, true); @@ -4521,84 +4239,6 @@ TEST_F(NavigationControllerTest, StaleNavigationsResurrected) { EXPECT_EQ(url_b, controller.GetEntryAtIndex(2)->GetURL()); } -// Test to ensure that the pending entry index is updated when a transient entry -// is inserted or removed. -TEST_F(NavigationControllerTest, PendingEntryIndexUpdatedWithTransient) { - NavigationControllerImpl& controller = controller_impl(); - const GURL url_0("http://foo/0"); - const GURL url_1("http://foo/1"); - const GURL url_transient_1("http://foo/transient_1"); - const GURL url_transient_2("http://foo/transient_2"); - - NavigateAndCommit(url_0); - NavigateAndCommit(url_1); - controller.GoBack(); - contents()->CommitPendingNavigation(); - controller.GoForward(); - - // Check the state before the insertion of the transient entry. - // entries[0] = url_0 <- last committed entry. - // entries[1] = url_1 <- pending entry. - ASSERT_EQ(2, controller.GetEntryCount()); - EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); - EXPECT_EQ(1, controller.GetPendingEntryIndex()); - EXPECT_EQ(controller.GetEntryAtIndex(1), controller.GetPendingEntry()); - EXPECT_EQ(url_0, controller.GetEntryAtIndex(0)->GetURL()); - EXPECT_EQ(url_1, controller.GetEntryAtIndex(1)->GetURL()); - - // Insert a transient entry before the pending one. It should increase the - // pending entry index by one (1 -> 2). - std::unique_ptr<NavigationEntry> transient_entry_1(new NavigationEntryImpl); - transient_entry_1->SetURL(url_transient_1); - controller.SetTransientEntry(std::move(transient_entry_1)); - - // Check the state after the insertion of the transient entry. - // entries[0] = url_0 <- last committed entry - // entries[1] = url_transient_1 <- transient entry - // entries[2] = url_1 <- pending entry - ASSERT_EQ(3, controller.GetEntryCount()); - EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); - EXPECT_EQ(2, controller.GetPendingEntryIndex()); - EXPECT_EQ(controller.GetEntryAtIndex(1), controller.GetTransientEntry()); - EXPECT_EQ(controller.GetEntryAtIndex(2), controller.GetPendingEntry()); - EXPECT_EQ(url_0, controller.GetEntryAtIndex(0)->GetURL()); - EXPECT_EQ(url_transient_1, controller.GetEntryAtIndex(1)->GetURL()); - EXPECT_EQ(url_1, controller.GetEntryAtIndex(2)->GetURL()); - - // Insert another transient entry. It should replace the previous one and this - // time the pending entry index should retain its value (i.e. 2). - std::unique_ptr<NavigationEntry> transient_entry_2(new NavigationEntryImpl); - transient_entry_2->SetURL(url_transient_2); - controller.SetTransientEntry(std::move(transient_entry_2)); - - // Check the state after the second insertion of a transient entry. - // entries[0] = url_0 <- last committed entry - // entries[1] = url_transient_2 <- transient entry - // entries[2] = url_1 <- pending entry - ASSERT_EQ(3, controller.GetEntryCount()); - EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); - EXPECT_EQ(2, controller.GetPendingEntryIndex()); - EXPECT_EQ(controller.GetEntryAtIndex(1), controller.GetTransientEntry()); - EXPECT_EQ(controller.GetEntryAtIndex(2), controller.GetPendingEntry()); - EXPECT_EQ(url_0, controller.GetEntryAtIndex(0)->GetURL()); - EXPECT_EQ(url_transient_2, controller.GetEntryAtIndex(1)->GetURL()); - EXPECT_EQ(url_1, controller.GetEntryAtIndex(2)->GetURL()); - - // Commit the pending entry. - contents()->CommitPendingNavigation(); - - // Check the final state. - // entries[0] = url_0 - // entries[1] = url_1 <- last committed entry - ASSERT_EQ(2, controller.GetEntryCount()); - EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); - EXPECT_EQ(-1, controller.GetPendingEntryIndex()); - EXPECT_EQ(nullptr, controller.GetPendingEntry()); - EXPECT_EQ(nullptr, controller.GetTransientEntry()); - EXPECT_EQ(url_0, controller.GetEntryAtIndex(0)->GetURL()); - EXPECT_EQ(url_1, controller.GetEntryAtIndex(1)->GetURL()); -} - // Tests that NavigationUIData has been passed to the NavigationHandle. TEST_F(NavigationControllerTest, MainFrameNavigationUIData) { LoadCommittedDetailsObserver observer(contents()); @@ -4757,7 +4397,6 @@ TEST_F(NavigationControllerTest, PruneForwardEntries) { const GURL url_1("http://foo/1"); const GURL url_2("http://foo/2"); const GURL url_3("http://foo/3"); - const GURL url_transient("http://foo/transient"); NavigateAndCommit(url_0); NavigateAndCommit(url_1); @@ -4797,10 +4436,6 @@ TEST_F(NavigationControllerTest, PruneForwardEntries) { EXPECT_EQ(1, controller.GetPendingEntryIndex()); EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); - // Insert a transient entry before the pending one. - std::unique_ptr<NavigationEntry> transient_entry(new NavigationEntryImpl); - transient_entry->SetURL(url_transient); - controller.SetTransientEntry(std::move(transient_entry)); state_change_count = delegate->navigation_state_change_count(); controller.PruneForwardEntries(); @@ -4812,7 +4447,6 @@ TEST_F(NavigationControllerTest, PruneForwardEntries) { EXPECT_EQ(0, controller.GetCurrentEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(nullptr, controller.GetPendingEntry()); - EXPECT_EQ(nullptr, controller.GetTransientEntry()); EXPECT_EQ(url_0, controller.GetVisibleEntry()->GetURL()); EXPECT_EQ(1U, navigation_list_pruned_counter_); EXPECT_EQ(1, last_navigation_entry_pruned_details_.index); diff --git a/chromium/content/browser/frame_host/navigation_entry_impl.cc b/chromium/content/browser/frame_host/navigation_entry_impl.cc index 6be77fe8508..0c43acd31a4 100644 --- a/chromium/content/browser/frame_host/navigation_entry_impl.cc +++ b/chromium/content/browser/frame_host/navigation_entry_impl.cc @@ -20,6 +20,7 @@ #include "build/build_config.h" #include "components/url_formatter/url_formatter.h" #include "content/browser/frame_host/navigation_controller_impl.h" +#include "content/browser/web_package/web_bundle_navigation_info.h" #include "content/common/content_constants_internal.h" #include "content/common/navigation_params.h" #include "content/common/page_state_serialization.h" @@ -77,7 +78,8 @@ void RecursivelyGenerateFrameEntries( state.referrer_policy), state.initiator_origin, std::vector<GURL>(), PageState::CreateFromEncodedData(data), "GET", -1, - nullptr /* blob_url_loader_factory */); + nullptr /* blob_url_loader_factory */, + nullptr /* web_bundle_navigation_info */); // Don't pass the file list to subframes, since that would result in multiple // copies of it ending up in the combined list in GetPageState (via @@ -356,23 +358,24 @@ NavigationEntryImpl::NavigationEntryImpl( ui::PageTransition transition_type, bool is_renderer_initiated, scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) - : frame_tree_( - std::make_unique<TreeNode>(nullptr, - base::MakeRefCounted<FrameNavigationEntry>( - "", - -1, - -1, - std::move(instance), - nullptr, - url, - nullptr /* origin */, - referrer, - initiator_origin, - std::vector<GURL>(), - PageState(), - "GET", - -1, - std::move(blob_url_loader_factory)))), + : frame_tree_(std::make_unique<TreeNode>( + nullptr, + base::MakeRefCounted<FrameNavigationEntry>( + "", + -1, + -1, + std::move(instance), + nullptr, + url, + nullptr /* origin */, + referrer, + initiator_origin, + std::vector<GURL>(), + PageState(), + "GET", + -1, + std::move(blob_url_loader_factory), + nullptr /* web_bundle_navigation_info */))), unique_id_(CreateUniqueEntryID()), page_type_(PAGE_TYPE_NORMAL), update_virtual_url_with_url_(false), @@ -826,7 +829,8 @@ NavigationEntryImpl::ConstructCommitNavigationParams( false, network::mojom::IPAddressSpace::kUnknown, GURL() /* web_bundle_physical_url */, GURL() /* base_url_override_for_web_bundle */, frame_policy, - std::vector<std::string>() /* force_enabled_origin_trials */); + std::vector<std::string>() /* force_enabled_origin_trials */, + false /* origin_isolation_restricted */); #if defined(OS_ANDROID) if (NavigationControllerImpl::ValidateDataURLAsString(GetDataURLAsString())) { commit_params->data_url_as_string = GetDataURLAsString()->data(); @@ -885,7 +889,8 @@ void NavigationEntryImpl::AddOrUpdateFrameEntry( const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) { + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info) { // If this is called for the main frame, the FrameNavigationEntry is // guaranteed to exist, so just update it directly and return. if (frame_tree_node->IsMainFrame()) { @@ -900,7 +905,8 @@ void NavigationEntryImpl::AddOrUpdateFrameEntry( document_sequence_number, site_instance, std::move(source_site_instance), url, origin, referrer, initiator_origin, redirect_chain, page_state, method, post_id, - std::move(blob_url_loader_factory)); + std::move(blob_url_loader_factory), + std::move(web_bundle_navigation_info)); return; } @@ -929,7 +935,8 @@ void NavigationEntryImpl::AddOrUpdateFrameEntry( unique_name, item_sequence_number, document_sequence_number, site_instance, std::move(source_site_instance), url, origin, referrer, initiator_origin, redirect_chain, page_state, method, post_id, - std::move(blob_url_loader_factory)); + std::move(blob_url_loader_factory), + std::move(web_bundle_navigation_info)); return; } } @@ -942,7 +949,8 @@ void NavigationEntryImpl::AddOrUpdateFrameEntry( site_instance, std::move(source_site_instance), url, base::OptionalOrNullptr(origin), referrer, initiator_origin, redirect_chain, page_state, method, post_id, - std::move(blob_url_loader_factory)); + std::move(blob_url_loader_factory), + std::move(web_bundle_navigation_info)); parent_node->children.push_back( std::make_unique<NavigationEntryImpl::TreeNode>(parent_node, std::move(frame_entry))); diff --git a/chromium/content/browser/frame_host/navigation_entry_impl.h b/chromium/content/browser/frame_host/navigation_entry_impl.h index ac969a4f025..09fd6806650 100644 --- a/chromium/content/browser/frame_host/navigation_entry_impl.h +++ b/chromium/content/browser/frame_host/navigation_entry_impl.h @@ -36,6 +36,8 @@ namespace content { +class WebBundleNavigationInfo; + class CONTENT_EXPORT NavigationEntryImpl : public NavigationEntry { public: // Represents a tree of FrameNavigationEntries that make up this joint session @@ -231,7 +233,8 @@ class CONTENT_EXPORT NavigationEntryImpl : public NavigationEntry { const PageState& page_state, const std::string& method, int64_t post_id, - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory); + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info); // Returns the FrameNavigationEntry corresponding to |frame_tree_node|, if // there is one in this NavigationEntry. diff --git a/chromium/content/browser/frame_host/navigation_request.cc b/chromium/content/browser/frame_host/navigation_request.cc index baf19ee3bae..5757ad84edb 100644 --- a/chromium/content/browser/frame_host/navigation_request.cc +++ b/chromium/content/browser/frame_host/navigation_request.cc @@ -23,7 +23,6 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/system/sys_info.h" -#include "base/task/post_task.h" #include "build/build_config.h" #include "content/browser/appcache/appcache_navigation_handle.h" #include "content/browser/appcache/chrome_appcache_service.h" @@ -47,6 +46,7 @@ #include "content/browser/loader/cached_navigation_url_loader.h" #include "content/browser/loader/navigation_url_loader.h" #include "content/browser/net/cross_origin_embedder_policy_reporter.h" +#include "content/browser/net/cross_origin_opener_policy_reporter.h" #include "content/browser/network_service_instance_impl.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/renderer_host/render_view_host_delegate.h" @@ -66,6 +66,7 @@ #include "content/common/navigation_params.h" #include "content/common/navigation_params_mojom_traits.h" #include "content/common/navigation_params_utils.h" +#include "content/common/state_transitions.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -102,6 +103,7 @@ #include "services/network/public/cpp/content_security_policy/content_security_policy.h" #include "services/network/public/cpp/cross_origin_resource_policy.h" #include "services/network/public/cpp/features.h" +#include "services/network/public/cpp/is_potentially_trustworthy.h" #include "services/network/public/cpp/resource_request_body.h" #include "services/network/public/cpp/url_loader_completion_status.h" #include "services/network/public/cpp/web_sandbox_flags.h" @@ -661,6 +663,107 @@ void EnterChildTraceEvent(const char* name, arg_value); } +network::mojom::RequestDestination GetDestinationFromFrameTreeNode( + FrameTreeNode* frame_tree_node) { + if (frame_tree_node->IsMainFrame()) { + return frame_tree_node->current_frame_host() + ->GetRenderViewHost() + ->GetDelegate() + ->IsPortal() + ? network::mojom::RequestDestination::kIframe + : network::mojom::RequestDestination::kDocument; + } else { + switch (frame_tree_node->frame_owner_element_type()) { + case blink::mojom::FrameOwnerElementType::kObject: + return network::mojom::RequestDestination::kObject; + case blink::mojom::FrameOwnerElementType::kEmbed: + return network::mojom::RequestDestination::kEmbed; + case blink::mojom::FrameOwnerElementType::kIframe: + return network::mojom::RequestDestination::kIframe; + case blink::mojom::FrameOwnerElementType::kFrame: + return network::mojom::RequestDestination::kFrame; + case blink::mojom::FrameOwnerElementType::kPortal: + case blink::mojom::FrameOwnerElementType::kNone: + NOTREACHED(); + return network::mojom::RequestDestination::kDocument; + } + NOTREACHED(); + return network::mojom::RequestDestination::kDocument; + } +} + +// This function implements the COOP matching algorithm as detailed in [1]. +// Note that COEP is also provided since the COOP enum does not have a +// "same-origin + COEP" value. +// [1] https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e +bool CrossOriginOpenerPolicyMatch( + network::mojom::CrossOriginOpenerPolicyValue initiator_coop, + network::mojom::CrossOriginEmbedderPolicyValue initiator_coep, + const url::Origin& initiator_origin, + network::mojom::CrossOriginOpenerPolicyValue destination_coop, + network::mojom::CrossOriginEmbedderPolicyValue destination_coep, + const url::Origin& destination_origin) { + if (initiator_coop != destination_coop) + return false; + if (initiator_coop == + network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone) { + return true; + } + if (initiator_coop == + network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin && + initiator_coep != destination_coep) { + return false; + } + if (!initiator_origin.IsSameOriginWith(destination_origin)) + return false; + return true; +} + +// This function returns whether the BrowsingInstance should change following +// COOP rules defined in: +// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e#changes-to-navigation +bool ShouldSwapBrowsingInstanceForCrossOriginOpenerPolicy( + network::mojom::CrossOriginOpenerPolicyValue initiator_coop, + network::mojom::CrossOriginEmbedderPolicyValue initiator_coep, + const url::Origin& initiator_origin, + bool is_initiator_aboutblank, + network::mojom::CrossOriginOpenerPolicyValue destination_coop, + network::mojom::CrossOriginEmbedderPolicyValue destination_coep, + const url::Origin& destination_origin) { + using network::mojom::CrossOriginEmbedderPolicyValue; + using network::mojom::CrossOriginOpenerPolicyValue; + + if (!base::FeatureList::IsEnabled( + network::features::kCrossOriginOpenerPolicy)) + return false; + + // If policies match there is no reason to switch BrowsingInstances. + if (CrossOriginOpenerPolicyMatch(initiator_coop, initiator_coep, + initiator_origin, destination_coop, + destination_coep, destination_origin)) { + return false; + } + + // "same-origin-allow-popups" is used to stay in the same BrowsingInstance + // despite COOP mismatch. This case is defined in the spec [1] as follow. + // ``` + // If the result of matching currentCOOP, currentOrigin, potentialCOOP, and + // potentialOrigin is false and one of the following is false: + // - doc is the initial about:blank document + // - currentCOOP is "same-origin-allow-popups" + // - potentialCOOP is "unsafe-none" + // Then create a new browsing context group. + // ``` + // [1] + // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e#changes-to-navigation + if (is_initiator_aboutblank && + initiator_coop == CrossOriginOpenerPolicyValue::kSameOriginAllowPopups && + destination_coop == CrossOriginOpenerPolicyValue::kUnsafeNone) { + return false; + } + return true; +} + } // namespace // static @@ -680,11 +783,13 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated( // This is not currently handled here. bool is_form_submission = !!post_body; + network::mojom::RequestDestination destination = + GetDestinationFromFrameTreeNode(frame_tree_node); + auto navigation_params = mojom::BeginNavigationParams::New( initiator_routing_id.frame_routing_id /* initiator_routing_id */, extra_headers, net::LOAD_NORMAL, false /* skip_service_worker */, - blink::mojom::RequestContextType::LOCATION, - network::mojom::RequestDestination::kDocument, + blink::mojom::RequestContextType::LOCATION, destination, blink::WebMixedContentContextType::kBlockable, is_form_submission, false /* was_initiated_by_link_click */, GURL() /* searchable_form_url */, std::string() /* searchable_form_encoding */, @@ -704,7 +809,7 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated( if (entry) { NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( - frame_tree_node->navigator()->GetController()); + frame_tree_node->navigator().GetController()); BackForwardCacheImpl::Entry* restored_entry = controller->GetBackForwardCache().GetEntry(entry->GetUniqueID()); if (restored_entry) { @@ -718,15 +823,14 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated( std::move(commit_params), browser_initiated, false /* from_begin_navigation */, false /* is_for_commit */, frame_entry, entry, std::move(navigation_ui_data), mojo::NullAssociatedRemote(), - mojo::NullRemote(), rfh_restored_from_back_forward_cache)); + mojo::NullRemote(), rfh_restored_from_back_forward_cache, + initiator_routing_id)); if (frame_entry) { navigation_request->blob_url_loader_factory_ = frame_entry->blob_url_loader_factory(); } - navigation_request->initiator_routing_id_ = initiator_routing_id; - if (navigation_request->common_params().url.SchemeIsBlob() && !navigation_request->blob_url_loader_factory_) { // If this navigation entry came from session history then the blob factory @@ -736,7 +840,7 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated( // alive. navigation_request->blob_url_loader_factory_ = ChromeBlobStorageContext::URLLoaderFactoryForUrl( - frame_tree_node->navigator()->GetController()->GetBrowserContext(), + frame_tree_node->navigator().GetController()->GetBrowserContext(), navigation_request->common_params().url); } @@ -767,6 +871,9 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated( common_params->navigation_type == mojom::NavigationType::DIFFERENT_DOCUMENT); + begin_params->request_destination = + GetDestinationFromFrameTreeNode(frame_tree_node); + // TODO(clamy): See if the navigation start time should be measured in the // renderer and sent to the browser instead of being measured here. mojom::CommitNavigationParamsPtr commit_params = @@ -804,7 +911,17 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated( GURL() /* web_bundle_physical_url */, GURL() /* base_url_override_for_web_bundle */, frame_tree_node->pending_frame_policy(), - std::vector<std::string>() /* force_enabled_origin_trials */); + std::vector<std::string>() /* force_enabled_origin_trials */, + false /* origin_isolation_restricted */); + + // CreateRendererInitiated() should only be triggered when the navigation is + // initiated by a frame in the same process. + // TODO(https://crbug.com/1074464): Find a way to DCHECK that the routing ID + // is from the current RFH. + GlobalFrameRoutingId initiator_routing_id( + frame_tree_node->current_frame_host()->GetProcess()->GetID(), + begin_params->initiator_routing_id); + std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest( frame_tree_node, std::move(common_params), std::move(begin_params), std::move(commit_params), @@ -814,8 +931,8 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated( nullptr, entry, nullptr, // navigation_ui_data std::move(navigation_client), std::move(navigation_initiator), - nullptr // rfh_restored_from_back_forward_cache - )); + nullptr, // rfh_restored_from_back_forward_cache + initiator_routing_id)); navigation_request->blob_url_loader_factory_ = std::move(blob_url_loader_factory); navigation_request->prefetched_signed_exchange_cache_ = @@ -823,13 +940,6 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated( navigation_request->web_bundle_handle_tracker_ = std::move(web_bundle_handle_tracker); - // CreateRendererInitiated() should only be triggered when the navigation is - // initiated by a frame in the same process. - // TODO(https://crbug.com/1074464): Find a way to DCHECK that the routing ID - // is from the current RFH. - navigation_request->initiator_routing_id_ = GlobalFrameRoutingId( - frame_tree_node->current_frame_host()->GetProcess()->GetID(), - navigation_request->begin_params()->initiator_routing_id); return navigation_request; } @@ -839,7 +949,8 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateForCommit( RenderFrameHostImpl* render_frame_host, const FrameHostMsg_DidCommitProvisionalLoad_Params& params, std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter, - bool is_same_document) { + bool is_same_document, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info) { // TODO(clamy): Improve the *NavigationParams and *CommitParams to avoid // copying so many parameters here. mojom::CommonNavigationParamsPtr common_params = @@ -887,8 +998,9 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateForCommit( network::mojom::IPAddressSpace::kUnknown, GURL() /* web_bundle_physical_url */, GURL() /* base_url_override_for_web_bundle */, - base::nullopt /* frame policy */, - std::vector<std::string>() /* force_enabled_origin_trials */ + frame_tree_node->pending_frame_policy(), + std::vector<std::string>() /* force_enabled_origin_trials */, + false /* origin_isolation_restricted */ ); mojom::BeginNavigationParamsPtr begin_params = mojom::BeginNavigationParams::New(); @@ -896,10 +1008,13 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateForCommit( frame_tree_node, std::move(common_params), std::move(begin_params), std::move(commit_params), false /* browser_initiated */, false /* from_begin_navigation */, true /* is_for_commit */, - nullptr /* frame_navigation_entry */, nullptr /* navitation_entry */, + nullptr /* frame_navigation_entry */, nullptr /* navigation_entry */, nullptr /* navigation_ui_data */, mojo::NullAssociatedRemote(), - mojo::NullRemote(), nullptr /* rfh_restored_from_back_forward_cache */)); + mojo::NullRemote(), nullptr, /* rfh_restored_from_back_forward_cache */ + {} /* initiator_routing_id */)); + navigation_request->web_bundle_navigation_info_ = + std::move(web_bundle_navigation_info); navigation_request->render_frame_host_ = render_frame_host; navigation_request->coep_reporter_ = std::move(coep_reporter); navigation_request->StartNavigation(true); @@ -921,7 +1036,8 @@ NavigationRequest::NavigationRequest( std::unique_ptr<NavigationUIData> navigation_ui_data, mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client, mojo::PendingRemote<blink::mojom::NavigationInitiator> navigation_initiator, - RenderFrameHostImpl* rfh_restored_from_back_forward_cache) + RenderFrameHostImpl* rfh_restored_from_back_forward_cache, + GlobalFrameRoutingId initiator_routing_id) : frame_tree_node_(frame_tree_node), is_for_commit_(is_for_commit), common_params_(std::move(common_params)), @@ -930,24 +1046,40 @@ NavigationRequest::NavigationRequest( browser_initiated_(browser_initiated), navigation_ui_data_(std::move(navigation_ui_data)), state_(NOT_STARTED), - restore_type_(RestoreType::NONE), + restore_type_(entry ? entry->restore_type() : RestoreType::NONE), + reload_type_(entry ? entry->reload_type() : ReloadType::NONE), + nav_entry_id_(entry ? entry->GetUniqueID() : 0), is_view_source_(false), bindings_(FrameNavigationEntry::kInvalidBindings), response_should_be_rendered_(true), associated_site_instance_type_(AssociatedSiteInstanceType::NONE), from_begin_navigation_(from_begin_navigation), has_stale_copy_in_cache_(false), - net_error_(net::OK), expected_render_process_host_id_(ChildProcessHost::kInvalidUniqueID), + initiator_csp_context_(std::make_unique<InitiatorCSPContext>( + std::move(common_params_->initiator_csp_info->initiator_csp), + std::move(common_params_->initiator_csp_info->initiator_self_source), + std::move(navigation_initiator))), devtools_navigation_token_(base::UnguessableToken::Create()), request_navigation_client_(mojo::NullAssociatedRemote()), commit_navigation_client_(mojo::NullAssociatedRemote()), + navigation_handle_timing_(std::make_unique<NavigationHandleTiming>()), rfh_restored_from_back_forward_cache_( rfh_restored_from_back_forward_cache), - client_security_state_(network::mojom::ClientSecurityState::New()) { + // Store the old RenderFrameHost id at request creation to be used later. + previous_render_frame_host_id_(GlobalFrameRoutingId( + frame_tree_node->current_frame_host()->GetProcess()->GetID(), + frame_tree_node->current_frame_host()->GetRoutingID())), + initiator_routing_id_(initiator_routing_id), + client_security_state_(network::mojom::ClientSecurityState::New()), + previous_page_load_ukm_source_id_( + frame_tree_node_->current_frame_host()->GetPageUkmSourceId()) { DCHECK(browser_initiated_ || common_params_->initiator_origin.has_value()); DCHECK(!IsRendererDebugURL(common_params_->url)); DCHECK(common_params_->method == "POST" || !common_params_->post_data); + DCHECK((IsInMainFrame() && browser_initiated) || + commit_params_->frame_policy.has_value()); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( "navigation", "NavigationRequest", this, "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", @@ -1012,7 +1144,9 @@ NavigationRequest::NavigationRequest( // Let the NTP override the navigation params and pretend that this is a // browser-initiated, bookmark-like navigation. - if (!browser_initiated_ && source_site_instance_) { + // TODO(crbug.com/1099431): determine why some link navigations on chrome:// + // pages have |browser_initiated_| set to true and others set to false. + if (source_site_instance_) { bool is_renderer_initiated = !browser_initiated_; Referrer referrer(*common_params_->referrer); GetContentClient()->browser()->OverrideNavigationParams( @@ -1024,14 +1158,6 @@ NavigationRequest::NavigationRequest( commit_params_->is_browser_initiated = browser_initiated_; } - // Store the old RenderFrameHost id at request creation to be used later. - previous_render_frame_host_id_ = GlobalFrameRoutingId( - frame_tree_node->current_frame_host()->GetProcess()->GetID(), - frame_tree_node->current_frame_host()->GetRoutingID()); - - previous_page_load_ukm_source_id_ = - frame_tree_node_->current_frame_host()->GetPageUkmSourceId(); - // Update the load flags with cache information. UpdateLoadFlagsWithCacheFlags(&begin_params_->load_flags, common_params_->navigation_type, @@ -1040,9 +1166,6 @@ NavigationRequest::NavigationRequest( // Add necessary headers that may not be present in the // mojom::BeginNavigationParams. if (entry) { - nav_entry_id_ = entry->GetUniqueID(); - restore_type_ = entry->restore_type(); - reload_type_ = entry->reload_type(); // TODO(altimin, crbug.com/933147): Remove this logic after we are done // with implementing back-forward cache. if (frame_tree_node->IsMainFrame() && entry->back_forward_cache_metrics()) { @@ -1100,11 +1223,6 @@ NavigationRequest::NavigationRequest( begin_params_->headers = headers.ToString(); - initiator_csp_context_.reset(new InitiatorCSPContext( - std::move(common_params_->initiator_csp_info->initiator_csp), - std::move(common_params_->initiator_csp_info->initiator_self_source), - std::move(navigation_initiator))); - navigation_entry_offset_ = EstimateHistoryOffset(); commit_params_->is_browser_initiated = browser_initiated_; @@ -1151,6 +1269,7 @@ NavigationRequest::~NavigationRequest() { if (IsNavigationStarted()) { GetDelegate()->DidFinishNavigation(this); + ProcessOriginIsolationEndResult(); if (IsInMainFrame()) { TRACE_EVENT_NESTABLE_ASYNC_END2( "navigation", "Navigation StartToCommit", @@ -1161,13 +1280,12 @@ NavigationRequest::~NavigationRequest() { } void NavigationRequest::BeginNavigation() { - DCHECK(state_ == NOT_STARTED || state_ == WAITING_FOR_RENDERER_RESPONSE); EnterChildTraceEvent("BeginNavigation", this); DCHECK(!loader_); DCHECK(!render_frame_host_); ScopedNavigationRequestCrashKeys crash_keys(this); - state_ = WILL_START_NAVIGATION; + SetState(WILL_START_NAVIGATION); #if defined(OS_ANDROID) base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr()); @@ -1263,6 +1381,26 @@ void NavigationRequest::BeginNavigation() { return; } + // Try to inherit the current page COOP/COEP to have a relevant speculative + // RFH. The heuristic for inheriting is to have the most conservative approach + // towards BrowsingInstance switching. Every same-origin navigation should + // yield a no swap decision. This is done to work with the renderer crash + // optimization that instantly commits the speculative RenderFrameHost. + network::mojom::CrossOriginOpenerPolicyValue coop; + network::mojom::CrossOriginEmbedderPolicyValue coep; + RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host(); + + bool inherit_coop = + current_rfh->has_committed_any_navigation() || + current_rfh->cross_origin_opener_policy().value == + network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin; + coop = inherit_coop + ? current_rfh->cross_origin_opener_policy().value + : network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone; + coep = current_rfh->cross_origin_embedder_policy().value; + + UpdateCoopStatus(coop, coep); + if (!NeedsUrlLoader()) { // The types of pages that don't need a URL Loader should never get served // from the BackForwardCache. @@ -1287,18 +1425,21 @@ void NavigationRequest::BeginNavigation() { CommitNavigation(); return; } - - common_params_->previews_state = - GetContentClient()->browser()->DetermineAllowedPreviews( - common_params_->previews_state, this, common_params_->url); - + // If the navigation is served from the back-forward cache, we already know + // its preview type from the first time we navigated into the page, so we + // should only set |previews_state| when the navigation is not served from the + // back-forward cache. + if (!IsServedFromBackForwardCache()) { + common_params_->previews_state = + GetContentClient()->browser()->DetermineAllowedPreviews( + common_params_->previews_state, this, common_params_->url); + } WillStartRequest(); } void NavigationRequest::SetWaitingForRendererResponse() { EnterChildTraceEvent("WaitingForRendererResponse", this); - DCHECK(state_ == NOT_STARTED); - state_ = WAITING_FOR_RENDERER_RESPONSE; + SetState(WAITING_FOR_RENDERER_RESPONSE); } void NavigationRequest::StartNavigation(bool is_for_commit) { @@ -1314,7 +1455,7 @@ void NavigationRequest::StartNavigation(bool is_for_commit) { // starting SiteInstance. starting_site_instance_ = frame_tree_node->current_frame_host()->GetSiteInstance(); - site_url_ = GetSiteForCommonParamsURL(); + site_info_ = GetSiteInfoForCommonParamsURL(); // Compute the redirect chain. // TODO(clamy): Try to simplify this and have the redirects be part of @@ -1354,7 +1495,7 @@ void NavigationRequest::StartNavigation(bool is_for_commit) { } DCHECK(!IsNavigationStarted()); - state_ = WILL_START_REQUEST; + SetState(WILL_START_REQUEST); navigation_handle_id_ = CreateUniqueHandleID(); modified_request_headers_.Clear(); @@ -1420,7 +1561,7 @@ void NavigationRequest::ResetForCrossDocumentRestart() { // Reset the state of the NavigationRequest, and the navigation_handle_id. StopCommitTimeout(); - state_ = NOT_STARTED; + SetState(NOT_STARTED); processing_navigation_throttle_ = false; navigation_handle_id_ = 0; @@ -1437,6 +1578,9 @@ void NavigationRequest::ResetForCrossDocumentRestart() { // Convert the navigation type to the appropriate cross-document one. common_params_->navigation_type = ConvertToCrossDocumentType(common_params_->navigation_type); + + // Reset navigation handle timings. + navigation_handle_timing_ = std::make_unique<NavigationHandleTiming>(); } void NavigationRequest::ResetStateForSiteInstanceChange() { @@ -1508,6 +1652,37 @@ NavigationRequest::TakeCoepReporter() { return std::move(coep_reporter_); } +void NavigationRequest::CreateCoopReporter( + StoragePartition* storage_partition) { + // If the flag for reporting is off, we simply don't create anything. + // Since this is the only place we create COOP reporters this ensure reporting + // is completely off. + // Note that "popup inheritance" also instantiate a reporter, but only if we + // created one here first. + if (!base::FeatureList::IsEnabled( + network::features::kCrossOriginOpenerPolicyReporting)) { + return; + } + + // If the page does not have any reporting endpoints, skip creating a + // reporter. + if (!render_frame_host_->cross_origin_opener_policy().reporting_endpoint && + !render_frame_host_->cross_origin_opener_policy() + .report_only_reporting_endpoint) { + return; + } + + coop_reporter_ = std::make_unique<CrossOriginOpenerPolicyReporter>( + storage_partition, frame_tree_node_->current_frame_host(), + common_params_->url, render_frame_host_->cross_origin_opener_policy(), + render_frame_host_->cross_origin_embedder_policy()); +} + +std::unique_ptr<CrossOriginOpenerPolicyReporter> +NavigationRequest::TakeCoopReporter() { + return std::move(coop_reporter_); +} + ukm::SourceId NavigationRequest::GetPreviousPageUkmSourceId() { return previous_page_load_ukm_source_id_; } @@ -1615,7 +1790,9 @@ void NavigationRequest::OnRequestRedirected( return; } - if (const auto blocked_reason = IsBlockedByCorp()) { + SanitizeCoopHeaders(); + + if (const auto blocked_reason = IsBlockedByResponse()) { OnRequestFailedInternal(network::URLLoaderCompletionStatus(*blocked_reason), false /* skip_throttles */, base::nullopt /* error_page_content */, @@ -1630,11 +1807,8 @@ void NavigationRequest::OnRequestRedirected( if (redirect_info.new_method != "POST") common_params_->post_data.reset(); - // Record the first request start time and response start time. - if (first_request_start_.is_null()) - first_request_start_ = response_head_->load_timing.send_start; - if (first_response_start_.is_null()) - first_response_start_ = response_head_->load_timing.receive_headers_start; + const bool is_first_response = commit_params_->redirects.empty(); + UpdateNavigationHandleTimingsOnResponseReceived(is_first_response); // Mark time for the Navigation Timing API. if (commit_params_->navigation_timing->redirect_start.is_null()) { @@ -1696,6 +1870,13 @@ void NavigationRequest::OnRequestRedirected( return; } + if (base::FeatureList::IsEnabled( + network::features::kCrossOriginOpenerPolicy)) { + UpdateCoopStatus( + response_head_->parsed_headers->cross_origin_opener_policy.value, + response_head_->parsed_headers->cross_origin_embedder_policy.value); + } + // Compute the SiteInstance to use for the redirect and pass its // RenderProcessHost if it has a process. Keep a reference if it has a // process, so that the SiteInstance and its associated process aren't deleted @@ -1754,7 +1935,7 @@ void NavigationRequest::CheckForIsolationOptIn(const GURL& url) { // origin as non-opt-in before it gets the change to register itself as // opted-in. frame_tree_node_->navigator() - ->GetDelegate() + .GetDelegate() ->RegisterExistingOriginToPreventOptInIsolation( origin, this /* navigation_request_to_exclude */); } @@ -1800,12 +1981,8 @@ NavigationRequest::IsOptInIsolationRequested(const GURL& url) { url, response()->headers.get(), "OriginIsolationHeader", base::Time::Now())); - // TODO(https://crbug.com/1066930): For now we just check the presence of the - // header; we do not parse/validate it. When we do, that will have to be - // outside the browser process. const bool requests_via_header = - header_is_enabled && response()->headers && - response()->headers->HasHeader("origin-isolation"); + header_is_enabled && response_head_->parsed_headers->origin_isolation; if (requests_via_header) return OptInIsolationCheckResult::HEADER; @@ -1813,6 +1990,85 @@ NavigationRequest::IsOptInIsolationRequested(const GURL& url) { return OptInIsolationCheckResult::NONE; } +void NavigationRequest::DetermineOriginIsolationEndResult( + OptInIsolationCheckResult check_result) { + auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); + const url::Origin origin = url::Origin::Create(common_params_->url); + const IsolationContext& isolation_context = + render_frame_host_->GetSiteInstance()->GetIsolationContext(); + const bool got_isolated = + policy->ShouldOriginGetOptInIsolation(isolation_context, origin); + + switch (check_result) { + case OptInIsolationCheckResult::NONE: + origin_isolation_end_result_ = + got_isolated + ? OptInOriginIsolationEndResult::kNotRequestedButIsolated + : OptInOriginIsolationEndResult::kNotRequestedAndNotIsolated; + break; + case OptInIsolationCheckResult::ORIGIN_POLICY: + origin_isolation_end_result_ = + got_isolated ? OptInOriginIsolationEndResult:: + kRequestedViaOriginPolicyAndIsolated + : OptInOriginIsolationEndResult:: + kRequestedViaOriginPolicyButNotIsolated; + break; + case OptInIsolationCheckResult::HEADER: + origin_isolation_end_result_ = + got_isolated + ? OptInOriginIsolationEndResult::kRequestedViaHeaderAndIsolated + : OptInOriginIsolationEndResult:: + kRequestedViaHeaderButNotIsolated; + break; + } + + commit_params_->origin_isolation_restricted = + origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kRequestedViaOriginPolicyAndIsolated || + origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kRequestedViaHeaderAndIsolated || + origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kNotRequestedButIsolated; +} + +void NavigationRequest::ProcessOriginIsolationEndResult() { + if (!HasCommitted() || IsErrorPage() || IsSameDocument()) + return; + + if (origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kRequestedViaHeaderAndIsolated || + origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kRequestedViaHeaderButNotIsolated) + GetContentClient()->browser()->LogWebFeatureForCurrentPage( + render_frame_host_, blink::mojom::WebFeature::kOriginIsolationHeader); + + const url::Origin origin = url::Origin::Create(GetURL()); + + if (origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kRequestedViaHeaderButNotIsolated || + origin_isolation_end_result_ == + OptInOriginIsolationEndResult:: + kRequestedViaOriginPolicyButNotIsolated) + render_frame_host_->AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kWarning, + base::StringPrintf( + "The page requested origin isolation, but could not be isolated " + "since the origin '%s' had previously been seen with no " + "isolation. Update your headers to uniformly isolate all pages " + "on the origin.", + origin.Serialize().c_str())); + + if (origin_isolation_end_result_ == + OptInOriginIsolationEndResult::kNotRequestedButIsolated) + render_frame_host_->AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kWarning, + base::StringPrintf("The page did not request origin isolation, but " + "was isolated anyway because the origin '%s' had " + "previously been isolated. Update your headers to " + "uniformly isolate all pages on the origin.", + origin.Serialize().c_str())); +} + void NavigationRequest::OnResponseStarted( network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, network::mojom::URLResponseHeadPtr response_head, @@ -1837,7 +2093,7 @@ void NavigationRequest::OnResponseStarted( DCHECK(response_head); DCHECK(response_head->parsed_headers); EnterChildTraceEvent("OnResponseStarted", this); - state_ = WILL_PROCESS_RESPONSE; + SetState(WILL_PROCESS_RESPONSE); response_head_ = std::move(response_head); response_body_ = std::move(response_body); ssl_info_ = response_head_->ssl_info; @@ -1890,12 +2146,8 @@ void NavigationRequest::OnResponseStarted( ? base::make_optional(appcache_handle_->appcache_host_id()) : base::nullopt; - // Record the first request start time and first response start time. Skip if - // the timings are already recorded for redirection etc. - if (first_request_start_.is_null()) - first_request_start_ = response_head_->load_timing.send_start; - if (first_response_start_.is_null()) - first_response_start_ = response_head_->load_timing.receive_headers_start; + const bool is_first_response = commit_params_->redirects.empty(); + UpdateNavigationHandleTimingsOnResponseReceived(is_first_response); // Update fetch start timing. While NavigationRequest updates fetch start // timing for redirects, it's not aware of service worker interception so @@ -1928,7 +2180,7 @@ void NavigationRequest::OnResponseStarted( commit_params_->was_activated = mojom::WasActivatedOption::kNo; if (!browser_initiated_ && - (frame_tree_node_->has_received_user_gesture() || + (frame_tree_node_->HasStickyUserActivation() || frame_tree_node_->has_received_user_gesture_before_nav()) && ShouldPropagateUserActivation( frame_tree_node_->current_origin(), @@ -1946,7 +2198,9 @@ void NavigationRequest::OnResponseStarted( } } - if (const auto blocked_reason = IsBlockedByCorp()) { + SanitizeCoopHeaders(); + + if (const auto blocked_reason = IsBlockedByResponse()) { OnRequestFailedInternal(network::URLLoaderCompletionStatus(*blocked_reason), false /* skip_throttles */, base::nullopt /* error_page_content */, @@ -1960,79 +2214,75 @@ void NavigationRequest::OnResponseStarted( response_head_->parsed_headers->cross_origin_embedder_policy; if (base::FeatureList::IsEnabled( network::features::kCrossOriginEmbedderPolicy)) { - // https://mikewest.github.io/corpp/#process-navigation-response - if (auto* const parent_frame = GetParentFrame()) { - const auto& parent_coep = parent_frame->cross_origin_embedder_policy(); - const auto& url = common_params_->url; - constexpr auto kRequireCorp = - network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp; - constexpr auto kNone = - network::mojom::CrossOriginEmbedderPolicyValue::kNone; - - // Some special URLs not loaded using the network are inheriting the - // Cross-Origin-Embedder-Policy header from their parent. - const bool has_allowed_scheme = - url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme) || - GetContentClient() - ->browser() - ->ShouldInheritCrossOriginEmbedderPolicyImplicitly(url); - if (parent_coep.value == kRequireCorp && has_allowed_scheme) { - cross_origin_embedder_policy.value = kRequireCorp; - } + const auto& url = common_params_->url; + // https://w3c.github.io/webappsec-secure-contexts/#is-url-trustworthy + // returns "Potentially Trustworthy" for data URLs, but + // network::IsUrlPotentiallyTrustworthy returns false, so we need this + // extra condition. + if (network::IsUrlPotentiallyTrustworthy(url) || + url.SchemeIs(url::kDataScheme)) { + // https://mikewest.github.io/corpp/#process-navigation-response + if (auto* const parent = GetParentFrame()) { + const auto& parent_coep = parent->cross_origin_embedder_policy(); + constexpr auto kRequireCorp = + network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp; + constexpr auto kNone = + network::mojom::CrossOriginEmbedderPolicyValue::kNone; + + // Some special URLs not loaded using the network are inheriting the + // Cross-Origin-Embedder-Policy header from their parent. + const bool has_allowed_scheme = + url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme) || + GetContentClient() + ->browser() + ->ShouldInheritCrossOriginEmbedderPolicyImplicitly(url); + if (parent_coep.value == kRequireCorp && has_allowed_scheme) { + cross_origin_embedder_policy.value = kRequireCorp; + } - auto* const coep_reporter = parent_frame->coep_reporter(); - if (parent_coep.report_only_value == kRequireCorp && - !has_allowed_scheme && cross_origin_embedder_policy.value == kNone && - coep_reporter) { - coep_reporter->QueueNavigationReport(redirect_chain_[0], - /*report_only=*/true); - } - if (parent_coep.value == kRequireCorp && - cross_origin_embedder_policy.value == kNone) { - if (coep_reporter) { + auto* const coep_reporter = parent->coep_reporter(); + if (parent_coep.report_only_value == kRequireCorp && + !has_allowed_scheme && + cross_origin_embedder_policy.value == kNone && coep_reporter) { coep_reporter->QueueNavigationReport(redirect_chain_[0], - /*report_only=*/false); + /*report_only=*/true); + } + if (parent_coep.value == kRequireCorp && + cross_origin_embedder_policy.value == kNone) { + if (coep_reporter) { + coep_reporter->QueueNavigationReport(redirect_chain_[0], + /*report_only=*/false); + } + OnRequestFailedInternal(network::URLLoaderCompletionStatus( + network::mojom::BlockedByResponseReason:: + kCoepFrameResourceNeedsCoepHeader), + false /* skip_throttles */, + base::nullopt /* error_page_content */, + false /* collapse_frame */); + // DO NOT ADD CODE after this. The previous call to + // OnRequestFailedInternal has destroyed the NavigationRequest. + return; } - OnRequestFailedInternal(network::URLLoaderCompletionStatus( - network::mojom::BlockedByResponseReason:: - kCoepFrameResourceNeedsCoepHeader), - false /* skip_throttles */, - base::nullopt /* error_page_content */, - false /* collapse_frame */); - // DO NOT ADD CODE after this. The previous call to - // OnRequestFailedInternal has destroyed the NavigationRequest. - return; } + } else { + cross_origin_embedder_policy = network::CrossOriginEmbedderPolicy(); } } if (base::FeatureList::IsEnabled( network::features::kCrossOriginOpenerPolicy)) { - // The Cross-Origin-Opener-Policy header should be ignored if delivered in - // insecure contexts, and non-top level documents. - if (!IsOriginSecure(common_params_->url) || !IsInMainFrame()) { - response_head_->parsed_headers->cross_origin_opener_policy = - network::CrossOriginOpenerPolicy(); - } - - // Popups with a sandboxing flag, inherited from their opener, are not - // allowed to navigate to a document with a Cross-Origin-Opener-Policy that - // is not "unsafe-none". This ensures a COOP document does not inherit any - // property from an opener. - // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e - if (response_head_->parsed_headers->cross_origin_opener_policy.value != - network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone && - (frame_tree_node_->pending_frame_policy().sandbox_flags != - network::mojom::WebSandboxFlags::kNone)) { - OnRequestFailedInternal( - network::URLLoaderCompletionStatus( - network::mojom::BlockedByResponseReason:: - kCoopSandboxedIFrameCannotNavigateToCoopPage), - false /* skip_throttles */, base::nullopt /* error_page_content */, - false /* collapse_frame */); - // DO NOT ADD CODE after this. The previous call to - // OnRequestFailedInternal has destroyed the NavigationRequest. - return; + UpdateCoopStatus( + response_head_->parsed_headers->cross_origin_opener_policy.value, + response_head_->parsed_headers->cross_origin_embedder_policy.value); + + RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host(); + if (coop_status_.require_browsing_instance_swap && + coop_status_.had_opener_before_browsing_instance_swap && + current_rfh->coop_reporter()) { + current_rfh->coop_reporter()->QueueOpenerBreakageReport( + current_rfh->coop_reporter()->GetNextDocumentUrlForReporting( + GetRedirectChain(), GetInitiatorRoutingId()), + true /* is_reported_from_document */, false /* is_report_only */); } } @@ -2066,10 +2316,7 @@ void NavigationRequest::OnResponseStarted( DCHECK(render_frame_host_ || !response_should_be_rendered_); if (render_frame_host_) { - if (opt_in_isolation == OptInIsolationCheckResult::HEADER) { - GetContentClient()->browser()->LogWebFeatureForCurrentPage( - render_frame_host_, blink::mojom::WebFeature::kOriginIsolationHeader); - } + DetermineOriginIsolationEndResult(opt_in_isolation); // TODO(pmeuleman, ahemery): Only set COOP and COEP values on // RenderFrameHost when the navigation commits. In the meantime, keep them @@ -2091,7 +2338,7 @@ void NavigationRequest::OnResponseStarted( // Allow the embedder to cancel the cross-process commit if needed. // TODO(clamy): Rename ShouldTransferNavigation. - if (!frame_tree_node_->navigator()->GetDelegate()->ShouldTransferNavigation( + if (!frame_tree_node_->navigator().GetDelegate()->ShouldTransferNavigation( frame_tree_node_->IsMainFrame())) { net_error_ = net::ERR_ABORTED; frame_tree_node_->ResetNavigationRequest(false); @@ -2160,7 +2407,7 @@ void NavigationRequest::OnResponseStarted( // the new SiteInstance can be used with the old entry if we return to it. // See http://crbug.com/992198 for further context. NavigationController* controller = - frame_tree_node_->navigator()->GetController(); + frame_tree_node_->navigator().GetController(); NavigationEntryImpl* nav_entry; if (controller && (nav_entry = static_cast<NavigationEntryImpl*>( @@ -2179,7 +2426,10 @@ void NavigationRequest::OnResponseStarted( frame_entry->committed_origin(), frame_entry->referrer(), frame_entry->initiator_origin(), frame_entry->redirect_chain(), frame_entry->page_state(), frame_entry->method(), - frame_entry->post_id(), frame_entry->blob_url_loader_factory()); + frame_entry->post_id(), frame_entry->blob_url_loader_factory(), + frame_entry->web_bundle_navigation_info() + ? frame_entry->web_bundle_navigation_info()->Clone() + : nullptr); } } @@ -2208,6 +2458,10 @@ void NavigationRequest::OnResponseStarted( net::Error net_error = CheckContentSecurityPolicy( was_redirected_ /* has_followed_redirect */, false /* url_upgraded_after_redirect */, true /* is_response_check */); + DCHECK_NE(net_error, net::ERR_BLOCKED_BY_CLIENT); + // TODO(https://crbug.com/1090859): Remove this once the bug has been fixed. + if (net_error == net::ERR_BLOCKED_BY_CLIENT) + base::debug::DumpWithoutCrashing(); if (net_error != net::OK) { OnRequestFailedInternal(network::URLLoaderCompletionStatus(net_error), false /* skip_throttles */, @@ -2240,10 +2494,7 @@ void NavigationRequest::OnRequestFailedInternal( bool skip_throttles, const base::Optional<std::string>& error_page_content, bool collapse_frame) { - DCHECK(state_ == WILL_START_NAVIGATION || state_ == WILL_START_REQUEST || - state_ == WILL_REDIRECT_REQUEST || state_ == WILL_PROCESS_RESPONSE || - state_ == DID_COMMIT || state_ == CANCELING || - state_ == WILL_FAIL_REQUEST); + CheckStateTransition(WILL_FAIL_REQUEST); DCHECK(!(status.error_code == net::ERR_ABORTED && error_page_content.has_value())); ScopedNavigationRequestCrashKeys crash_keys(this); @@ -2261,7 +2512,7 @@ void NavigationRequest::OnRequestFailedInternal( // TODO(https://crbug.com/757633): Check that ssl_info.has_value() if // net_error is a certificate error. EnterChildTraceEvent("OnRequestFailed", this, "error", status.error_code); - state_ = WILL_FAIL_REQUEST; + SetState(WILL_FAIL_REQUEST); processing_navigation_throttle_ = false; // Ensure the pending entry also gets discarded if it has no other active @@ -2356,12 +2607,12 @@ bool NavigationRequest::ShouldKeepErrorPageInCurrentProcess(int net_error) { // URLs should be allowed to transfer away from the current process, which // didn't request the navigation and may have a higher privilege level // than the blocked destination. - return net_error == net::ERR_BLOCKED_BY_CLIENT && !browser_initiated(); + return net::IsRequestBlockedError(net_error) && !browser_initiated(); } void NavigationRequest::OnRequestStarted(base::TimeTicks timestamp) { - frame_tree_node_->navigator()->LogResourceRequestTime(timestamp, - common_params_->url); + frame_tree_node_->navigator().LogResourceRequestTime(timestamp, + common_params_->url); } namespace { @@ -2391,8 +2642,7 @@ void NavigationRequest::OnStartChecksComplete( result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { #if DCHECK_IS_ON() if (result.action() == NavigationThrottle::BLOCK_REQUEST) { - DCHECK(result.net_error_code() == net::ERR_BLOCKED_BY_CLIENT || - result.net_error_code() == net::ERR_BLOCKED_BY_ADMINISTRATOR); + DCHECK(net::IsRequestBlockedError(result.net_error_code())); } // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE. else if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE) { @@ -2407,8 +2657,8 @@ void NavigationRequest::OnStartChecksComplete( // is no onbeforeunload handler or if a NavigationThrottle cancelled it, // then this could cause reentrancy into NavigationController. So use a // PostTask to avoid that. - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&NavigationRequest::OnRequestFailedInternal, + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&NavigationRequest::OnRequestFailedInternal, weak_factory_.GetWeakPtr(), network::URLLoaderCompletionStatus( result.net_error_code()), @@ -2433,7 +2683,7 @@ void NavigationRequest::OnStartChecksComplete( SetExpectedProcess(navigating_frame_host->GetProcess()); BrowserContext* browser_context = - frame_tree_node_->navigator()->GetController()->GetBrowserContext(); + frame_tree_node_->navigator().GetController()->GetBrowserContext(); StoragePartition* partition = BrowserContext::GetStoragePartition( browser_context, navigating_frame_host->GetSiteInstance()); DCHECK(partition); @@ -2465,9 +2715,9 @@ void NavigationRequest::OnStartChecksComplete( .application_cache_enabled) { // The final process id won't be available until // NavigationRequest::ReadyToCommitNavigation. - appcache_handle_.reset(new AppCacheNavigationHandle( + appcache_handle_ = std::make_unique<AppCacheNavigationHandle>( static_cast<ChromeAppCacheService*>(partition->GetAppCacheService()), - ChildProcessHost::kInvalidUniqueID)); + ChildProcessHost::kInvalidUniqueID); } } @@ -2570,6 +2820,11 @@ void NavigationRequest::OnServiceWorkerAccessed( GetDelegate()->OnServiceWorkerAccessed(this, scope, allowed); } +base::Optional<network::mojom::WebSandboxFlags> +NavigationRequest::SandboxFlagsToCommit() { + return sandbox_flags_to_commit_; +} + void NavigationRequest::OnRedirectChecksComplete( NavigationThrottle::ThrottleCheckResult result) { DCHECK(result.action() != NavigationThrottle::DEFER); @@ -2595,8 +2850,7 @@ void NavigationRequest::OnRedirectChecksComplete( if (result.action() == NavigationThrottle::BLOCK_REQUEST || result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) { - DCHECK(result.net_error_code() == net::ERR_BLOCKED_BY_CLIENT || - result.net_error_code() == net::ERR_BLOCKED_BY_ADMINISTRATOR); + DCHECK(net::IsRequestBlockedError(result.net_error_code())); OnRequestFailedInternal( network::URLLoaderCompletionStatus(result.net_error_code()), true /* skip_throttles */, result.error_page_content(), collapse_frame); @@ -2616,7 +2870,7 @@ void NavigationRequest::OnRedirectChecksComplete( // Add any required Client Hints to the current request. BrowserContext* browser_context = - frame_tree_node_->navigator()->GetController()->GetBrowserContext(); + frame_tree_node_->navigator().GetController()->GetBrowserContext(); ClientHintsControllerDelegate* client_hints_delegate = browser_context->GetClientHintsControllerDelegate(); if (client_hints_delegate) { @@ -2695,7 +2949,7 @@ void NavigationRequest::OnWillProcessResponseChecksComplete( resource_request->trusted_params->isolation_info = GetIsolationInfo(); BrowserContext* browser_context = - frame_tree_node_->navigator()->GetController()->GetBrowserContext(); + frame_tree_node_->navigator().GetController()->GetBrowserContext(); DownloadManagerImpl* download_manager = static_cast<DownloadManagerImpl*>( BrowserContext::GetDownloadManager(browser_context)); download_manager->InterceptNavigation( @@ -2797,6 +3051,7 @@ void NavigationRequest::CommitErrorPage( } } + sandbox_flags_to_commit_ = ComputeSandboxFlagsToCommit(); ReadyToCommitNavigation(true); render_frame_host_->FailedNavigation(this, *common_params_, *commit_params_, has_stale_copy_in_cache_, net_error_, @@ -2865,10 +3120,12 @@ void NavigationRequest::CommitNavigation() { } } + sandbox_flags_to_commit_ = ComputeSandboxFlagsToCommit(); CreateCoepReporter(render_frame_host_->GetProcess()->GetStoragePartition()); + CreateCoopReporter(render_frame_host_->GetProcess()->GetStoragePartition()); - blink::mojom::ServiceWorkerProviderInfoForClientPtr - service_worker_provider_info; + blink::mojom::ServiceWorkerContainerInfoForClientPtr + service_worker_container_info; if (service_worker_handle_) { DCHECK(coep_reporter()); mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> @@ -2880,12 +3137,23 @@ void NavigationRequest::CommitNavigation() { render_frame_host_->GetProcess()->GetID(), render_frame_host_->GetRoutingID(), render_frame_host_->cross_origin_embedder_policy(), - std::move(reporter_remote), &service_worker_provider_info); + std::move(reporter_remote), &service_worker_container_info); } - if (web_bundle_handle_ && web_bundle_handle_->navigation_info()) { - web_bundle_navigation_info_ = - web_bundle_handle_->navigation_info()->Clone(); + if (web_bundle_handle_) { + // Check whether the page was served from a web bundle. + if (web_bundle_handle_->navigation_info()) { + // If the page was served from a web bundle, sets + // |web_bundle_navigation_info_| which will be passed to + // the FrameNavigationEntry of the navigation, and will be used for + // history navigations. + web_bundle_navigation_info_ = + web_bundle_handle_->navigation_info()->Clone(); + } else { + // If the page was not served from a web bundle, clears + // |web_bundle_handle_| not to pass it to |render_frame_host_|. + web_bundle_handle_.reset(); + } } auto common_params = common_params_->Clone(); @@ -2902,8 +3170,9 @@ void NavigationRequest::CommitNavigation() { std::move(response_head), std::move(response_body_), std::move(url_loader_client_endpoints_), is_view_source_, std::move(subresource_loader_params_), std::move(subresource_overrides_), - std::move(service_worker_provider_info), devtools_navigation_token_, + std::move(service_worker_container_info), devtools_navigation_token_, std::move(web_bundle_handle_)); + UpdateNavigationHandleTimingsOnCommitSent(); // Give SpareRenderProcessHostManager a heads-up about the most recently used // BrowserContext. This is mostly needed to make sure the spare is warmed-up @@ -2921,8 +3190,8 @@ void NavigationRequest::ResetExpectedProcess() { RenderProcessHost::FromID(expected_render_process_host_id_); if (process) { RenderProcessHostImpl::RemoveExpectedNavigationToSite( - frame_tree_node()->navigator()->GetController()->GetBrowserContext(), - process, site_url_); + frame_tree_node()->navigator().GetController()->GetBrowserContext(), + process, site_info_); process->RemoveObserver(this); } expected_render_process_host_id_ = ChildProcessHost::kInvalidUniqueID; @@ -2943,12 +3212,12 @@ void NavigationRequest::SetExpectedProcess( return; // Keep track of the speculative RenderProcessHost and tell it to expect a - // navigation to |site_url_|. + // navigation to |site_info_|. expected_render_process_host_id_ = expected_process->GetID(); expected_process->AddObserver(this); RenderProcessHostImpl::AddExpectedNavigationToSite( - frame_tree_node()->navigator()->GetController()->GetBrowserContext(), - expected_process, site_url_); + frame_tree_node()->navigator().GetController()->GetBrowserContext(), + expected_process, site_info_); } void NavigationRequest::RenderProcessHostDestroyed(RenderProcessHost* host) { @@ -2960,23 +3229,66 @@ void NavigationRequest::RenderProcessExited( RenderProcessHost* host, const ChildProcessTerminationInfo& info) {} -void NavigationRequest::UpdateSiteURL( +void NavigationRequest::UpdateNavigationHandleTimingsOnResponseReceived( + bool is_first_response) { + base::TimeTicks loader_callback_time = base::TimeTicks::Now(); + + if (is_first_response) { + DCHECK(navigation_handle_timing_->first_request_start_time.is_null()); + DCHECK(navigation_handle_timing_->first_response_start_time.is_null()); + DCHECK(navigation_handle_timing_->first_loader_callback_time.is_null()); + navigation_handle_timing_->first_request_start_time = + response_head_->load_timing.send_start; + navigation_handle_timing_->first_response_start_time = + response_head_->load_timing.receive_headers_start; + navigation_handle_timing_->first_loader_callback_time = + loader_callback_time; + } + + navigation_handle_timing_->final_request_start_time = + response_head_->load_timing.send_start; + navigation_handle_timing_->final_response_start_time = + response_head_->load_timing.receive_headers_start; + navigation_handle_timing_->final_loader_callback_time = loader_callback_time; + + // 103 Early Hints experiment (https://crbug.com/1093693). + if (is_first_response) { + DCHECK(navigation_handle_timing_->early_hints_for_first_request_time + .is_null()); + navigation_handle_timing_->early_hints_for_first_request_time = + response_head_->load_timing.first_early_hints_time; + } + navigation_handle_timing_->early_hints_for_final_request_time = + response_head_->load_timing.first_early_hints_time; + + // |navigation_commit_sent_time| will be updated by + // UpdateNavigationHandleTimingsOnCommitSent() later. + DCHECK(navigation_handle_timing_->navigation_commit_sent_time.is_null()); +} + +void NavigationRequest::UpdateNavigationHandleTimingsOnCommitSent() { + DCHECK(navigation_handle_timing_->navigation_commit_sent_time.is_null()); + navigation_handle_timing_->navigation_commit_sent_time = + base::TimeTicks::Now(); +} + +void NavigationRequest::UpdateSiteInfo( RenderProcessHost* post_redirect_process) { - GURL new_site_url = GetSiteForCommonParamsURL(); + SiteInfo new_site_info = GetSiteInfoForCommonParamsURL(); int post_redirect_process_id = post_redirect_process ? post_redirect_process->GetID() : ChildProcessHost::kInvalidUniqueID; - if (new_site_url == site_url_ && + if (new_site_info == site_info_ && post_redirect_process_id == expected_render_process_host_id_) { return; } - // Stop expecting a navigation to the current site URL in the current expected + // Stop expecting a navigation to the current SiteInfo in the current expected // process. ResetExpectedProcess(); - // Update the site URL and the expected process. - site_url_ = new_site_url; + // Update the SiteInfo and the expected process. + site_info_ = new_site_info; SetExpectedProcess(post_redirect_process); } @@ -3028,11 +3340,8 @@ net::Error NavigationRequest::CheckCSPDirectives( if (navigate_to_allowed && frame_src_allowed) return net::OK; - // If 'frame-src' fails, ERR_BLOCKED_BY_CLIENT is used instead. - // If both checks fail, ERR_BLOCKED_BY_CLIENT is used to keep the existing - // behaviour before 'navigate-to' was introduced. if (!frame_src_allowed) - return net::ERR_BLOCKED_BY_CLIENT; + return net::ERR_BLOCKED_BY_CSP; // net::ERR_ABORTED is used to ensure that the navigation is cancelled // when the 'navigate-to' directive check is failed. This is a better user @@ -3095,6 +3404,8 @@ net::Error NavigationRequest::CheckContentSecurityPolicy( parent->ContentSecurityPolicies())) { upgrade_if_insecure_ = true; network::UpgradeInsecureRequest(&common_params_->url); + common_params_->referrer = Referrer::SanitizeForRequest( + common_params_->url, *common_params_->referrer); commit_params_->original_url = common_params_->url; } } @@ -3192,7 +3503,7 @@ NavigationRequest::AboutSrcDocCheckResult NavigationRequest::CheckAboutSrcDoc() void NavigationRequest::UpdateCommitNavigationParamsHistory() { NavigationController* navigation_controller = - frame_tree_node_->navigator()->GetController(); + frame_tree_node_->navigator().GetController(); commit_params_->current_history_list_offset = navigation_controller->GetCurrentEntryIndex(); commit_params_->current_history_list_length = @@ -3207,7 +3518,7 @@ void NavigationRequest::OnRendererAbortedNavigation() { if (IsWaitingToCommit()) { render_frame_host_->NavigationRequestCancelled(this); } else { - frame_tree_node_->navigator()->CancelNavigation(frame_tree_node_); + frame_tree_node_->navigator().CancelNavigation(frame_tree_node_); } // Do not add code after this, NavigationRequest has been destroyed. @@ -3236,7 +3547,7 @@ int NavigationRequest::EstimateHistoryOffset() { return 0; NavigationController* controller = - frame_tree_node_->navigator()->GetController(); + frame_tree_node_->navigator().GetController(); if (!controller) // Interstitial page. return 1; @@ -3331,7 +3642,7 @@ void NavigationRequest::OnWillStartRequestProcessed( DCHECK(processing_navigation_throttle_); processing_navigation_throttle_ = false; if (result.action() != NavigationThrottle::PROCEED) - state_ = CANCELING; + SetState(CANCELING); if (complete_callback_for_testing_ && std::move(complete_callback_for_testing_).Run(result)) { @@ -3359,7 +3670,7 @@ void NavigationRequest::OnWillRedirectRequestProcessed( GetDelegate()->DidRedirectNavigation(this); } } else { - state_ = CANCELING; + SetState(CANCELING); } if (complete_callback_for_testing_ && @@ -3382,7 +3693,7 @@ void NavigationRequest::OnWillFailRequestProcessed( result = NavigationThrottle::ThrottleCheckResult( NavigationThrottle::PROCEED, net_error_); } else { - state_ = CANCELING; + SetState(CANCELING); } if (complete_callback_for_testing_ && @@ -3417,7 +3728,7 @@ void NavigationRequest::OnWillProcessResponseProcessed( if (!weak_self) return; } else { - state_ = CANCELING; + SetState(CANCELING); } if (complete_callback_for_testing_ && @@ -3431,7 +3742,7 @@ void NavigationRequest::OnWillProcessResponseProcessed( } NavigatorDelegate* NavigationRequest::GetDelegate() const { - return frame_tree_node()->navigator()->GetDelegate(); + return frame_tree_node()->navigator().GetDelegate(); } void NavigationRequest::Resume(NavigationThrottle* resuming_throttle) { @@ -3485,7 +3796,7 @@ void NavigationRequest::CancelDeferredNavigationInternal( EnterChildTraceEvent("CancelDeferredNavigation", this); NavigationState old_state = state_; - state_ = CANCELING; + SetState(CANCELING); if (complete_callback_for_testing_ && std::move(complete_callback_for_testing_).Run(result)) { return; @@ -3516,7 +3827,7 @@ void NavigationRequest::WillStartRequest() { DCHECK_EQ(state_, WILL_START_REQUEST); if (IsSelfReferentialURL()) { - state_ = CANCELING; + SetState(CANCELING); if (complete_callback_for_testing_ && std::move(complete_callback_for_testing_) .Run(NavigationThrottle::CANCEL)) { @@ -3552,10 +3863,10 @@ void NavigationRequest::WillRedirectRequest( EnterChildTraceEvent("WillRedirectRequest", this, "url", common_params_->url.possibly_invalid_spec()); UpdateStateFollowingRedirect(new_referrer_url); - UpdateSiteURL(post_redirect_process); + UpdateSiteInfo(post_redirect_process); if (IsSelfReferentialURL()) { - state_ = CANCELING; + SetState(CANCELING); if (complete_callback_for_testing_ && std::move(complete_callback_for_testing_) .Run(NavigationThrottle::CANCEL)) { @@ -3578,7 +3889,7 @@ void NavigationRequest::WillRedirectRequest( void NavigationRequest::WillFailRequest() { EnterChildTraceEvent("WillFailRequest", this); - state_ = WILL_FAIL_REQUEST; + SetState(WILL_FAIL_REQUEST); processing_navigation_throttle_ = true; // Notify each throttle of the request. @@ -3649,10 +3960,10 @@ void NavigationRequest::DidCommitNavigation( if (params.base_url.spec() == kUnreachableWebDataURL || net_error_ != net::OK) { EnterChildTraceEvent("DidCommitNavigation: error page", this); - state_ = DID_COMMIT_ERROR_PAGE; + SetState(DID_COMMIT_ERROR_PAGE); } else { EnterChildTraceEvent("DidCommitNavigation", this); - state_ = DID_COMMIT; + SetState(DID_COMMIT); } StopCommitTimeout(); @@ -3661,7 +3972,7 @@ void NavigationRequest::DidCommitNavigation( // The renderer already knows locally about it because we sent an empty name // at frame creation time. The renderer has now committed the page and we can // safely enforce the empty name on the browser side. - if (require_coop_browsing_instance_swap()) { + if (coop_status().require_browsing_instance_swap) { std::string name, unique_name; // "COOP swaps" only affect main frames, that have an empty unique name. DCHECK(frame_tree_node_->unique_name().empty()); @@ -3694,12 +4005,18 @@ void NavigationRequest::DidCommitNavigation( DCHECK(!IsSameDocument() || !frame_tree_node()->is_collapsed()); frame_tree_node()->SetCollapsed(false); } + + if (service_worker_handle_) { + // Notify the service worker navigation handle that the navigation finished + // committing. + service_worker_handle_->OnEndNavigationCommit(); + } } -GURL NavigationRequest::GetSiteForCommonParamsURL() const { +SiteInfo NavigationRequest::GetSiteInfoForCommonParamsURL() const { // TODO(alexmos): Using |starting_site_instance_|'s IsolationContext may not // be correct for cross-BrowsingInstance redirects. - return SiteInstanceImpl::GetSiteForURL( + return SiteInstanceImpl::ComputeSiteInfo( starting_site_instance_->GetIsolationContext(), common_params_->url); } @@ -3725,7 +4042,7 @@ void NavigationRequest::UpdateStateFollowingRedirect( was_redirected_ = true; redirect_chain_.push_back(common_params_->url); - state_ = WILL_REDIRECT_REQUEST; + SetState(WILL_REDIRECT_REQUEST); processing_navigation_throttle_ = true; #if defined(OS_ANDROID) @@ -3759,7 +4076,7 @@ bool NavigationRequest::NeedsUrlLoader() { void NavigationRequest::ReadyToCommitNavigation(bool is_error) { EnterChildTraceEvent("ReadyToCommitNavigation", this); - state_ = READY_TO_COMMIT; + SetState(READY_TO_COMMIT); ready_to_commit_time_ = base::TimeTicks::Now(); RestartCommitTimeout(); @@ -4034,12 +4351,8 @@ base::TimeTicks NavigationRequest::NavigationInputStart() { return common_params().input_start; } -base::TimeTicks NavigationRequest::FirstRequestStart() { - return first_request_start_; -} - -base::TimeTicks NavigationRequest::FirstResponseStart() { - return first_response_start_; +const NavigationHandleTiming& NavigationRequest::GetNavigationHandleTiming() { + return *navigation_handle_timing_; } bool NavigationRequest::IsPost() { @@ -4050,6 +4363,12 @@ const blink::mojom::Referrer& NavigationRequest::GetReferrer() { return *sanitized_referrer_; } +void NavigationRequest::SetReferrer(blink::mojom::ReferrerPtr referrer) { + DCHECK(state_ == WILL_START_REQUEST || state_ == WILL_REDIRECT_REQUEST); + sanitized_referrer_ = std::move(referrer); + common_params_->referrer = sanitized_referrer_.Clone(); +} + bool NavigationRequest::HasUserGesture() { return common_params().has_user_gesture; } @@ -4240,7 +4559,7 @@ void NavigationRequest::SetIsOverridingUserAgent(bool override_ua) { ? GetContentClient()->browser()->GetUserAgent() : user_agent_override); BrowserContext* browser_context = - frame_tree_node_->navigator()->GetController()->GetBrowserContext(); + frame_tree_node_->navigator().GetController()->GetBrowserContext(); ClientHintsControllerDelegate* client_hints_delegate = browser_context->GetClientHintsControllerDelegate(); if (client_hints_delegate) { @@ -4298,8 +4617,8 @@ void NavigationRequest::RestartBackForwardCachedNavigation() { "NavigationRequest::RestartBackForwardCachedNavigation"); CHECK(IsServedFromBackForwardCache()); restarting_back_forward_cached_navigation_ = true; - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&NavigationRequest::RestartBackForwardCachedNavigationImpl, weak_factory_.GetWeakPtr())); } @@ -4328,27 +4647,44 @@ void NavigationRequest::ForceEnableOriginTrials( } base::Optional<network::mojom::BlockedByResponseReason> -NavigationRequest::IsBlockedByCorp() { - if (!base::FeatureList::IsEnabled( - network::features::kCrossOriginEmbedderPolicy)) { - return base::nullopt; - } - // https://mikewest.github.io/corpp/#integration-html - auto* parent_frame = GetParentFrame(); - if (!parent_frame) { - return base::nullopt; +NavigationRequest::IsBlockedByResponse() { + if (base::FeatureList::IsEnabled( + network::features::kCrossOriginOpenerPolicy)) { + // Popups with a sandboxing flag, inherited from their opener, are not + // allowed to navigate to a document with a Cross-Origin-Opener-Policy that + // is not "unsafe-none". This ensures a COOP document does not inherit any + // property from an opener. + // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e + if (response_head_->parsed_headers->cross_origin_opener_policy.value != + network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone && + (frame_tree_node_->pending_frame_policy().sandbox_flags != + network::mojom::WebSandboxFlags::kNone)) { + return network::mojom::BlockedByResponseReason:: + kCoopSandboxedIFrameCannotNavigateToCoopPage; + } } - const auto& url = common_params_->url; - // Some special URLs not loaded using the network are inheriting the - // Cross-Origin-Embedder-Policy header from their parent. - if (url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme)) { - return base::nullopt; + + if (base::FeatureList::IsEnabled( + network::features::kCrossOriginEmbedderPolicy)) { + // https://mikewest.github.io/corpp/#integration-html + auto* parent_frame = GetParentFrame(); + if (!parent_frame) { + return base::nullopt; + } + const auto& url = common_params_->url; + // Some special URLs not loaded using the network are inheriting the + // Cross-Origin-Embedder-Policy header from their parent. + if (url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme)) { + return base::nullopt; + } + return network::CrossOriginResourcePolicy::IsNavigationBlocked( + url, redirect_chain_[0], parent_frame->GetLastCommittedOrigin(), + *response_head_, parent_frame->GetLastCommittedOrigin(), + parent_frame->cross_origin_embedder_policy(), + parent_frame->coep_reporter()); } - return network::CrossOriginResourcePolicy::IsNavigationBlocked( - url, redirect_chain_[0], parent_frame->GetLastCommittedOrigin(), - *response_head_, parent_frame->GetLastCommittedOrigin(), - parent_frame->cross_origin_embedder_policy(), - parent_frame->coep_reporter()); + + return base::nullopt; } std::unique_ptr<PeakGpuMemoryTracker> @@ -4358,7 +4694,7 @@ NavigationRequest::TakePeakGpuMemoryTracker() { std::string NavigationRequest::GetUserAgentOverride() { return IsOverridingUserAgent() ? frame_tree_node_->navigator() - ->GetDelegate() + .GetDelegate() ->GetUserAgentOverride() .ua_string_override : std::string(); @@ -4366,7 +4702,7 @@ std::string NavigationRequest::GetUserAgentOverride() { NavigationControllerImpl* NavigationRequest::GetNavigationController() { return static_cast<NavigationControllerImpl*>( - frame_tree_node_->navigator()->GetController()); + frame_tree_node_->navigator().GetController()); } mojo::PendingRemote<network::mojom::CookieAccessObserver> @@ -4403,4 +4739,144 @@ NavigationRequest::TakeCookieObservers() { return cookie_observers_.TakeReceivers(); } +network::mojom::WebSandboxFlags +NavigationRequest::ComputeSandboxFlagsToCommit() { + DCHECK(commit_params_); + DCHECK(!HasCommitted()); + DCHECK(!IsErrorPage()); + + network::mojom::WebSandboxFlags out; + if (commit_params_->frame_policy) { + // This corresponds to the sandbox policy of the frame embedding the + // document that were active when the navigation started. + out = commit_params_->frame_policy->sandbox_flags; + } else { + // The document doesn't have a sandbox policy. This case should in theory + // contains only the navigations that are: + // - main frame. + // - browser initiated. + // - non-history. + // - non-error. + // + // TODO(arthursonzogni): In practice, a few navigations not complying with + // one of the 4 items above are using this path. They must be identified + // and removed. A set of DCHECK must be added. + out = network::mojom::WebSandboxFlags::kNone; + } + + // The response can also restrict the policy further. + if (response_head_) { + for (const auto& csp : + response_head_->parsed_headers->content_security_policy) { + out |= ~(csp->sandbox); + } + } + + return out; +} + +const CrossOriginOpenerPolicyStatus& NavigationRequest::coop_status() const { + return coop_status_; +} + +void NavigationRequest::CheckStateTransition(NavigationState state) const { +#if DCHECK_IS_ON() + // See + // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/navigation-request-navigation-state.png + // clang-format off + static const base::NoDestructor<StateTransitions<NavigationState>> + transitions(StateTransitions<NavigationState>({ + {NOT_STARTED, { + WAITING_FOR_RENDERER_RESPONSE, + WILL_START_NAVIGATION, + WILL_START_REQUEST, + }}, + {WAITING_FOR_RENDERER_RESPONSE, { + WILL_START_NAVIGATION, + }}, + {WILL_START_NAVIGATION, { + WILL_START_REQUEST, + WILL_FAIL_REQUEST, + }}, + {WILL_START_REQUEST, { + WILL_REDIRECT_REQUEST, + WILL_PROCESS_RESPONSE, + READY_TO_COMMIT, + DID_COMMIT, + CANCELING, + WILL_FAIL_REQUEST, + DID_COMMIT_ERROR_PAGE, + }}, + {WILL_REDIRECT_REQUEST, { + WILL_REDIRECT_REQUEST, + WILL_PROCESS_RESPONSE, + CANCELING, + WILL_FAIL_REQUEST, + }}, + {WILL_PROCESS_RESPONSE, { + READY_TO_COMMIT, + CANCELING, + WILL_FAIL_REQUEST, + }}, + {READY_TO_COMMIT, { + NOT_STARTED, + DID_COMMIT, + DID_COMMIT_ERROR_PAGE, + }}, + {CANCELING, { + READY_TO_COMMIT, + WILL_FAIL_REQUEST, + }}, + {WILL_FAIL_REQUEST, { + READY_TO_COMMIT, + CANCELING, + WILL_FAIL_REQUEST, + }}, + {DID_COMMIT, {}}, + {DID_COMMIT_ERROR_PAGE, {}}, + })); + // clang-format on + DCHECK_STATE_TRANSITION(transitions, state_, state); +#endif // DCHECK_IS_ON() +} + +void NavigationRequest::SetState(NavigationState state) { + CheckStateTransition(state); + state_ = state; +} + +void NavigationRequest::SanitizeCoopHeaders() { + // We blank out the COOP headers in a number of situations. + // - When the COOP flag is not enabled. + // - When the headers were not sent over HTTPS. + // - For subframes. + if (!base::FeatureList::IsEnabled( + network::features::kCrossOriginOpenerPolicy) || + !IsOriginSecure(common_params_->url) || !IsInMainFrame()) { + response_head_->parsed_headers->cross_origin_opener_policy = + network::CrossOriginOpenerPolicy(); + } +} + +void NavigationRequest::UpdateCoopStatus( + network::mojom::CrossOriginOpenerPolicyValue coop, + network::mojom::CrossOriginEmbedderPolicyValue coep) { + RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host(); + + bool cross_origin_policy_swap = + IsInMainFrame() && !common_params_->url.IsAboutBlank() && + ShouldSwapBrowsingInstanceForCrossOriginOpenerPolicy( + current_rfh->cross_origin_opener_policy().value, + current_rfh->cross_origin_embedder_policy().value, + current_rfh->GetLastCommittedOrigin(), + !current_rfh->has_committed_any_navigation(), coop, coep, + url::Origin::Create(common_params_->url)); + + if (cross_origin_policy_swap) { + coop_status_.require_browsing_instance_swap = true; + if (frame_tree_node_->opener()) + coop_status_.had_opener_before_browsing_instance_swap = true; + } +} + } // namespace content diff --git a/chromium/content/browser/frame_host/navigation_request.h b/chromium/content/browser/frame_host/navigation_request.h index cd7ee0d77bb..15e241680e7 100644 --- a/chromium/content/browser/frame_host/navigation_request.h +++ b/chromium/content/browser/frame_host/navigation_request.h @@ -23,6 +23,7 @@ #include "content/browser/initiator_csp_context.h" #include "content/browser/loader/navigation_url_loader_delegate.h" #include "content/browser/navigation_subresource_loader_params.h" +#include "content/browser/site_instance_impl.h" #include "content/browser/web_package/web_bundle_handle.h" #include "content/common/content_export.h" #include "content/common/navigation_params.h" @@ -45,6 +46,7 @@ #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/origin_policy.h" #include "services/network/public/mojom/blocked_by_response_reason.mojom-shared.h" +#include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" #if defined(OS_ANDROID) #include "base/android/scoped_java_ref.h" @@ -62,6 +64,7 @@ namespace content { class AppCacheNavigationHandle; class CrossOriginEmbedderPolicyReporter; +class CrossOriginOpenerPolicyReporter; class WebBundleHandleTracker; class WebBundleNavigationInfo; class FrameNavigationEntry; @@ -71,9 +74,28 @@ class NavigationUIData; class NavigatorDelegate; class PrefetchedSignedExchangeCache; class ServiceWorkerMainResourceHandle; -class SiteInstanceImpl; struct SubresourceLoaderParams; +// A structure that groups information about how COOP has interacted with the +// navigation. These are used to trigger a number of mechanisms such as name +// clearing or reporting. +struct CrossOriginOpenerPolicyStatus { + // Set to true whenever the Cross-Origin-Opener-Policy spec requires a + // "BrowsingContext group" swap: + // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e + // This forces the new RenderFrameHost to use a different BrowsingInstance + // than the current one. If other pages had JavaScript references to the + // Window object for the frame (via window.opener, window.open(), et cetera), + // those references will be broken; window.name will also be reset to an empty + // string. + bool require_browsing_instance_swap = false; + + // When a page has a reachable opener and COOP triggers a browsing instance + // swap we potentially break the page. This is one of the case that can be + // reported using the COOP reporting API. + bool had_opener_before_browsing_instance_swap = false; +}; + // A UI thread object that owns a navigation request until it commits. It // ensures the UI thread can start a navigation request in the // ResourceDispatcherHost (that lives on the IO thread). @@ -87,6 +109,7 @@ class CONTENT_EXPORT NavigationRequest private network::mojom::CookieAccessObserver { public: // Keeps track of the various stages of a NavigationRequest. + // To see what state transitions are allowed, see |SetState|. enum NavigationState { // Initial state. NOT_STARTED = 0, @@ -197,7 +220,8 @@ class CONTENT_EXPORT NavigationRequest RenderFrameHostImpl* render_frame_host, const FrameHostMsg_DidCommitProvisionalLoad_Params& params, std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter, - bool is_same_document); + bool is_same_document, + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info); static NavigationRequest* From(NavigationHandle* handle); @@ -216,6 +240,21 @@ class CONTENT_EXPORT NavigationRequest }; OptInIsolationCheckResult IsOptInIsolationRequested(const GURL& url); + // The origin isolation end result is determined early in the lifecycle of a + // NavigationRequest, but used late. In particular, we want to trigger use + // counters and console warnings once navigation has committed. + enum class OptInOriginIsolationEndResult { + kNotRequestedAndNotIsolated, + kNotRequestedButIsolated, + kRequestedViaOriginPolicyButNotIsolated, + kRequestedViaOriginPolicyAndIsolated, + kRequestedViaHeaderButNotIsolated, + kRequestedViaHeaderAndIsolated + }; + void DetermineOriginIsolationEndResult( + OptInIsolationCheckResult check_result); + void ProcessOriginIsolationEndResult(); + // NavigationHandle implementation: int64_t GetNavigationId() override; const GURL& GetURL() override; @@ -230,10 +269,10 @@ class CONTENT_EXPORT NavigationRequest RenderFrameHostImpl* GetParentFrame() override; base::TimeTicks NavigationStart() override; base::TimeTicks NavigationInputStart() override; - base::TimeTicks FirstRequestStart() override; - base::TimeTicks FirstResponseStart() override; + const NavigationHandleTiming& GetNavigationHandleTiming() override; bool IsPost() override; const blink::mojom::Referrer& GetReferrer() override; + void SetReferrer(blink::mojom::ReferrerPtr referrer) override; bool HasUserGesture() override; ui::PageTransition GetPageTransition() override; NavigationUIData* GetNavigationUIData() override; @@ -367,11 +406,11 @@ class CONTENT_EXPORT NavigationRequest // url we're navigating to. void SetExpectedProcess(RenderProcessHost* expected_process); - // Updates the destination site URL for this navigation. This is called on + // Updates the destination SiteInfo for this navigation. This is called on // redirects. |post_redirect_process| is the renderer process that should // handle the navigation following the redirect if it can be handled by an // existing RenderProcessHost. Otherwise, it should be null. - void UpdateSiteURL(RenderProcessHost* post_redirect_process); + void UpdateSiteInfo(RenderProcessHost* post_redirect_process); int nav_entry_id() const { return nav_entry_id_; } @@ -580,20 +619,17 @@ class CONTENT_EXPORT NavigationRequest } network::mojom::ClientSecurityStatePtr TakeClientSecurityState(); - bool require_coop_browsing_instance_swap() const { - return require_coop_browsing_instance_swap_; - } - bool ua_change_requires_reload() const { return ua_change_requires_reload_; } - void set_require_coop_browsing_instance_swap() { - require_coop_browsing_instance_swap_ = true; - } CrossOriginEmbedderPolicyReporter* coep_reporter() { return coep_reporter_.get(); } + CrossOriginOpenerPolicyReporter* coop_reporter() { + return coop_reporter_.get(); + } std::unique_ptr<CrossOriginEmbedderPolicyReporter> TakeCoepReporter(); + std::unique_ptr<CrossOriginOpenerPolicyReporter> TakeCoopReporter(); // Returns UKM SourceId for the page we are navigating away from. // Equal to GetRenderFrameHost()->GetPageUkmSourceId() for subframe @@ -614,6 +650,22 @@ class CONTENT_EXPORT NavigationRequest std::vector<mojo::PendingReceiver<network::mojom::CookieAccessObserver>> TakeCookieObservers() WARN_UNUSED_RESULT; + // The sandbox policy of the document to be loaded. This returns nullopt for + // navigations that haven't reached the 'ReadyToCommit' stage yet. In + // particular, this returns nullopt for same-document navigations. + // + // TODO(arthursonzogni): After RenderDocument, this can be computed and stored + // directly into the RenderDocumentHost. + base::Optional<network::mojom::WebSandboxFlags> SandboxFlagsToCommit(); + + // Returns the coop status information relevant to the current navigation. + const CrossOriginOpenerPolicyStatus& coop_status() const; + + // Whether the navigation was sent to be committed in a renderer by the + // RenderFrameHost. This can either be for the commit of a successful + // navigation or an error page. + bool IsWaitingToCommit(); + private: friend class NavigationRequestTest; @@ -631,7 +683,8 @@ class CONTENT_EXPORT NavigationRequest mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client, mojo::PendingRemote<blink::mojom::NavigationInitiator> navigation_initiator, - RenderFrameHostImpl* rfh_restored_from_back_forward_cache); + RenderFrameHostImpl* rfh_restored_from_back_forward_cache, + GlobalFrameRoutingId initiator_routing_id); // Checks if the response requests an isolated origin (using either origin // policy or the Origin-Isolation header), and if so opts in the origin to be @@ -702,7 +755,7 @@ class CONTENT_EXPORT NavigationRequest // Checks if CSP allows the navigation. This will check the frame-src and // navigate-to directives. // Returns net::OK if the checks pass, and net::ERR_ABORTED or - // net::ERR_BLOCKED_BY_CLIENT depending on which checks fail. + // net::ERR_BLOCKED_BY_CSP depending on which checks fail. net::Error CheckCSPDirectives( RenderFrameHostImpl* parent, bool has_followed_redirect, @@ -849,11 +902,13 @@ class CONTENT_EXPORT NavigationRequest const ChildProcessTerminationInfo& info) override; void RenderProcessHostDestroyed(RenderProcessHost* host) override; - void RecordNavigationMetrics() const; + // Updates navigation handle timings. + void UpdateNavigationHandleTimingsOnResponseReceived(bool is_first_response); + void UpdateNavigationHandleTimingsOnCommitSent(); - // Helper function that computes the site URL for |common_params_.url|. - // Note: |site_url_| should only be updated with the result of this function. - GURL GetSiteForCommonParamsURL() const; + // Helper function that computes the SiteInfo for |common_params_.url|. + // Note: |site_info_| should only be updated with the result of this function. + SiteInfo GetSiteInfoForCommonParamsURL() const; // Updates the state of the navigation handle after encountering a server // redirect. @@ -890,11 +945,6 @@ class CONTENT_EXPORT NavigationRequest // |state_| and inform the delegate. void ReadyToCommitNavigation(bool is_error); - // Whether the navigation was sent to be committed in a renderer by the - // RenderFrameHost. This can either be for the commit of a successful - // navigation or an error page. - bool IsWaitingToCommit(); - // Called if READY_TO_COMMIT -> COMMIT state transition takes an unusually // long time. void OnCommitTimeout(); @@ -933,8 +983,9 @@ class CONTENT_EXPORT NavigationRequest void ForceEnableOriginTrials(const std::vector<std::string>& trials) override; void CreateCoepReporter(StoragePartition* storage_partition); + void CreateCoopReporter(StoragePartition* storage_partition); - base::Optional<network::mojom::BlockedByResponseReason> IsBlockedByCorp(); + base::Optional<network::mojom::BlockedByResponseReason> IsBlockedByResponse(); bool IsOverridingUserAgent() const { return commit_params_->is_overriding_user_agent || entry_overrides_ua_; @@ -956,7 +1007,27 @@ class CONTENT_EXPORT NavigationRequest // NavigationRequest is in. NavigationControllerImpl* GetNavigationController(); - FrameTreeNode* frame_tree_node_; + // Compute the sandbox policy of the document to be loaded. Called once when + // reaching the 'ReadyToCommit' stage. + network::mojom::WebSandboxFlags ComputeSandboxFlagsToCommit(); + + // DCHECK that tranistioning from the current state to |state| valid. This + // does nothing in non-debug builds. + void CheckStateTransition(NavigationState state) const; + + // Set |state_| to |state| and also DCHECK that this state transition is + // valid. + void SetState(NavigationState state); + + // Make sure COOP is relevant or clear the COOP headers. + void SanitizeCoopHeaders(); + + // Updates the internal coop_status assuming the page navigated to has + // cross-origin-opener-policy |coop| and cross-origin-embedder-policy |coep|. + void UpdateCoopStatus(network::mojom::CrossOriginOpenerPolicyValue coop, + network::mojom::CrossOriginEmbedderPolicyValue coep); + + FrameTreeNode* const frame_tree_node_; // Value of |is_for_commit| supplied to the constructor. const bool is_for_commit_; @@ -1006,11 +1077,11 @@ class CONTENT_EXPORT NavigationRequest // creation time. scoped_refptr<SiteInstanceImpl> source_site_instance_; scoped_refptr<SiteInstanceImpl> dest_site_instance_; - RestoreType restore_type_ = RestoreType::NONE; - ReloadType reload_type_ = ReloadType::NONE; + const RestoreType restore_type_; + const ReloadType reload_type_; + const int nav_entry_id_; bool is_view_source_; int bindings_; - int nav_entry_id_ = 0; bool entry_overrides_ua_ = false; // Set to true if SetIsOverridingUserAgent() is called. @@ -1035,7 +1106,7 @@ class CONTENT_EXPORT NavigationRequest // IPC. When true, main frame navigations should not commit in a different // process (unless asked by the content/ embedder). When true, the renderer // process expects to be notified if the navigation is aborted. - bool from_begin_navigation_; + const bool from_begin_navigation_; // Holds objects received from OnResponseStarted while the WillProcessResponse // checks are performed by the NavigationHandle. Once the checks have been @@ -1064,10 +1135,11 @@ class CONTENT_EXPORT NavigationRequest // commit. int expected_render_process_host_id_; - // The site URL of this navigation, as obtained from SiteInstance::GetSiteURL. - GURL site_url_; + // The SiteInfo of this navigation, as obtained from + // SiteInstanceImpl::ComputeSiteInfo(). + SiteInfo site_info_; - std::unique_ptr<InitiatorCSPContext> initiator_csp_context_; + const std::unique_ptr<InitiatorCSPContext> initiator_csp_context_; base::OnceClosure on_start_checks_complete_closure_; @@ -1153,28 +1225,8 @@ class CONTENT_EXPORT NavigationRequest // is enabled or TrustableWebBundleFileUrl switch is set. std::unique_ptr<WebBundleHandleTracker> web_bundle_handle_tracker_; - // The time the first HTTP request was sent. This is filled with - // net::LoadTimingInfo::send_start during navigation. - // - // In some cases, this can be the time an internal request started that did - // not go to the networking layer. For example, - // - Service Worker: the time the fetch event was ready to be dispatched, see - // content::ServiceWorkerNavigationLoader::DidPrepareFetchEvent()). - // - HSTS: the time the internal redirect was handled. - // - Signed Exchange: the time the SXG was handled. - base::TimeTicks first_request_start_; - - // The time the headers of the first HTTP response were received. This is - // filled with net::LoadTimingInfo::receive_headers_start on the first HTTP - // response during navigation. - // - // In some cases, this can be the time an internal response was received that - // did not come from the networking layer. For example, - // - Service Worker: the time the response from the service worker was - // received, see content::ServiceWorkerNavigationLoader::StartResponse(). - // - HSTS: the time the internal redirect was handled. - // - Signed Exchange: the time the SXG was handled. - base::TimeTicks first_response_start_; + // Timing information of loading for the navigation. Used for recording UMAs. + std::unique_ptr<NavigationHandleTiming> navigation_handle_timing_; // The time this navigation was ready to commit. base::TimeTicks ready_to_commit_time_; @@ -1209,9 +1261,9 @@ class CONTENT_EXPORT NavigationRequest // TrustableWebBundleFileUrl switch is set. // For navigations to Web Bundle file, this is cloned from // |web_bundle_handle_| in CommitNavigation(), and is passed to - // NavigationEntry for the navigation. And for history (back / forward) + // FrameNavigationEntry for the navigation. And for history (back / forward) // navigations within the Web Bundle file, this is cloned from the - // NavigationEntry and is used to create a WebBundleHandle. + // FrameNavigationEntry and is used to create a WebBundleHandle. std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info_; // Which proxy server was used for this navigation, if any. @@ -1220,8 +1272,8 @@ class CONTENT_EXPORT NavigationRequest // The unique id to identify the NavigationHandle with. int64_t navigation_handle_id_ = 0; - // Manages the lifetime of a pre-created ServiceWorkerProviderHost until a - // corresponding provider is created in the renderer. + // Manages the lifetime of a pre-created ServiceWorkerContainerHost until a + // corresponding container is created in the renderer. std::unique_ptr<ServiceWorkerMainResourceHandle> service_worker_handle_; // Timer for detecting an unexpectedly long time to commit a navigation. @@ -1255,7 +1307,7 @@ class CONTENT_EXPORT NavigationRequest // The RenderFrameHost that was restored from the back-forward cache. This // will be null except for navigations that are restoring a page from the // back-forward cache. - RenderFrameHostImpl* rfh_restored_from_back_forward_cache_; + RenderFrameHostImpl* const rfh_restored_from_back_forward_cache_; // These are set to the values from the FrameNavigationEntry this // NavigationRequest is associated with (if any). @@ -1267,14 +1319,14 @@ class CONTENT_EXPORT NavigationRequest base::Optional<net::IsolationInfo> isolation_info_; // This is used to store the current_frame_host id at request creation time. - GlobalFrameRoutingId previous_render_frame_host_id_; + const GlobalFrameRoutingId previous_render_frame_host_id_; // Routing id of the frame host that initiated the navigation, derived from // |begin_params()->initiator_routing_id|. This is best effort: it is only // defined for some renderer-initiated navigations (e.g., not drag and drop). // The frame with the corresponding routing ID may have been deleted before // the navigation begins. - GlobalFrameRoutingId initiator_routing_id_; + const GlobalFrameRoutingId initiator_routing_id_; // This tracks a connection between the current pending entry and this // request, such that the pending entry can be discarded if no requests are @@ -1296,29 +1348,20 @@ class CONTENT_EXPORT NavigationRequest network::mojom::ClientSecurityStatePtr client_security_state_; std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter_; + std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter_; std::unique_ptr<PeakGpuMemoryTracker> loading_mem_tracker_ = nullptr; - // Set to true whenever we the Cross-Origin-Opener-Policy spec requires us to - // do a "BrowsingContext group" swap: - // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e - // This forces a new BrowsingInstance to be used for the RenderFrameHost the - // navigation will commit in. If other pages had JavaScript references to the - // Window object for the frame (via window.opener, window.open(), et cetera), - // those references will be broken; window.name will also be reset to an empty - // string. - // TODO(ahemery): COOP requires that any page during the redirect chain - // having an incompatible COOP triggers a BrowsingInstance swap. Even if the - // end document could be put in the same BrowsingInstance as the starting - // one. Implement the behavior. - bool require_coop_browsing_instance_swap_ = false; + // Structure tracking the effects of the CrossOriginOpenerPolicy on this + // navigation. + CrossOriginOpenerPolicyStatus coop_status_; #if DCHECK_IS_ON() bool is_safe_to_delete_ = true; #endif // UKM source associated with the page we are navigated away from. - ukm::SourceId previous_page_load_ukm_source_id_ = ukm::kInvalidSourceId; + const ukm::SourceId previous_page_load_ukm_source_id_; // If true, changes to the user-agent override require a reload. If false, a // reload is not necessary. @@ -1328,6 +1371,13 @@ class CONTENT_EXPORT NavigationRequest // made by this navigation. mojo::ReceiverSet<network::mojom::CookieAccessObserver> cookie_observers_; + // The sandbox flags of the document to be loaded. This is computed at + // 'ReadyToCommit' time. + base::Optional<network::mojom::WebSandboxFlags> sandbox_flags_to_commit_; + + OptInOriginIsolationEndResult origin_isolation_end_result_ = + OptInOriginIsolationEndResult::kNotRequestedAndNotIsolated; + base::WeakPtrFactory<NavigationRequest> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(NavigationRequest); diff --git a/chromium/content/browser/frame_host/navigation_request_browsertest.cc b/chromium/content/browser/frame_host/navigation_request_browsertest.cc index 3f274d6f23c..74997532956 100644 --- a/chromium/content/browser/frame_host/navigation_request_browsertest.cc +++ b/chromium/content/browser/frame_host/navigation_request_browsertest.cc @@ -7,7 +7,6 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/weak_ptr.h" #include "base/strings/stringprintf.h" -#include "base/task/post_task.h" #include "base/test/metrics/histogram_tester.h" #include "content/browser/frame_host/debug_urls.h" #include "content/browser/frame_host/navigation_request.h" @@ -101,8 +100,8 @@ class TestNavigationThrottle : public NavigationThrottle { navigation_request->request_context_type()); request_context_type_ = navigation_request->request_context_type(); - base::PostTask(FROM_HERE, {BrowserThread::UI}, - std::move(did_call_will_start_)); + GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + std::move(did_call_will_start_)); return will_start_result_; } @@ -111,8 +110,8 @@ class TestNavigationThrottle : public NavigationThrottle { NavigationRequest::From(navigation_handle()); CHECK_EQ(request_context_type_, navigation_request->request_context_type()); - base::PostTask(FROM_HERE, {BrowserThread::UI}, - std::move(did_call_will_redirect_)); + GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + std::move(did_call_will_redirect_)); return will_redirect_result_; } @@ -121,8 +120,8 @@ class TestNavigationThrottle : public NavigationThrottle { NavigationRequest::From(navigation_handle()); CHECK_EQ(request_context_type_, navigation_request->request_context_type()); - base::PostTask(FROM_HERE, {BrowserThread::UI}, - std::move(did_call_will_fail_)); + GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + std::move(did_call_will_fail_)); return will_fail_result_; } @@ -131,8 +130,8 @@ class TestNavigationThrottle : public NavigationThrottle { NavigationRequest::From(navigation_handle()); CHECK_EQ(request_context_type_, navigation_request->request_context_type()); - base::PostTask(FROM_HERE, {BrowserThread::UI}, - std::move(did_call_will_process_)); + GetUIThreadTaskRunner({})->PostTask(FROM_HERE, + std::move(did_call_will_process_)); return will_process_result_; } @@ -1909,8 +1908,8 @@ IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest, ErrorPageNetworkError) { GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); GURL error_url(embedded_test_server()->GetURL("/close-socket")); EXPECT_NE(start_url.host(), error_url.host()); - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler)); { NavigationHandleObserver observer(shell()->web_contents(), start_url); diff --git a/chromium/content/browser/frame_host/navigation_request_unittest.cc b/chromium/content/browser/frame_host/navigation_request_unittest.cc index 3a5e2050e47..654075c29e6 100644 --- a/chromium/content/browser/frame_host/navigation_request_unittest.cc +++ b/chromium/content/browser/frame_host/navigation_request_unittest.cc @@ -192,9 +192,12 @@ class NavigationRequestTest : public RenderViewHostImplTestHarness { auto common_params = CreateCommonNavigationParams(); common_params->initiator_origin = url::Origin::Create(GURL("https://initiator.example.com")); + auto commit_params = CreateCommitNavigationParams(); + commit_params->frame_policy = + main_test_rfh()->frame_tree_node()->pending_frame_policy(); request_ = NavigationRequest::CreateBrowserInitiated( main_test_rfh()->frame_tree_node(), std::move(common_params), - CreateCommitNavigationParams(), false /* browser-initiated */, + std::move(commit_params), false /* browser-initiated */, GlobalFrameRoutingId() /* initiator_routing_id */, std::string() /* extra_headers */, nullptr /* frame_entry */, nullptr /* entry */, nullptr /* post_body */, diff --git a/chromium/content/browser/frame_host/navigator.cc b/chromium/content/browser/frame_host/navigator.cc index e052b64302d..8bf2016ef50 100644 --- a/chromium/content/browser/frame_host/navigator.cc +++ b/chromium/content/browser/frame_host/navigator.cc @@ -235,8 +235,8 @@ void Navigator::DidNavigate( frame_tree_node->render_manager()->DidNavigateFrame( render_frame_host, params.gesture == NavigationGestureUser, is_same_document_navigation, - navigation_request - ->require_coop_browsing_instance_swap() /* clear_proxies_on_commit */, + navigation_request->coop_status() + .require_browsing_instance_swap /* clear_proxies_on_commit */, pending_frame_policy); // Save the new page's origin and other properties, and replicate them to @@ -258,10 +258,24 @@ void Navigator::DidNavigate( render_frame_host->ResetContentSecurityPolicies(); frame_tree_node->ResetForNavigation(); - // Save the new document's embedding token and propagate to any parent - // document that embeds it. A token is only assigned to cross-process - // child frames. - render_frame_host->SetEmbeddingToken(params.embedding_token); + // Back-forward cache navigations should not update the embedding token. + // + // |was_within_same_document| (controlled by the renderer) also needs + // to be considered: in some cases, the browser and renderer can disagree. + // While this is usually a bad message kill, there are some situations + // where this can legitimately happen. When a new frame is created (e.g. + // with <iframe src="...">), the initial about:blank document doesn't have + // a corresponding entry in the browser process. As a result, the browser + // process incorrectly determines that the navigation is cross-document + // when in reality it's same-document. + if (!navigation_request->IsServedFromBackForwardCache() && + !was_within_same_document) { + DCHECK(params.embedding_token.has_value()); + // Save the new document's embedding token and propagate to any parent + // document that embeds it. A token exists for all navigations creating a + // new document. + render_frame_host->SetEmbeddingToken(params.embedding_token.value()); + } } // Update the site of the SiteInstance if it doesn't have one yet, unless @@ -285,8 +299,11 @@ void Navigator::DidNavigate( // TODO(nasko): Verify the correctness of the above comment, since some of the // code doesn't exist anymore. Also, move this code in the // PageTransitionIsMainFrame code block above. - if (ui::PageTransitionIsMainFrame(params.transition) && delegate_) - delegate_->SetMainFrameMimeType(params.contents_mime_type); + if (ui::PageTransitionIsMainFrame(params.transition) && delegate_) { + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + render_frame_host->GetRenderViewHost()); + rvh->SetContentsMimeType(params.contents_mime_type); + } int old_entry_count = controller_->GetEntryCount(); LoadCommittedDetails details; @@ -610,7 +627,7 @@ void Navigator::OnBeginNavigation( // Try to find a FrameNavigationEntry that matches this frame instead, based // on the frame's unique name. If this can't be found, fall back to the // default path below. - if (frame_tree_node->navigator()->StartHistoryNavigationInNewSubframe( + if (frame_tree_node->navigator().StartHistoryNavigationInNewSubframe( frame_tree_node->current_frame_host(), &navigation_client)) { return; } @@ -805,12 +822,6 @@ Navigator::GetNavigationEntryForRendererInitiatedNavigation( if (renderer_provisional_load_to_pending_url) return nullptr; - // If there is a transient entry, creating a new pending entry will result - // in deleting it, which leads to inconsistent state. - bool has_transient_entry = !!controller_->GetTransientEntry(); - if (has_transient_entry) - return nullptr; - // Since GetNavigationEntryForRendererInitiatedNavigation is called from // OnBeginNavigation, we can assume that no frame proxies are involved and // therefore that |current_site_instance| is also the |source_site_instance|. @@ -826,7 +837,8 @@ Navigator::GetNavigationEntryForRendererInitiatedNavigation( ui::PAGE_TRANSITION_LINK, true /* is_renderer_initiated */, std::string() /* extra_headers */, controller_->GetBrowserContext(), - nullptr /* blob_url_loader_factory */)); + nullptr /* blob_url_loader_factory */, + common_params.should_replace_current_entry)); controller_->SetPendingEntry(std::move(entry)); if (delegate_) diff --git a/chromium/content/browser/frame_host/navigator.h b/chromium/content/browser/frame_host/navigator.h index 19ce4447f95..a79f0106cda 100644 --- a/chromium/content/browser/frame_host/navigator.h +++ b/chromium/content/browser/frame_host/navigator.h @@ -46,17 +46,13 @@ class RenderFrameHostImpl; class WebBundleHandleTracker; struct LoadCommittedDetails; -// Navigator is responsible for performing navigations in a node of the -// FrameTree. Its lifetime is bound to all FrameTreeNode objects that are using -// it and will be released once all nodes that use it are freed. The Navigator -// is bound to a single frame tree and cannot be used by multiple instances of -// FrameTree. -// TODO(nasko): Move all navigation methods, such as didStartProvisionalLoad -// from WebContentsImpl to this interface. -class CONTENT_EXPORT Navigator : public base::RefCounted<Navigator> { +// Navigator is responsible for performing navigations in nodes of the +// FrameTree. Its lifetime is bound to the FrameTree. +class CONTENT_EXPORT Navigator { public: Navigator(NavigationControllerImpl* navigation_controller, NavigatorDelegate* delegate); + ~Navigator(); // This method verifies that a navigation to |url| doesn't commit into a WebUI // process if it is not allowed to. Callers of this method should take one of @@ -203,14 +199,14 @@ class CONTENT_EXPORT Navigator : public base::RefCounted<Navigator> { const base::TimeTicks& renderer_before_unload_start_time, const base::TimeTicks& renderer_before_unload_end_time); + NavigationControllerImpl* controller() { return controller_; } + private: - friend class base::RefCounted<Navigator>; friend class NavigatorTestWithBrowserSideNavigation; // Holds data used to track browser side navigation metrics. struct NavigationMetricsData; - ~Navigator(); void RecordNavigationMetrics( const LoadCommittedDetails& details, diff --git a/chromium/content/browser/frame_host/navigator_delegate.h b/chromium/content/browser/frame_host/navigator_delegate.h index 6ee41979d7d..a8386f7a94c 100644 --- a/chromium/content/browser/frame_host/navigator_delegate.h +++ b/chromium/content/browser/frame_host/navigator_delegate.h @@ -74,7 +74,6 @@ class CONTENT_EXPORT NavigatorDelegate { const LoadCommittedDetails& details, const FrameHostMsg_DidCommitProvisionalLoad_Params& params) = 0; - virtual void SetMainFrameMimeType(const std::string& mime_type) = 0; virtual bool CanOverscrollContent() const = 0; // Notification to the Navigator embedder that navigation state has diff --git a/chromium/content/browser/frame_host/navigator_unittest.cc b/chromium/content/browser/frame_host/navigator_unittest.cc index 0d819dc2d49..8d04236aa9e 100644 --- a/chromium/content/browser/frame_host/navigator_unittest.cc +++ b/chromium/content/browser/frame_host/navigator_unittest.cc @@ -1264,7 +1264,7 @@ TEST_F(NavigatorTest, FeaturePolicySameSiteNavigation) { contents()->NavigateAndCommit(kUrl1); // Check the feature policy before navigation. - blink::FeaturePolicy* original_feature_policy = + const blink::FeaturePolicy* original_feature_policy = main_test_rfh()->feature_policy(); ASSERT_TRUE(original_feature_policy); @@ -1272,7 +1272,7 @@ TEST_F(NavigatorTest, FeaturePolicySameSiteNavigation) { contents()->NavigateAndCommit(kUrl2); // Check the feature policy after navigation. - blink::FeaturePolicy* final_feature_policy = + const blink::FeaturePolicy* final_feature_policy = main_test_rfh()->feature_policy(); ASSERT_TRUE(final_feature_policy); ASSERT_NE(original_feature_policy, final_feature_policy); @@ -1287,7 +1287,7 @@ TEST_F(NavigatorTest, FeaturePolicyFragmentNavigation) { contents()->NavigateAndCommit(kUrl1); // Check the feature policy before navigation. - blink::FeaturePolicy* original_feature_policy = + const blink::FeaturePolicy* original_feature_policy = main_test_rfh()->feature_policy(); ASSERT_TRUE(original_feature_policy); @@ -1295,7 +1295,7 @@ TEST_F(NavigatorTest, FeaturePolicyFragmentNavigation) { contents()->NavigateAndCommit(kUrl2); // Check the feature policy after navigation. - blink::FeaturePolicy* final_feature_policy = + const blink::FeaturePolicy* final_feature_policy = main_test_rfh()->feature_policy(); ASSERT_EQ(original_feature_policy, final_feature_policy); } @@ -1313,7 +1313,7 @@ TEST_F(NavigatorTest, FeaturePolicyNewChild) { contents()->GetMainFrame()->AppendChild("child"); NavigationSimulator::NavigateAndCommitFromDocument(kUrl2, subframe_rfh); - blink::FeaturePolicy* subframe_feature_policy = + const blink::FeaturePolicy* subframe_feature_policy = subframe_rfh->feature_policy(); ASSERT_TRUE(subframe_feature_policy); ASSERT_FALSE(subframe_feature_policy->GetOriginForTest().opaque()); diff --git a/chromium/content/browser/frame_host/render_document_host_user_data_browsertest.cc b/chromium/content/browser/frame_host/render_document_host_user_data_browsertest.cc index 09dff784251..089d8e01546 100644 --- a/chromium/content/browser/frame_host/render_document_host_user_data_browsertest.cc +++ b/chromium/content/browser/frame_host/render_document_host_user_data_browsertest.cc @@ -65,6 +65,53 @@ class Data : public RenderDocumentHostUserData<Data> { RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(Data) +// Observer class to track the creation of RenderFrameHost objects. It is used +// in subsequent tests. +class RenderFrameHostCreatedObserver : public WebContentsObserver { + public: + using OnRenderFrameHostCreatedCallback = + base::RepeatingCallback<void(RenderFrameHost*)>; + + RenderFrameHostCreatedObserver( + WebContents* web_contents, + OnRenderFrameHostCreatedCallback on_rfh_created) + : WebContentsObserver(web_contents), + on_rfh_created_(std::move(on_rfh_created)) {} + + void RenderFrameCreated(RenderFrameHost* render_frame_host) override { + on_rfh_created_.Run(std::move(render_frame_host)); + } + + private: + OnRenderFrameHostCreatedCallback on_rfh_created_; +}; + +// Observer class to track creation of new popups. It is used +// in subsequent tests. +class PopupCreatedObserver : public WebContentsDelegate { + public: + using WebContentsCreatedCallback = + base::RepeatingCallback<void(WebContents* web_contents)>; + + explicit PopupCreatedObserver(WebContentsCreatedCallback callback) + : callback_(std::move(callback)) {} + + void AddNewContents(WebContents* source_contents, + std::unique_ptr<WebContents> new_contents, + const GURL& target_url, + WindowOpenDisposition disposition, + const gfx::Rect& initial_rect, + bool user_gesture, + bool* was_blocked) override { + callback_.Run(new_contents.get()); + web_contents_.push_back(std::move(new_contents)); + } + + private: + WebContentsCreatedCallback callback_; + std::vector<std::unique_ptr<WebContents>> web_contents_; +}; + } // namespace class RenderDocumentHostUserDataTest : public ContentBrowserTest { @@ -114,6 +161,32 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, EXPECT_FALSE(Data::GetForCurrentDocument(rfh_a)); } +// Test GetOrCreateForCurrentDocument API of RenderDocumentHostUserData. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, + GetOrCreateForCurrentDocument) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), url_a)); + RenderFrameHostImpl* rfh_a = top_frame_host(); + + // 2) Get the Data associated with this RenderFrameHost. It should be null + // before creation. + Data* data = Data::GetForCurrentDocument(rfh_a); + EXPECT_FALSE(data); + + // 3) |GetOrCreateForCurrentDocument| should create Data. + base::WeakPtr<Data> created_data = + Data::GetOrCreateForCurrentDocument(rfh_a)->GetWeakPtr(); + EXPECT_TRUE(created_data); + + // 4) Another call to |GetOrCreateForCurrentDocument| should not create the + // new data and the previous data created in 3) should be preserved. + Data::GetOrCreateForCurrentDocument(rfh_a); + EXPECT_TRUE(created_data); +} + // Tests that RenderDocumentHostUserData objects are different for each // RenderFrameHost in FrameTree when there are multiple frames. IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, @@ -141,9 +214,12 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, EXPECT_NE(data_a->unique_id(), data_b->unique_id()); } -// Tests that RenderDocumentHostUserData objects are cleared when the renderer -// process crashes even when having RenderFrameHost that still exists. -IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, CheckForCrashedFrame) { +// Tests that RenderDocumentHostUserData object is preserved when the renderer +// process crashes even when RenderFrameHost still exists, but the RDHUD object +// is cleared if the RenderFrameHost is reused for the new RenderFrame creation +// when the previous renderer process crashes. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, + CrashedFrameUserDataIsPreservedAndDeletedOnReset) { ASSERT_TRUE(embedded_test_server()->Start()); GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); @@ -163,11 +239,168 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, CheckForCrashedFrame) { renderer_process->Shutdown(0); crash_observer.Wait(); - // 4) Check if the RDHUD object is deleted after renderer process crashes even - // when RFH is around. - EXPECT_EQ(top_frame_host(), rfh_a); + // 4) RDHUD shouldn't be cleared after the renderer crash. EXPECT_FALSE(rfh_a->IsRenderFrameLive()); + EXPECT_TRUE(data); + + // 5) Register an observer which observes created RenderFrameHost and checks + // if the data is cleared when callback was run. + bool did_clear_user_data = false; + RenderFrameHostCreatedObserver observer( + web_contents(), base::BindRepeating( + [](bool* did_clear_user_data, RenderFrameHost* rfh) { + if (!Data::GetForCurrentDocument(rfh)) + *did_clear_user_data = true; + }, + &did_clear_user_data)); + + // 6) Re-initialize RenderFrame, now RDHUD should be cleared on new + // RenderFrame creation after crash when + // RenderFrameHostImpl::SetRenderFrameCreated was called. + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + root->render_manager()->InitializeMainRenderFrameForImmediateUse(); + EXPECT_TRUE(did_clear_user_data); + + // With RenderDocument, on a reload renderer crashes would give a new + // RenderFrameHost different from rfh_a. + RenderFrameHostImpl* new_rfh_a = top_frame_host(); + EXPECT_TRUE(new_rfh_a->IsRenderFrameLive()); + + // 7) RDHUD should be cleared after initialization. EXPECT_FALSE(data); + + // 8) Check RDHUD object creation after new RenderFrame creation. + Data::CreateForCurrentDocument(new_rfh_a); + base::WeakPtr<Data> new_data = + Data::GetForCurrentDocument(new_rfh_a)->GetWeakPtr(); + EXPECT_TRUE(new_data); +} + +// Tests that RenderDocumentHostUserData object is not cleared when speculative +// RFH commits after the renderer hosting the current RFH (of old URL) crashes +// i.e., while navigating to a new URL (using speculative RFH) and having the +// current RFH (of old URL) not alive. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, + CheckWithFrameCrashDuringNavigation) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); + + // Isolate "b.com" so we are guaranteed to get a different process + // for navigations to this origin on Android. Doing this ensures that a + // speculative RenderFrameHost is used. + IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(), + {"b.com"}); + + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), url_a)); + RenderFrameHostImpl* rfh_a = top_frame_host(); + + // 2) Start navigation to B, but don't commit yet. + TestNavigationManager manager(shell()->web_contents(), url_b); + shell()->LoadURL(url_b); + EXPECT_TRUE(manager.WaitForRequestStart()); + + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + RenderFrameHostImpl* pending_rfh = + root->render_manager()->speculative_frame_host(); + NavigationRequest* navigation_request = root->navigation_request(); + EXPECT_EQ(navigation_request->associated_site_instance_type(), + NavigationRequest::AssociatedSiteInstanceType::SPECULATIVE); + EXPECT_TRUE(pending_rfh); + + // 3) Get the RenderDocumentHostUserData associated with the speculative + // RenderFrameHost. + Data::CreateForCurrentDocument(pending_rfh); + base::WeakPtr<Data> data = + Data::GetForCurrentDocument(pending_rfh)->GetWeakPtr(); + EXPECT_TRUE(data); + + // 4) Crash the renderer hosting current RFH. + RenderProcessHost* renderer_process = rfh_a->GetProcess(); + RenderProcessHostWatcher crash_observer( + renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + renderer_process->Shutdown(0); + crash_observer.Wait(); + + // 5) Check that the RDHUD object is not cleared after renderer process + // crashes. + EXPECT_EQ(top_frame_host(), rfh_a); + EXPECT_FALSE(pending_rfh->IsCurrent()); + EXPECT_FALSE(rfh_a->IsRenderFrameLive()); + EXPECT_TRUE(pending_rfh->IsRenderFrameLive()); + EXPECT_TRUE(data); + + // 6) Let the navigation finish and make sure it has succeeded. + manager.WaitForNavigationFinished(); + EXPECT_EQ(url_b, web_contents()->GetMainFrame()->GetLastCommittedURL()); + + // 7) Data shouldn't be cleared in this case, as state + // |committed_speculative_rfh_before_navigation_commit_| is true during the + // check in DidCommitInternalNavigation as the speculative RFH swaps with the + // crashed RFH and performs commit before navigation commit happens. + EXPECT_TRUE(data); +} + +// Tests that RenderDocumentHostUserData object is not cleared when speculative +// RFH commits after renderer hosting the current RFH (of old URL) which happens +// before navigating to a new URL (using Speculative RFH) and having the current +// RenderFrameHost (of old URL) not alive. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, + CheckWithFrameCrashBeforeNavigation) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); + + // Isolate "b.com" so we are guaranteed to get a different process + // for navigations to this origin on Android. Doing this ensures that a + // speculative RenderFrameHost is used. + IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(), + {"b.com"}); + + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), url_a)); + RenderFrameHostImpl* rfh_a = top_frame_host(); + + // 2) Crash the renderer hosting current RFH. + RenderProcessHost* renderer_process = rfh_a->GetProcess(); + RenderProcessHostWatcher crash_observer( + renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + renderer_process->Shutdown(0); + crash_observer.Wait(); + + // 3) Start navigation to B, but don't commit yet. + TestNavigationManager manager(shell()->web_contents(), url_b); + shell()->LoadURL(url_b); + EXPECT_TRUE(manager.WaitForRequestStart()); + + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + // Speculative RenderFrameHost for B will commit early because current rfh_a + // is not alive after the crash in step (2). + RenderFrameHostImpl* current_rfh = + root->render_manager()->current_frame_host(); + NavigationRequest* navigation_request = root->navigation_request(); + EXPECT_EQ(navigation_request->associated_site_instance_type(), + NavigationRequest::AssociatedSiteInstanceType::CURRENT); + EXPECT_TRUE(current_rfh); + EXPECT_TRUE(current_rfh->IsCurrent()); + + // 4) Get the RenderDocumentHostUserData associated with speculative + // RenderFrameHost. + Data::CreateForCurrentDocument(current_rfh); + base::WeakPtr<Data> data = + Data::GetForCurrentDocument(current_rfh)->GetWeakPtr(); + EXPECT_TRUE(data); + + // 5) Let the navigation finish and make sure it has succeeded. + manager.WaitForNavigationFinished(); + EXPECT_EQ(url_b, web_contents()->GetMainFrame()->GetLastCommittedURL()); + + // 6) Data shouldn't be cleared in this case, as state + // |committed_speculative_rfh_before_navigation_commit_| is true during the + // check in DidCommitInternalNavigation as the speculative RFH swaps with the + // crashed RFH and performs commit before navigation commit happens. + EXPECT_TRUE(data); } // Tests that RenderDocumentHostUserData object is created for speculative @@ -234,11 +467,8 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, SpeculativeRFHDeleted) { RenderFrameHostImpl* rfh_a = web_contents()->GetMainFrame(); RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); - // RFH B has an unload handler. - auto detach_filter_b = base::MakeRefCounted<DropMessageFilter>( - FrameMsgStart, FrameHostMsg_Detach::ID); - rfh_b->GetProcess()->AddFilter(detach_filter_b.get()); - EXPECT_TRUE(ExecJs(rfh_b, "onunload=function(){}")); + // Leave rfh_b in pending deletion state. + LeaveInPendingDeletionState(rfh_b); // 2) Navigation from B to C. The server is slow to respond. TestNavigationManager navigation_observer(web_contents(), url_c); @@ -303,7 +533,6 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, CheckInPendingDeletionState) { ASSERT_TRUE(embedded_test_server()->Start()); IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); - std::string onunload_script = "window.onunload = function(){ while(1); }"; GURL url_ab(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); @@ -313,13 +542,9 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, RenderFrameHostImpl* rfh_a = top_frame_host(); RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); - // 2) Act as if there is a slow unload handler on rfh_a and rfh_b. + // 2) Leave both rfh_a and rfh_b in pending deletion state. LeaveInPendingDeletionState(rfh_a); - rfh_b->SetSubframeUnloadTimeoutForTesting(base::TimeDelta::FromSeconds(30)); - auto detach_filter = base::MakeRefCounted<DropMessageFilter>( - FrameMsgStart, FrameHostMsg_Detach::ID); - rfh_b->GetProcess()->AddFilter(detach_filter.get()); - EXPECT_TRUE(ExecuteScript(rfh_b->frame_tree_node(), onunload_script)); + LeaveInPendingDeletionState(rfh_b); // 3) Create RDHUD object for both rfh_a and rfh_b before running unload // handlers. @@ -504,6 +729,177 @@ IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, SameSiteNavigation) { EXPECT_FALSE(data); } +// This test ensures that the data created during the new WebContents +// initialisation is not lost during the initial "navigation" triggered by the +// window.open. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, WindowOpen) { + ASSERT_TRUE(embedded_test_server()->Start()); + + int popup_data_id = -1; + WebContents* new_tab = nullptr; + + // 1) Navigate to A. + GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); + EXPECT_TRUE(NavigateToURL(shell(), url)); + + // 2) Register WebContentsDelegate to get notified about new popups. + PopupCreatedObserver observer(base::BindRepeating( + [](int* popup_data_id, WebContents** new_tab, WebContents* web_contents) { + EXPECT_EQ(*popup_data_id, -1); + EXPECT_FALSE(*new_tab); + *new_tab = web_contents; + + *popup_data_id = + Data::GetOrCreateForCurrentDocument(web_contents->GetMainFrame()) + ->unique_id(); + }, + &popup_data_id, &new_tab)); + web_contents()->SetDelegate(&observer); + + // 3) Invoke a window.open() without parameters. The delegate should capture + // the ownership of the new WebContents and attach Data to its main frame. + EXPECT_TRUE(ExecJs(top_frame_host(), "window.open()")); + EXPECT_TRUE(new_tab); + // Do not check for the success status because we haven't committed a + // navigation yet, which causes checks to fail. + WaitForLoadStopWithoutSuccessCheck(new_tab); + + // 4) Expect the data to the preserved (it might have been deleted due to us + // having a fake initial navigation for blank window.open). + Data* new_tab_data = Data::GetForCurrentDocument(new_tab->GetMainFrame()); + EXPECT_TRUE(new_tab_data); + EXPECT_EQ(new_tab_data->unique_id(), popup_data_id); + + web_contents()->SetDelegate(nullptr); +} + +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, BlankIframe) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b( + embedded_test_server()->GetURL("b.com", "/page_with_blank_iframe.html")); + + // 0) Navigate to A to ensure that we have a live frame (see a comment in + // AttachOnCreatingInitialFrame). + EXPECT_TRUE(NavigateToURL(shell(), url_a)); + + int starting_id = next_id + 1; + + std::vector<RenderFrameHost*> created_rfhs; + + // 1) Register an observer which observes all created RenderFrameHosts + // and attaches user data to them. + RenderFrameHostCreatedObserver observer( + web_contents(), base::BindRepeating( + [](std::vector<RenderFrameHost*>* created_rfhs, + RenderFrameHost* rfh) { + created_rfhs->push_back(rfh); + Data::GetOrCreateForCurrentDocument(rfh); + }, + &created_rfhs)); + + // 2) Navigate to a page with a blank iframe. + EXPECT_TRUE(NavigateToURL(shell(), url_b)); + + // 3) Expect that we have two frames with valid user data. + // The danger here lies in the perculiar properties of the initial navigation + // for the blank iframes, which does not create a new document, but goes via + // DidCommitProvisionalLoad. + std::vector<int> current_ids; + for (RenderFrameHost* rfh : created_rfhs) { + if (auto* data = Data::GetForCurrentDocument(rfh)) { + current_ids.push_back(data->unique_id()); + } + } + + EXPECT_EQ(2u, created_rfhs.size()); + EXPECT_THAT(current_ids, testing::ElementsAre(starting_id, starting_id + 1)); +} + +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, SrcDocIframe) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b(embedded_test_server()->GetURL( + "b.com", "/frame_tree/page_with_srcdoc_frame.html")); + + // 0) Navigate to A to ensure that we have a live frame (see a comment in + // AttachOnCreatingInitialFrame). + EXPECT_TRUE(NavigateToURL(shell(), url_a)); + + int starting_id = next_id + 1; + + std::vector<RenderFrameHost*> created_rfhs; + + // 1) Register an observer which observes all created RenderFrameHosts + // and attaches user data to them. + RenderFrameHostCreatedObserver observer( + web_contents(), base::BindRepeating( + [](std::vector<RenderFrameHost*>* created_rfhs, + RenderFrameHost* rfh) { + created_rfhs->push_back(rfh); + Data::GetOrCreateForCurrentDocument(rfh); + }, + &created_rfhs)); + + // 2) Navigate to a page with a srcdoc iframe. + EXPECT_TRUE(NavigateToURL(shell(), url_b)); + + // 3) Expect that we have one frame with valid user data. + // For the subframe, the user data was attached to the initial blank document + // and should have been deleted when the document was navigated to + // about:srcdoc. + std::vector<int> current_ids; + for (RenderFrameHost* rfh : created_rfhs) { + if (auto* data = Data::GetForCurrentDocument(rfh)) { + current_ids.push_back(data->unique_id()); + } + } + + EXPECT_EQ(2u, created_rfhs.size()); + EXPECT_THAT(current_ids, testing::ElementsAre(starting_id)); +} + +// This test doesn't actually work at the moment and documents the current +// behaviour rather than intended one. +// TODO(sreejakshetty): Fix it. +IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest, + AttachOnCreatingInitialFrame) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); + + bool observed_frame_creation = false; + + // 1) Register an observer which observes all created RenderFrameHosts + // and attaches user data to them. + RenderFrameHostCreatedObserver observer( + web_contents(), + base::BindRepeating( + [](bool* observed_frame_creation, RenderFrameHost* rfh) { + Data::GetOrCreateForCurrentDocument(rfh); + *observed_frame_creation = true; + }, + &observed_frame_creation)); + + // 2) Navigate to a new page. + EXPECT_FALSE(shell()->web_contents()->GetMainFrame()->IsRenderFrameLive()); + EXPECT_TRUE(NavigateToURL(shell(), url)); + + // 3) Unfortunately, user data will not be there – the reason is that we + // actually reuse the initial RenderFrameHost, which wasn't fully initialised + // when we started a navigation (IsRenderFrameLive == false). It will complete + // its initialisation, dispatching RenderFrameCreated, but during the + // navigation commit we will consider the document in it to be reset later + // when we commit the navigation. In the short term, we should not reset RDHUD + // in that case. In the long term, we probably should create a new + // RenderFrameHost here. + EXPECT_TRUE(observed_frame_creation); + EXPECT_FALSE( + Data::GetForCurrentDocument(shell()->web_contents()->GetMainFrame())); +} + // Test RenderDocumentHostUserData with BackForwardCache feature enabled. class RenderDocumentHostUserDataWithBackForwardCacheTest : public RenderDocumentHostUserDataTest { diff --git a/chromium/content/browser/frame_host/render_frame_host_delegate.cc b/chromium/content/browser/frame_host/render_frame_host_delegate.cc index b6a4bd21bf5..6db3cc869ff 100644 --- a/chromium/content/browser/frame_host/render_frame_host_delegate.cc +++ b/chromium/content/browser/frame_host/render_frame_host_delegate.cc @@ -38,20 +38,6 @@ bool RenderFrameHostDelegate::DidAddMessageToConsole( return false; } -void RenderFrameHostDelegate::RunFileChooser( - RenderFrameHost* render_frame_host, - std::unique_ptr<FileChooserImpl::FileSelectListenerImpl> listener, - const blink::mojom::FileChooserParams& params) { - listener->FileSelectionCanceled(); -} - -void RenderFrameHostDelegate::EnumerateDirectory( - RenderFrameHost* render_frame_host, - std::unique_ptr<FileChooserImpl::FileSelectListenerImpl> listener, - const base::FilePath& path) { - listener->FileSelectionCanceled(); -} - WebContents* RenderFrameHostDelegate::GetAsWebContents() { return nullptr; } @@ -135,7 +121,6 @@ RenderFrameHostDelegate* RenderFrameHostDelegate::CreateNewWindow( } bool RenderFrameHostDelegate::ShouldAllowRunningInsecureContent( - WebContents* web_contents, bool allowed_per_prefs, const url::Origin& origin, const GURL& resource_url) { diff --git a/chromium/content/browser/frame_host/render_frame_host_delegate.h b/chromium/content/browser/frame_host/render_frame_host_delegate.h index f20bbc780e7..e70d6104c82 100644 --- a/chromium/content/browser/frame_host/render_frame_host_delegate.h +++ b/chromium/content/browser/frame_host/render_frame_host_delegate.h @@ -15,7 +15,6 @@ #include "base/optional.h" #include "build/build_config.h" #include "components/viz/common/surfaces/surface_id.h" -#include "content/browser/frame_host/file_chooser_impl.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/webui/web_ui_impl.h" #include "content/common/content_export.h" @@ -71,7 +70,6 @@ class Origin; namespace blink { namespace mojom { -class FileChooserParams; class FullscreenOptions; } } // namespace blink @@ -184,26 +182,6 @@ class CONTENT_EXPORT RenderFrameHostDelegate { RenderFrameHost* source, std::vector<blink::mojom::FaviconURLPtr> candidates) {} - // Called when a file selection is to be done. - // - // Overrides of this function must call either listener->FileSelected() or - // listener->FileSelectionCanceled(). - virtual void RunFileChooser( - RenderFrameHost* render_frame_host, - std::unique_ptr<FileChooserImpl::FileSelectListenerImpl> listener, - const blink::mojom::FileChooserParams& params); - - // Request to enumerate a directory. This is equivalent to running the file - // chooser in directory-enumeration mode and having the user select the given - // directory. - // - // Overrides of this function must call either listener->FileSelected() or - // listener->FileSelectionCanceled(). - virtual void EnumerateDirectory( - RenderFrameHost* render_frame_host, - std::unique_ptr<FileChooserImpl::FileSelectListenerImpl> listener, - const base::FilePath& directory_path); - // The pending page load was canceled, so the address bar should be updated. virtual void DidCancelLoading() {} @@ -298,11 +276,10 @@ class CONTENT_EXPORT RenderFrameHostDelegate { // Returns whether entering fullscreen with EnterFullscreenMode() is allowed. virtual bool CanEnterFullscreenMode(); - // Notification that the frame wants to go into fullscreen mode. - // |origin| represents the origin of the frame that requests fullscreen. Must - // only be called if CanEnterFullscreenMode returns true. + // Notification that the frame with the given host wants to enter fullscreen + // mode. Must only be called if CanEnterFullscreenMode returns true. virtual void EnterFullscreenMode( - const GURL& origin, + RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options) {} // Notification that the frame wants to go out of fullscreen mode. @@ -430,10 +407,8 @@ class CONTENT_EXPORT RenderFrameHostDelegate { // Reports that passive mixed content was found at the specified url. virtual void PassiveInsecureContentFound(const GURL& resource_url) {} - // Checks if running of active mixed content is allowed for the specified - // WebContents/tab. - virtual bool ShouldAllowRunningInsecureContent(WebContents* web_contents, - bool allowed_per_prefs, + // Checks if running of active mixed content is allowed in the current tab. + virtual bool ShouldAllowRunningInsecureContent(bool allowed_per_prefs, const url::Origin& origin, const GURL& resource_url); @@ -496,6 +471,10 @@ class CONTENT_EXPORT RenderFrameHostDelegate { virtual void AudioContextPlaybackStopped(RenderFrameHost* host, int context_id) {} + // Notifies observers if the frame has changed audible state. + virtual void OnFrameAudioStateChanged(RenderFrameHost* host, + bool is_audible) {} + // Returns the main frame of the inner delegate that is attached to this // delegate using |frame_tree_node|. Returns nullptr if no such inner delegate // exists. @@ -571,7 +550,6 @@ class CONTENT_EXPORT RenderFrameHostDelegate { virtual void CreateNewWidget( int32_t render_process_id, int32_t widget_route_id, - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {} @@ -580,7 +558,6 @@ class CONTENT_EXPORT RenderFrameHostDelegate { virtual void CreateNewFullscreenWidget( int32_t render_process_id, int32_t widget_route_id, - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {} @@ -614,6 +591,26 @@ class CONTENT_EXPORT RenderFrameHostDelegate { virtual void OnCookiesAccessed(RenderFrameHostImpl* render_frame_host, const CookieAccessDetails& details) {} + // Notified that the renderer responded after calling GetSavableResourceLinks. + virtual void SavableResourceLinksResponse( + RenderFrameHostImpl* source, + const std::vector<GURL>& resources_list, + blink::mojom::ReferrerPtr referrer, + const std::vector<blink::mojom::SavableSubframePtr>& subframes) {} + + // Notified that the renderer returned an error after calling + // GetSavableResourceLinks in case the frame contains non-savable content + // (i.e. from a non-savable scheme) or if there were errors gathering the + // links. + virtual void SavableResourceLinksError(RenderFrameHostImpl* source) {} + + // Called when |RenderFrameHostImpl::lifecycle_state()| changes i.e., when + // RenderFrameHost LifecycleState changes from old_state to new_state. + virtual void RenderFrameHostStateChanged( + RenderFrameHost* host, + RenderFrameHostImpl::LifecycleState old_state, + RenderFrameHostImpl::LifecycleState new_state) {} + protected: virtual ~RenderFrameHostDelegate() = default; }; diff --git a/chromium/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc b/chromium/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc index cecab23e41f..913ce4a535c 100644 --- a/chromium/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc +++ b/chromium/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc @@ -65,10 +65,10 @@ class RenderFrameHostFeaturePolicyTest RenderFrameHost* child, blink::mojom::FeaturePolicyFeature feature, const std::vector<std::string>& origins) { - static_cast<TestRenderFrameHost*>(parent)->OnDidChangeFramePolicy( - child->GetRoutingID(), {network::mojom::WebSandboxFlags::kNone, - CreateFPHeader(feature, origins), - {} /* required_document_policy */}); + static_cast<TestRenderFrameHost*>(parent)->DidChangeFramePolicy( + child->GetFrameToken(), {network::mojom::WebSandboxFlags::kNone, + CreateFPHeader(feature, origins), + {} /* required_document_policy */}); } void SimulateNavigation(RenderFrameHost** rfh, const GURL& url) { diff --git a/chromium/content/browser/frame_host/render_frame_host_impl.cc b/chromium/content/browser/frame_host/render_frame_host_impl.cc index 115673d129d..54cc5135341 100644 --- a/chromium/content/browser/frame_host/render_frame_host_impl.cc +++ b/chromium/content/browser/frame_host/render_frame_host_impl.cc @@ -14,8 +14,8 @@ #include "base/command_line.h" #include "base/containers/queue.h" #include "base/debug/alias.h" +#include "base/debug/crash_logging.h" #include "base/debug/dump_without_crashing.h" -#include "base/hash/hash.h" #include "base/i18n/character_encoding.h" #include "base/lazy_instance.h" #include "base/memory/ptr_util.h" @@ -46,6 +46,7 @@ #include "content/browser/contacts/contacts_manager_impl.h" #include "content/browser/data_url_loader_factory.h" #include "content/browser/devtools/devtools_instrumentation.h" +#include "content/browser/devtools/protocol/audits.h" #include "content/browser/dom_storage/dom_storage_context_wrapper.h" #include "content/browser/download/data_url_blob_reader.h" #include "content/browser/download/mhtml_generation_manager.h" @@ -55,7 +56,6 @@ #include "content/browser/frame_host/cookie_utils.h" #include "content/browser/frame_host/cross_process_frame_connector.h" #include "content/browser/frame_host/debug_urls.h" -#include "content/browser/frame_host/file_chooser_impl.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/frame_host/input/input_injector_impl.h" @@ -82,6 +82,7 @@ #include "content/browser/native_file_system/native_file_system_manager_impl.h" #include "content/browser/navigation_subresource_loader_params.h" #include "content/browser/net/cross_origin_embedder_policy_reporter.h" +#include "content/browser/net/cross_origin_opener_policy_reporter.h" #include "content/browser/payments/payment_app_context_impl.h" #include "content/browser/permissions/permission_controller_impl.h" #include "content/browser/permissions/permission_service_context.h" @@ -122,22 +123,21 @@ #include "content/browser/webui/url_data_manager_backend.h" #include "content/browser/webui/web_ui_controller_factory_registry.h" #include "content/browser/webui/web_ui_url_loader_factory_internal.h" -#include "content/browser/worker_host/dedicated_worker_host.h" +#include "content/browser/worker_host/dedicated_worker_host_factory_impl.h" #include "content/browser/worker_host/shared_worker_service_impl.h" #include "content/common/associated_interfaces.mojom.h" #include "content/common/content_constants_internal.h" #include "content/common/content_navigation_policy.h" #include "content/common/frame.mojom.h" #include "content/common/frame_messages.h" -#include "content/common/input/input_handler.mojom.h" #include "content/common/inter_process_time_ticks_converter.h" #include "content/common/navigation_params.h" #include "content/common/navigation_params_mojom_traits.h" #include "content/common/navigation_params_utils.h" #include "content/common/render_message_filter.mojom.h" #include "content/common/renderer.mojom.h" +#include "content/common/state_transitions.h" #include "content/common/unfreezable_frame_messages.h" -#include "content/common/widget.mojom.h" #include "content/public/browser/ax_event_notification_details.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_context.h" @@ -192,10 +192,12 @@ #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/features.h" #include "services/network/public/cpp/is_potentially_trustworthy.h" +#include "services/network/public/cpp/trust_token_operation_authorization.h" #include "services/network/public/cpp/web_sandbox_flags.h" #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" #include "services/network/public/mojom/cookie_access_observer.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" +#include "services/network/public/mojom/url_loader.mojom-shared.h" #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" #include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/interface_provider.h" @@ -232,6 +234,7 @@ #include "ui/accessibility/ax_tree.h" #include "ui/accessibility/ax_tree_id_registry.h" #include "ui/accessibility/ax_tree_update.h" +#include "ui/events/event_constants.h" #include "ui/gfx/geometry/quad_f.h" #include "url/gurl.h" #include "url/origin.h" @@ -422,7 +425,7 @@ void ForEachFrame(RenderFrameHostImpl* root_frame_host, const FrameCallback& frame_callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - FrameTree* frame_tree = root_frame_host->frame_tree_node()->frame_tree(); + FrameTree* frame_tree = root_frame_host->frame_tree(); DCHECK_EQ(root_frame_host, frame_tree->GetMainFrame()); for (FrameTreeNode* node : frame_tree->Nodes()) { @@ -474,11 +477,10 @@ base::Histogram::Sample HashInterfaceNameToHistogramSample( // Set crash keys that will help understand the circumstances of a renderer // kill. Note that the commit URL is already reported in a crash key, and // additional keys are logged in RenderProcessHostImpl::ShutdownForBadMessage. -void LogRendererKillCrashKeys(const GURL& site_url) { +void LogRendererKillCrashKeys(const SiteInfo& site_info) { static auto* site_url_key = base::debug::AllocateCrashKeyString( "current_site_url", base::debug::CrashKeySize::Size64); - base::debug::SetCrashKeyString(site_url_key, - site_url.possibly_invalid_spec()); + base::debug::SetCrashKeyString(site_url_key, site_info.GetDebugString()); } void LogCanCommitOriginAndUrlFailureReason(const std::string& failure_reason) { @@ -487,18 +489,43 @@ void LogCanCommitOriginAndUrlFailureReason(const std::string& failure_reason) { base::debug::SetCrashKeyString(failure_reason_key, failure_reason); } +bool ShouldBypassChecksForErrorPage( + RenderFrameHostImpl* frame, + NavigationRequest* navigation_request, + bool* should_commit_unreachable_url = nullptr) { + DCHECK(frame); + + if (should_commit_unreachable_url) + *should_commit_unreachable_url = false; + + bool is_main_frame = !frame->GetParent(); + if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame)) { + if (frame->GetSiteInstance()->GetSiteInfo() == + SiteInfo::CreateForErrorPage()) { + if (should_commit_unreachable_url) + *should_commit_unreachable_url = true; + + // With error page isolation, any URL can commit in an error page process. + return true; + } + } else { + // Without error page isolation, a blocked navigation is expected to + // commit in the old renderer process. This may be true for subframe + // navigations even when error page isolation is enabled for main frames. + if (navigation_request && + net::IsRequestBlockedError(navigation_request->GetNetErrorCode())) { + return true; + } + } + + return false; +} + url::Origin GetOriginForURLLoaderFactoryUnchecked( + RenderFrameHostImpl* target_frame, NavigationRequest* navigation_request) { - // Return a safe opaque origin when there is no |navigation_request| (e.g. - // when RFHI::CommitNavigation is called via RFHI::NavigateToInterstitialURL). - if (!navigation_request) - return url::Origin(); - - // GetOriginForURLLoaderFactory should only be called at the ready-to-commit - // time, when the RFHI to commit the navigation is already known. - DCHECK_LE(NavigationRequest::READY_TO_COMMIT, navigation_request->state()); - RenderFrameHostImpl* target_frame = navigation_request->GetRenderFrameHost(); DCHECK(target_frame); + DCHECK(navigation_request); // Check if this is loadDataWithBaseUrl (which needs special treatment). auto& common_params = navigation_request->common_params(); @@ -547,18 +574,26 @@ url::Origin GetOriginForURLLoaderFactoryUnchecked( url::Origin GetOriginForURLLoaderFactory( NavigationRequest* navigation_request) { - url::Origin result = - GetOriginForURLLoaderFactoryUnchecked(navigation_request); + DCHECK(navigation_request); - // |result| must be an origin that is allowed to be accessed from the process - // that is the target of this navigation. - if (navigation_request) { - int process_id = - navigation_request->GetRenderFrameHost()->GetProcess()->GetID(); - auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); - CHECK(policy->CanAccessDataForOrigin(process_id, result)); - } + // GetOriginForURLLoaderFactory should only be called at the ReadyToCommit + // time, when the RFHI to commit the navigation is already known. + DCHECK_EQ(NavigationRequest::READY_TO_COMMIT, navigation_request->state()); + RenderFrameHostImpl* target_frame = navigation_request->GetRenderFrameHost(); + DCHECK(target_frame); + + // Calculate an approximation (sandbox/csp is ignored) of the origin that will + // be committed because of |navigation_request|. + url::Origin result = + GetOriginForURLLoaderFactoryUnchecked(target_frame, navigation_request); + // Check that |result| origin is allowed to be accessed from the process that + // is the target of this navigation. + if (ShouldBypassChecksForErrorPage(target_frame, navigation_request)) + return result; + int process_id = target_frame->GetProcess()->GetID(); + auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); + CHECK(policy->CanAccessDataForOrigin(process_id, result)); return result; } @@ -607,9 +642,6 @@ void OnDataURLRetrieved( StartDownload(std::move(parameters), mojo::NullRemote()); } -// TODO(crbug.com/977040): Remove when no longer needed. -const uint32_t kMaxCookieSameSiteDeprecationUrls = 20; - void RecordCrossOriginIsolationMetrics(RenderFrameHostImpl* rfh) { ContentBrowserClient* client = GetContentClient()->browser(); if (rfh->cross_origin_opener_policy().value == @@ -648,15 +680,77 @@ bool ParentNeedsTrustTokenFeaturePolicy( if (!begin_params.trust_token_params) return false; - switch (begin_params.trust_token_params->type) { - case network::mojom::TrustTokenOperationType::kRedemption: - case network::mojom::TrustTokenOperationType::kSigning: - return true; - case network::mojom::TrustTokenOperationType::kIssuance: - return false; + return network::DoesTrustTokenOperationRequireFeaturePolicy( + begin_params.trust_token_params->type); +} + +// Analyzes trusted sources of a frame's trust-token-redemption Feature Policy +// feature to see if the feature is definitely disabled or potentially enabled. +// +// This information will be bound to a URLLoaderFactory; if the answer is +// "definitely disabled," the network service will report a bad message if it +// receives a request from the renderer to execute a Trust Tokens redemption or +// signing operation in the frame. +// +// A return value of kForbid denotes that the feature is disabled for the +// frame. A return value of kPotentiallyPermit means that all trusted +// information sources say that the policy is enabled. +network::mojom::TrustTokenRedemptionPolicy +DetermineWhetherToForbidTrustTokenRedemption( + const RenderFrameHostImpl* parent, + const mojom::CommitNavigationParams& commit_params, + const url::Origin& subframe_origin) { + // For main frame loads, the frame's feature policy is determined entirely by + // response headers, which are provided by the renderer. + if (!parent || !commit_params.frame_policy) + return network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit; + + const blink::FeaturePolicy* parent_policy = parent->feature_policy(); + blink::ParsedFeaturePolicy container_policy = + commit_params.frame_policy->container_policy; + + auto subframe_policy = blink::FeaturePolicy::CreateFromParentPolicy( + parent_policy, container_policy, subframe_origin); + + if (subframe_policy->IsFeatureEnabled( + blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption)) { + return network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit; + } + return network::mojom::TrustTokenRedemptionPolicy::kForbid; +} + +// When a frame creates its initial subresource loaders, it needs to know +// whether the trust-token-redemption Feature Policy feature will be enabled +// after the commit finishes, which is a little involved (see +// DetermineWhetherToForbidTrustTokenRedemption). In contrast, if it needs to +// make this decision once the frame has committted---for instance, to create +// more loaders after the network service crashes---it can directly consult the +// current Feature Policy state to determine whether the feature is enabled. +network::mojom::TrustTokenRedemptionPolicy +DetermineAfterCommitWhetherToForbidTrustTokenRedemption( + RenderFrameHostImpl* impl) { + return impl->IsFeatureEnabled( + blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption) + ? network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit + : network::mojom::TrustTokenRedemptionPolicy::kForbid; +} + +// Returns the string corresponding to LifecycleState, used for logging crash +// keys. +const char* LifecycleStateToString(RenderFrameHostImpl::LifecycleState state) { + using LifecycleState = RenderFrameHostImpl::LifecycleState; + switch (state) { + case LifecycleState::kSpeculative: + return "Speculative"; + case LifecycleState::kActive: + return "Active"; + case LifecycleState::kInBackForwardCache: + return "InBackForwardCache"; + case LifecycleState::kRunningUnloadHandlers: + return "RunningUnloadHandlers"; + case LifecycleState::kReadyToBeDeleted: + return "ReadyToDeleted"; } - NOTREACHED(); - return false; } } // namespace @@ -757,6 +851,23 @@ RenderFrameHostImpl* RenderFrameHostImpl::FromID(int render_process_id, return RenderFrameHostImpl::FromID( GlobalFrameRoutingId(render_process_id, render_frame_id)); } + +// static +RenderFrameHostImpl* RenderFrameHostImpl::FromFrameToken( + int process_id, + const base::UnguessableToken& frame_token) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + auto it = g_token_frame_map.Get().find(frame_token); + if (it == g_token_frame_map.Get().end()) + return nullptr; + + // TODO(tonikitoo): Consider killing the renderer when this happens + if (it->second->GetProcess()->GetID() != process_id) + return nullptr; + + return it->second; +} + // static RenderFrameHost* RenderFrameHost::FromAXTreeID(ui::AXTreeID ax_tree_id) { return RenderFrameHostImpl::FromAXTreeID(ax_tree_id); @@ -822,6 +933,7 @@ RenderFrameHostImpl::RenderFrameHostImpl( web_ui_type_(WebUI::kNoWebUI), has_selection_(false), is_audible_(false), + should_virtual_keyboard_overlay_content_(false), last_navigation_previews_state_(PREVIEWS_UNSPECIFIED), waiting_for_init_(renderer_initiated_creation), has_focused_editable_element_(false), @@ -872,10 +984,6 @@ RenderFrameHostImpl::RenderFrameHostImpl( // // Local roots require a RenderWidget for input/layout/painting. if (!parent_ || IsCrossProcessSubframe()) { - mojo::PendingRemote<mojom::Widget> widget; - GetRemoteInterfaces()->GetInterface( - widget.InitWithNewPipeAndPassReceiver()); - if (!parent_) { // For main frames, the RenderWidgetHost is owned by the RenderViewHost. // TODO(https://crbug.com/545684): Once RenderViewHostImpl has-a @@ -883,10 +991,6 @@ RenderFrameHostImpl::RenderFrameHostImpl( // owning the RenderWidgetHostImpl itself. DCHECK(GetLocalRenderWidgetHost()); DCHECK(!GetLocalRenderWidgetHost()->owned_by_render_frame_host()); - - // Make the RenderWidgetHostImpl able to call the mojo Widget interface - // (implemented by the RenderWidgetImpl). - GetLocalRenderWidgetHost()->SetWidget(std::move(widget)); } else { // For local child roots, the RenderFrameHost directly creates and owns // its RenderWidgetHost. @@ -895,7 +999,7 @@ RenderFrameHostImpl::RenderFrameHostImpl( DCHECK_EQ(nullptr, GetLocalRenderWidgetHost()); owned_render_widget_host_ = RenderWidgetHostFactory::Create( frame_tree_->render_widget_delegate(), GetProcess(), - widget_routing_id, std::move(widget), /*hidden=*/true); + widget_routing_id, /*hidden=*/true); owned_render_widget_host_->set_owned_by_render_frame_host(true); #if defined(OS_ANDROID) owned_render_widget_host_->SetForceEnableZoom( @@ -906,8 +1010,6 @@ RenderFrameHostImpl::RenderFrameHostImpl( if (is_main_frame()) GetLocalRenderWidgetHost()->SetIntersectsViewport(true); GetLocalRenderWidgetHost()->SetFrameDepth(frame_tree_node_->depth()); - GetLocalRenderWidgetHost()->SetFrameInputHandler( - frame_input_handler_.get()); GetLocalRenderWidgetHost()->input_router()->SetFrameTreeNodeId( frame_tree_node_->frame_tree_node_id()); } @@ -948,7 +1050,7 @@ RenderFrameHostImpl::~RenderFrameHostImpl() { // RenderFrameHost during cleanup. ClearWebUI(); - SetLastCommittedSiteUrl(GURL()); + SetLastCommittedSiteInfo(GURL()); if (last_committed_document_priority_) { GetProcess()->UpdateFrameWithPriority(last_committed_document_priority_, base::nullopt); @@ -968,7 +1070,8 @@ RenderFrameHostImpl::~RenderFrameHostImpl() { // streams from this frame have terminated. This is required to ensure the // process host has the correct media stream count, which affects its // background priority. - OnAudibleStateChanged(false); + if (is_audible_) + OnAudibleStateChanged(false); // If this was the last active frame in the SiteInstance, the // DecrementActiveFrameCount call will trigger the deletion of the @@ -1027,7 +1130,7 @@ RenderFrameHostImpl::~RenderFrameHostImpl() { // follows that |GetMainFrame()| will never return the speculative main frame // being deleted, since it must have already been unset. if (was_created && render_view_host_->GetMainFrame() != this) - CHECK(!is_active()); + CHECK(IsPendingDeletion() || IsInBackForwardCache()); GetProcess()->RemoveRoute(routing_id_); g_routing_id_frame_map.Get().erase( @@ -1050,9 +1153,6 @@ RenderFrameHostImpl::~RenderFrameHostImpl() { if (owned_render_widget_host_) owned_render_widget_host_->ShutdownAndDestroyWidget(false); - // This needs to be deleted before |frame_input_handler_| so associated - // remotes can send messages during shutdown. See crbug.com/1010478 for - // details. render_view_host_.reset(); // If another frame is waiting for a beforeunload completion callback from @@ -1074,16 +1174,20 @@ int RenderFrameHostImpl::GetRoutingID() { return routing_id_; } +const base::UnguessableToken& RenderFrameHostImpl::GetFrameToken() { + return frame_token_; +} + ui::AXTreeID RenderFrameHostImpl::GetAXTreeID() { return ax_tree_id(); } -const base::UnguessableToken& RenderFrameHostImpl::GetTopFrameToken() const { - const RenderFrameHostImpl* frame = this; +const base::UnguessableToken& RenderFrameHostImpl::GetTopFrameToken() { + RenderFrameHostImpl* frame = this; while (frame->parent_) { frame = frame->parent_; } - return frame->frame_token(); + return frame->GetFrameToken(); } void RenderFrameHostImpl::AudioContextPlaybackStarted(int audio_context_id) { @@ -1160,11 +1264,9 @@ void RenderFrameHostImpl::StartBackForwardCacheEvictionTimer() { DCHECK(IsInBackForwardCache()); base::TimeDelta evict_after = BackForwardCacheImpl::GetTimeToLiveInBackForwardCache(); - NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( - frame_tree_node_->navigator()->GetController()); back_forward_cache_eviction_timer_.SetTaskRunner( - controller->GetBackForwardCache().GetTaskRunner()); + frame_tree()->controller()->GetBackForwardCache().GetTaskRunner()); back_forward_cache_eviction_timer_.Start( FROM_HERE, evict_after, @@ -1184,17 +1286,11 @@ void RenderFrameHostImpl::OnGrantedMediaStreamAccess() { } void RenderFrameHostImpl::OnPortalActivated( - std::unique_ptr<WebContents> predecessor_web_contents, + std::unique_ptr<Portal> predecessor, + mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal, + mojo::PendingAssociatedReceiver<blink::mojom::PortalClient> client_receiver, blink::TransferableMessage data, base::OnceCallback<void(blink::mojom::PortalActivateResult)> callback) { - mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal; - auto portal_receiver = pending_portal.InitWithNewEndpointAndPassReceiver(); - mojo::PendingAssociatedRemote<blink::mojom::PortalClient> pending_client; - auto client_receiver = pending_client.InitWithNewEndpointAndPassReceiver(); - - auto predecessor = - std::make_unique<Portal>(this, std::move(predecessor_web_contents)); - predecessor->Bind(std::move(portal_receiver), std::move(pending_client)); auto it = portals_.insert(std::move(predecessor)).first; GetNavigationControl()->OnPortalActivated( @@ -1361,15 +1457,19 @@ void RenderFrameHostImpl::GetCanonicalUrlForSharing( } } -mojo::Remote<blink::mojom::PauseSubresourceLoadingHandle> -RenderFrameHostImpl::PauseSubresourceLoading() { - DCHECK(frame_); - mojo::Remote<blink::mojom::PauseSubresourceLoadingHandle> - pause_subresource_loading_handle; - GetRemoteInterfaces()->GetInterface( - pause_subresource_loading_handle.BindNewPipeAndPassReceiver()); +void RenderFrameHostImpl::GetSerializedHtmlWithLocalLinks( + const base::flat_map<GURL, base::FilePath>& url_map, + const base::flat_map<base::UnguessableToken, base::FilePath>& + frame_token_map, + bool save_with_empty_url, + mojo::PendingRemote<mojom::FrameHTMLSerializerHandler> serializer_handler) { + // TODO(https://crbug.com/859110): Remove once frame_ can no longer be null. + if (!IsRenderFrameLive()) + return; - return pause_subresource_loading_handle; + frame_->GetSerializedHtmlWithLocalLinks(url_map, frame_token_map, + save_with_empty_url, + std::move(serializer_handler)); } void RenderFrameHostImpl::ExecuteMediaPlayerActionAtLocation( @@ -1403,7 +1503,8 @@ bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactory( CreateURLLoaderFactoryParamsForMainWorld( last_committed_origin_, mojo::Clone(last_committed_client_security_state_), - std::move(coep_reporter_remote)), + std::move(coep_reporter_remote), + DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this)), std::move(default_factory_receiver)); } @@ -1436,7 +1537,8 @@ void RenderFrameHostImpl::MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( CreateURLLoaderFactoriesForIsolatedWorlds( GetExpectedMainWorldOriginForUrlLoaderFactory(), isolated_world_origins, - mojo::Clone(last_committed_client_security_state_)); + mojo::Clone(last_committed_client_security_state_), + DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this)); GetNavigationControl()->UpdateSubresourceLoaderFactories( std::move(subresource_loader_factories)); } @@ -1456,7 +1558,8 @@ blink::PendingURLLoaderFactoryBundle::OriginMap RenderFrameHostImpl::CreateURLLoaderFactoriesForIsolatedWorlds( const url::Origin& main_world_origin, const base::flat_set<url::Origin>& isolated_world_origins, - network::mojom::ClientSecurityStatePtr client_security_state) { + network::mojom::ClientSecurityStatePtr client_security_state, + network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy) { WebPreferences preferences = GetRenderViewHost()->GetWebkitPreferences(); blink::PendingURLLoaderFactoryBundle::OriginMap result; @@ -1464,7 +1567,7 @@ RenderFrameHostImpl::CreateURLLoaderFactoriesForIsolatedWorlds( network::mojom::URLLoaderFactoryParamsPtr factory_params = URLLoaderFactoryParamsHelper::CreateForIsolatedWorld( this, isolated_world_origin, main_world_origin, - mojo::Clone(client_security_state)); + mojo::Clone(client_security_state), trust_token_redemption_policy); mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote; CreateNetworkServiceDefaultFactoryAndObserve( @@ -1637,17 +1740,11 @@ bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderFrameHostImpl, msg) IPC_MESSAGE_HANDLER(FrameHostMsg_Detach, OnDetach) - IPC_MESSAGE_HANDLER(FrameHostMsg_UpdateState, OnUpdateState) - IPC_MESSAGE_HANDLER(FrameHostMsg_OpenURL, OnOpenURL) IPC_MESSAGE_HANDLER(FrameHostMsg_Unload_ACK, OnUnloadACK) IPC_MESSAGE_HANDLER(FrameHostMsg_ContextMenu, OnContextMenu) IPC_MESSAGE_HANDLER(FrameHostMsg_VisualStateResponse, OnVisualStateResponse) - IPC_MESSAGE_HANDLER(FrameHostMsg_DidChangeOpener, OnDidChangeOpener) - IPC_MESSAGE_HANDLER(FrameHostMsg_DidChangeFramePolicy, - OnDidChangeFramePolicy) IPC_MESSAGE_HANDLER(FrameHostMsg_DidStopLoading, OnDidStopLoading) IPC_MESSAGE_HANDLER(FrameHostMsg_SelectionChanged, OnSelectionChanged) - IPC_MESSAGE_HANDLER(FrameHostMsg_FrameDidCallFocus, OnFrameDidCallFocus) IPC_END_MESSAGE_MAP() // No further actions here, since we may have been deleted. @@ -1668,26 +1765,29 @@ void RenderFrameHostImpl::OnAssociatedInterfaceRequest( void RenderFrameHostImpl::AccessibilityPerformAction( const ui::AXActionData& action_data) { - if (!is_active() || !render_accessibility_) + // Don't perform any Accessibility action on an inactive frame. + if (IsInactiveAndDisallowReactivation() || !render_accessibility_) return; - // Use the dedicated HitTest method so that we can handle its response via - // mojo callback once it's been handled in the renderer process. if (action_data.action == ax::mojom::Action::kHitTest) { - render_accessibility_->HitTest( - action_data, - base::BindOnce(&RenderFrameHostImpl::RequestAXHitTestCallback, - weak_ptr_factory_.GetWeakPtr(), action_data.request_id)); + AccessibilityHitTest(action_data.target_point, + action_data.hit_test_event_to_fire, + action_data.request_id, {}); return; } + // Set the input modality in RenderWidgetHostViewAura to touch so the + // VK shows up. + if (action_data.action == ax::mojom::Action::kDoDefault) { + RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( + render_view_host_->GetWidget()->GetView()); + if (view) + view->SetLastPointerType(ui::EventPointerType::kTouch); + } render_accessibility_->PerformAction(action_data); } bool RenderFrameHostImpl::AccessibilityViewHasFocus() { - if (!is_active()) - return false; - RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView(); if (view) return view->HasFocus(); @@ -1695,7 +1795,8 @@ bool RenderFrameHostImpl::AccessibilityViewHasFocus() { } void RenderFrameHostImpl::AccessibilityViewSetFocus() { - if (!is_active()) + // Don't update Accessibility for inactive frames. + if (IsInactiveAndDisallowReactivation()) return; RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView(); @@ -1704,9 +1805,6 @@ void RenderFrameHostImpl::AccessibilityViewSetFocus() { } gfx::Rect RenderFrameHostImpl::AccessibilityGetViewBounds() { - if (!is_active()) - return gfx::Rect(); - RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView(); if (view) return view->GetViewBounds(); @@ -1714,9 +1812,6 @@ gfx::Rect RenderFrameHostImpl::AccessibilityGetViewBounds() { } float RenderFrameHostImpl::AccessibilityGetDeviceScaleFactor() { - if (!is_active()) - return 1.0f; - RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView(); if (view) return GetScaleFactorForView(view); @@ -1749,7 +1844,7 @@ RenderFrameHostImpl::AccessibilityGetAcceleratedWidget() { // Only the main frame's current frame host is connected to the native // widget tree for accessibility, so return null if this is queried on // any other frame. - if (!is_active() || !is_main_frame() || !IsCurrent()) + if (!is_main_frame() || !IsCurrent()) return gfx::kNullAcceleratedWidget; RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( @@ -1761,7 +1856,10 @@ RenderFrameHostImpl::AccessibilityGetAcceleratedWidget() { gfx::NativeViewAccessible RenderFrameHostImpl::AccessibilityGetNativeViewAccessible() { - if (!is_active()) + // If this method is called when the document is in BackForwardCache, evict + // the document to avoid ignoring any accessibility related events which the + // document might not expect. + if (IsInactiveAndDisallowReactivation()) return nullptr; RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( @@ -1773,7 +1871,10 @@ RenderFrameHostImpl::AccessibilityGetNativeViewAccessible() { gfx::NativeViewAccessible RenderFrameHostImpl::AccessibilityGetNativeViewAccessibleForWindow() { - if (!is_active()) + // If this method is called when the frame is in BackForwardCache, evict + // the frame to avoid ignoring any accessibility related events which are not + // expected. + if (IsInactiveAndDisallowReactivation()) return nullptr; RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( @@ -1784,15 +1885,38 @@ RenderFrameHostImpl::AccessibilityGetNativeViewAccessibleForWindow() { } WebContents* RenderFrameHostImpl::AccessibilityWebContents() { - if (!is_active()) + // If this method is called when the frame is in BackForwardCache, evict + // the frame to avoid ignoring any accessibility related events which are not + // expected. + if (IsInactiveAndDisallowReactivation()) return nullptr; return delegate()->GetAsWebContents(); } +void RenderFrameHostImpl::AccessibilityHitTest( + const gfx::Point& point_in_frame_pixels, + ax::mojom::Event opt_event_to_fire, + int opt_request_id, + base::OnceCallback<void(BrowserAccessibilityManager* hit_manager, + int hit_node_id)> opt_callback) { + // This is called by BrowserAccessibilityManager. During teardown it's + // possible that render_accessibility_ is null but the corresponding + // BrowserAccessibilityManager still exists and could call this. + if (IsInactiveAndDisallowReactivation() || !render_accessibility_) { + if (opt_callback) + std::move(opt_callback).Run(nullptr, 0); + return; + } + + render_accessibility_->HitTest( + point_in_frame_pixels, opt_event_to_fire, opt_request_id, + base::BindOnce(&RenderFrameHostImpl::AccessibilityHitTestCallback, + weak_ptr_factory_.GetWeakPtr(), opt_request_id, + opt_event_to_fire, std::move(opt_callback))); +} + bool RenderFrameHostImpl::AccessibilityIsMainFrame() { - if (!is_active()) - return false; - return frame_tree_node()->IsMainFrame(); + return is_main_frame(); } void RenderFrameHostImpl::RenderProcessExited( @@ -1886,7 +2010,8 @@ void RenderFrameHostImpl::RenderProcessGone( // process should be ignored until the next commit. set_nav_entry_id(0); - OnAudibleStateChanged(false); + if (is_audible_) + OnAudibleStateChanged(false); } void RenderFrameHostImpl::ReportContentSecurityPolicyViolation( @@ -1954,16 +2079,11 @@ bool RenderFrameHostImpl::SchemeShouldBypassCSP( return base::Contains(bypassing_schemes, scheme); } -mojom::FrameInputHandler* RenderFrameHostImpl::GetFrameInputHandler() { - if (!frame_input_handler_) - return nullptr; - return frame_input_handler_.get(); -} - -bool RenderFrameHostImpl::CreateRenderFrame(int previous_routing_id, - int opener_routing_id, - int parent_routing_id, - int previous_sibling_routing_id) { +bool RenderFrameHostImpl::CreateRenderFrame( + int previous_routing_id, + const base::Optional<base::UnguessableToken>& opener_frame_token, + int parent_routing_id, + int previous_sibling_routing_id) { TRACE_EVENT0("navigation", "RenderFrameHostImpl::CreateRenderFrame"); DCHECK(!IsRenderFrameLive()) << "Creating frame twice"; @@ -1992,7 +2112,7 @@ bool RenderFrameHostImpl::CreateRenderFrame(int previous_routing_id, params->routing_id = routing_id_; params->previous_routing_id = previous_routing_id; - params->opener_routing_id = opener_routing_id; + params->opener_frame_token = opener_frame_token; params->parent_routing_id = parent_routing_id; params->previous_sibling_routing_id = previous_sibling_routing_id; params->replication_state = frame_tree_node()->current_replication_state(); @@ -2013,7 +2133,7 @@ bool RenderFrameHostImpl::CreateRenderFrame(int previous_routing_id, NavigationRequest* navigation_request = frame_tree_node()->navigation_request(); if (navigation_request && - navigation_request->require_coop_browsing_instance_swap()) { + navigation_request->coop_status().require_browsing_instance_swap) { params->replication_state.name = ""; // "COOP swaps" only affect main frames, that have an empty unique name. DCHECK(params->replication_state.unique_name.empty()); @@ -2053,12 +2173,10 @@ bool RenderFrameHostImpl::CreateRenderFrame(int previous_routing_id, rwh->BindNewFrameWidgetInterfaces(); } - // TODO(https://crbug.com/1006814): Remove this. - if (params->previous_routing_id == MSG_ROUTING_NONE && - params->parent_routing_id == MSG_ROUTING_NONE) { - base::debug::DumpWithoutCrashing(); - NOTREACHED(); - } + // https://crbug.com/1006814. The renderer needs at least one of these IDs to + // be able to insert the new frame in the frame tree. + DCHECK(params->previous_routing_id != MSG_ROUTING_NONE || + params->parent_routing_id != MSG_ROUTING_NONE); GetProcess()->GetRendererInterface()->CreateFrame(std::move(params)); if (previous_routing_id != MSG_ROUTING_NONE) { @@ -2124,6 +2242,22 @@ void RenderFrameHostImpl::SetRenderFrameCreated(bool created) { bool was_created = render_frame_created_; render_frame_created_ = created; + // Clear all the user data associated with this RenderFrameHost when its + // RenderFrame is recreated after a crash. Checking + // |was_render_frame_ever_created_| guarantees that the user data isn't + // cleared for the initial RenderFrame creation. Note that the user data is + // intentionally not cleared at the time of crash. Please refer to + // https://crbug.com/1099237 for more details. + // + // Clearing of user data should be called before RenderFrameCreated to ensure: + // - a) new new state set in RenderFrameCreated doesn't get deleted. + // - b) the old state is not leaked to a new RenderFrameHost. + if (!was_created && created && was_render_frame_ever_created_) + document_associated_data_.ClearAllUserData(); + + if (created) + was_render_frame_ever_created_ = true; + // If the current status is different than the new status, the delegate // needs to be notified. if (created != was_created) { @@ -2139,12 +2273,6 @@ void RenderFrameHostImpl::SetRenderFrameCreated(bool created) { CHECK(frame_); if (created && GetLocalRenderWidgetHost()) { - mojo::PendingRemote<mojom::Widget> widget; - GetRemoteInterfaces()->GetInterface( - widget.InitWithNewPipeAndPassReceiver()); - GetLocalRenderWidgetHost()->SetWidget(std::move(widget)); - GetLocalRenderWidgetHost()->SetFrameInputHandler( - frame_input_handler_.get()); GetLocalRenderWidgetHost()->input_router()->SetFrameTreeNodeId( frame_tree_node_->frame_tree_node_id()); mojo::Remote<viz::mojom::InputTargetClient> input_target_client; @@ -2161,11 +2289,6 @@ void RenderFrameHostImpl::SetRenderFrameCreated(bool created) { GetRemoteAssociatedInterfaces()->GetInterface(&frame_bindings_control_); frame_bindings_control_->AllowBindings(enabled_bindings_); } - - // Clear all the user data associated with this RenderFrameHost in case if - // the renderer crashes and the RenderFrameHost still stays alive. - if (!created) - document_associated_data_.ClearAllUserData(); } void RenderFrameHostImpl::SwapIn() { @@ -2179,7 +2302,7 @@ void RenderFrameHostImpl::Init() { waiting_for_init_ = false; if (pending_navigate_) { - frame_tree_node()->navigator()->OnBeginNavigation( + frame_tree_node()->navigator().OnBeginNavigation( frame_tree_node(), std::move(pending_navigate_->common_params), std::move(pending_navigate_->begin_navigation_params), std::move(pending_navigate_->blob_url_loader_factory), @@ -2192,13 +2315,14 @@ void RenderFrameHostImpl::Init() { } void RenderFrameHostImpl::OnAudibleStateChanged(bool is_audible) { - if (is_audible_ == is_audible) - return; - if (is_audible) + DCHECK_NE(is_audible_, is_audible); + if (is_audible) { GetProcess()->OnMediaStreamAdded(); - else + } else { GetProcess()->OnMediaStreamRemoved(); + } is_audible_ = is_audible; + delegate_->OnFrameAudioStateChanged(this, is_audible_); } void RenderFrameHostImpl::DidAddMessageToConsole( @@ -2252,10 +2376,10 @@ void RenderFrameHostImpl::OnCreateChildFrame( } // The RenderFrame corresponding to this host sent an IPC message to create a - // child, but by the time we get here, it's possible for the host to have been - // swapped out, or for its process to have disconnected (maybe due to browser - // shutdown). Ignore such messages. - if (!is_active() || !IsCurrent() || !render_frame_created_) + // child, but by the time we get here, it's possible for the RenderFrameHost + // to become pending deletion, or for its process to have disconnected (maybe + // due to browser shutdown). Ignore such messages. + if (IsInactiveAndDisallowReactivation() || !render_frame_created_) return; // |new_routing_id|, |new_interface_provider_provider_receiver|, @@ -2391,7 +2515,7 @@ net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal( frame_origin, candidate_site_for_cookies); } -void RenderFrameHostImpl::SetOriginAndIsolationInfoOfNewFrame( +void RenderFrameHostImpl::SetOriginDependentStateOfNewFrame( const url::Origin& new_frame_creator) { // This method should only be called for *new* frames, that haven't committed // a navigation yet. @@ -2410,6 +2534,13 @@ void RenderFrameHostImpl::SetOriginAndIsolationInfoOfNewFrame( isolation_info_ = ComputeIsolationInfoInternal( new_frame_origin, net::IsolationInfo::RedirectMode::kUpdateNothing); SetLastCommittedOrigin(new_frame_origin); + + // Construct the frame's feature policy only once we know its initial + // committed origin. It's necessary to wait for the origin because the feature + // policy's state depends on the origin, so the FeaturePolicy object could be + // configured incorrectly if it were initialized before knowing the value of + // |last_committed_origin_|. More at crbug.com/1112959. + ResetFeaturePolicy(); } FrameTreeNode* RenderFrameHostImpl::AddChild( @@ -2435,7 +2566,7 @@ FrameTreeNode* RenderFrameHostImpl::AddChild( // When the child is added, it hasn't committed any navigation yet - its // initial empty document should inherit the origin of its parent (the origin // may change after the first commit). See also https://crbug.com/932067. - child->current_frame_host()->SetOriginAndIsolationInfoOfNewFrame( + child->current_frame_host()->SetOriginDependentStateOfNewFrame( GetLastCommittedOrigin()); children_.push_back(std::move(child)); @@ -2508,6 +2639,11 @@ void RenderFrameHostImpl::OnDetach() { return; } + // Ignore FrameHostMsg_Detach IPC message, if the RenderFrameHost should be + // left in pending deletion state. + if (do_not_delete_for_testing_) + return; + if (IsPendingDeletion()) { // The frame is pending deletion. FrameHostMsg_Detach is used to confirm // its unload handlers ran. Note that it is possible for a frame to already @@ -2529,7 +2665,7 @@ void RenderFrameHostImpl::OnDetach() { // descendant frames to execute unload handlers. Start executing those // handlers now. StartPendingDeletionOnSubtree(); - frame_tree_node_->frame_tree()->FrameUnloading(frame_tree_node_); + frame_tree()->FrameUnloading(frame_tree_node_); // Some children with no unload handler may be eligible for immediate // deletion. Cut the dead branches now. This is a performance optimization. @@ -2545,12 +2681,17 @@ void RenderFrameHostImpl::DidFailLoadWithError(const GURL& url, GURL validated_url(url); GetProcess()->FilterURL(false, &validated_url); - frame_tree_node_->navigator()->DidFailLoadWithError(this, validated_url, - error_code); + frame_tree_node_->navigator().DidFailLoadWithError(this, validated_url, + error_code); } void RenderFrameHostImpl::DidFocusFrame() { - if (!is_active()) + // We don't handle this IPC signal for non-active RenderFrameHost. + // + // For RenderFrameHost in BackForwardCache, it is safe to ignore this IPC as + // there is a renderer side check (see Document::IsFocusedAllowed) which + // returns false. + if (lifecycle_state_ != LifecycleState::kActive) return; // We need to handle receiving this IPC from a frame that is inside a portal @@ -2565,6 +2706,10 @@ void RenderFrameHostImpl::DidFocusFrame() { delegate_->SetFocusedFrame(frame_tree_node_, GetSiteInstance()); } +void RenderFrameHostImpl::DidCallFocus() { + delegate_->DidCallFocus(); +} + void RenderFrameHostImpl::DidAddContentSecurityPolicies( std::vector<network::mojom::ContentSecurityPolicyPtr> policies) { TRACE_EVENT1("navigation", @@ -2579,27 +2724,6 @@ void RenderFrameHostImpl::DidAddContentSecurityPolicies( frame_tree_node()->AddContentSecurityPolicies(std::move(headers)); } -void RenderFrameHostImpl::OnOpenURL(const FrameHostMsg_OpenURL_Params& params) { - // Verify and unpack IPC payload. - GURL validated_url; - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; - if (!VerifyOpenURLParams(GetSiteInstance(), params, &validated_url, - &blob_url_loader_factory)) { - return; - } - - TRACE_EVENT1("navigation", "RenderFrameHostImpl::OpenURL", "url", - validated_url.possibly_invalid_spec()); - - frame_tree_node_->navigator()->RequestOpenURL( - this, validated_url, - GlobalFrameRoutingId(GetProcess()->GetID(), params.initiator_routing_id), - params.initiator_origin, params.post_body, params.extra_headers, - params.referrer, params.disposition, params.should_replace_current_entry, - params.user_gesture, params.triggering_event_info, params.href_translate, - std::move(blob_url_loader_factory), params.impression); -} - void RenderFrameHostImpl::CancelInitialHistoryLoad() { // A Javascript navigation interrupted the initial history load. Check if an // initial subframe cross-process navigation needs to be canceled as a result. @@ -2617,13 +2741,14 @@ void RenderFrameHostImpl::DidChangeActiveSchedulerTrackedFeatures( void RenderFrameHostImpl::OnSchedulerTrackedFeatureUsed( blink::scheduler::WebSchedulerTrackedFeature feature) { browser_reported_scheduler_tracked_features_ |= - 1 << static_cast<uint64_t>(feature); + 1ull << static_cast<uint64_t>(feature); MaybeEvictFromBackForwardCache(); } bool RenderFrameHostImpl::IsFrozen() { - return frame_lifecycle_state_ != blink::mojom::FrameLifecycleState::kRunning; + // TODO(crbug.com/1081920): Account for non-bfcache freezing here as well. + return lifecycle_state_ == LifecycleState::kInBackForwardCache; } void RenderFrameHostImpl::DidCommitProvisionalLoad( @@ -2663,9 +2788,17 @@ void RenderFrameHostImpl::DidCommitBackForwardCacheNavigation( } void RenderFrameHostImpl::SetEmbeddingToken( - const base::Optional<base::UnguessableToken>& embedding_token) { + const base::UnguessableToken& embedding_token) { embedding_token_ = embedding_token; - if (!embedding_token_.has_value()) + + // We only need to propagate the token to the parent frame if it's + // remote. For local parents the propagation occurs within the renderer + // process. The token is also present on the main frame for generalization + // when the main frame in embedded in another context (e.g. browser UI). + // The main frame is not embedded in the context of the frame tree so it + // is not propagated here. See RenderFrameHost::GetEmbeddingToken for more + // details. + if (!IsCrossProcessSubframe()) return; // Only non-null tokens are propagated to the parent document. The token is @@ -2703,8 +2836,8 @@ void RenderFrameHostImpl::DidCommitPerNavigationMojoInterfaceNavigation( void RenderFrameHostImpl::DidCommitSameDocumentNavigation( std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params) { - ScopedActiveURL scoped_active_url( - params->url, frame_tree_node()->frame_tree()->root()->current_origin()); + ScopedActiveURL scoped_active_url(params->url, + frame_tree()->root()->current_origin()); ScopedCommitStateResetter commit_state_resetter(this); // When the frame is pending deletion, the browser is waiting for it to unload @@ -2715,7 +2848,10 @@ void RenderFrameHostImpl::DidCommitSameDocumentNavigation( // See https://crbug.com/805705 and https://crbug.com/930132. // TODO(ahemery): Investigate to see if this can be removed when the // NavigationClient interface is implemented. - if (!is_active()) + // If this is called when the frame is in BackForwardCache, evict the frame + // to avoid ignoring the renderer-initiated navigation, which the frame + // might not expect. + if (IsInactiveAndDisallowReactivation()) return; TRACE_EVENT2("navigation", @@ -2740,20 +2876,6 @@ void RenderFrameHostImpl::DidCommitSameDocumentNavigation( commit_state_resetter.disable(); } -void RenderFrameHostImpl::OnUpdateState(const PageState& state) { - // TODO(creis): Verify the state's ISN matches the last committed FNE. - - // Without this check, the renderer can trick the browser into using - // filenames it can't access in a future session restore. - if (!CanAccessFilesOfPageState(state)) { - bad_message::ReceivedBadMessage( - GetProcess(), bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE); - return; - } - - delegate_->UpdateStateForFrame(this, state); -} - RenderWidgetHostImpl* RenderFrameHostImpl::GetRenderWidgetHost() { RenderFrameHostImpl* frame = this; while (frame) { @@ -2864,7 +2986,7 @@ void RenderFrameHostImpl::DetachFromProxy() { // Start pending deletion on this frame and its children. DeleteRenderFrame(FrameDeleteIntention::kNotMainFrame); StartPendingDeletionOnSubtree(); - frame_tree_node_->frame_tree()->FrameUnloading(frame_tree_node_); + frame_tree()->FrameUnloading(frame_tree_node_); // Some children with no unload handler may be eligible for immediate // deletion. Cut the dead branches now. This is a performance optimization. @@ -2960,7 +3082,7 @@ void RenderFrameHostImpl::ProcessBeforeUnloadCompletedFromFrame( UMA_HISTOGRAM_TIMES("Navigation.OnBeforeUnloadOverheadTime", on_before_unload_overhead_time); - frame_tree_node_->navigator()->LogBeforeUnloadTime( + frame_tree_node_->navigator().LogBeforeUnloadTime( renderer_before_unload_start_time, renderer_before_unload_end_time); } @@ -2975,7 +3097,7 @@ void RenderFrameHostImpl::ProcessBeforeUnloadCompletedFromFrame( // current navigation stop/proceed. Otherwise, send it to the // RenderFrameHostManager which handles closing. if (unload_ack_is_for_navigation_) { - frame_tree_node_->navigator()->BeforeUnloadCompleted( + frame_tree_node_->navigator().BeforeUnloadCompleted( frame_tree_node_, proceed, before_unload_end_time); } else { // We could reach this from a subframe destructor for |frame| while we're @@ -3066,7 +3188,7 @@ void RenderFrameHostImpl::SetSubframeUnloadTimeoutForTesting( void RenderFrameHostImpl::OnContextMenu( const UntrustworthyContextMenuParams& params) { - if (!is_active()) + if (IsInactiveAndDisallowReactivation()) return; // Validate the URLs in |params|. If the renderer can't request the URLs @@ -3178,9 +3300,10 @@ void RenderFrameHostImpl::RunJavaScriptDialog( const base::string16& default_prompt, JavaScriptDialogType dialog_type, JavaScriptDialogCallback ipc_response_callback) { - // Don't show the dialog if it's triggered on a frame that's pending deletion - // (e.g., from an unload handler), or when the tab is being closed. - if (!is_active()) { + // Don't show the dialog if it's triggered on a non-active RenderFrameHost. + // This happens when the RenderFrameHost is pending deletion or in the + // back-forward cache. + if (lifecycle_state_ != LifecycleState::kActive) { std::move(ipc_response_callback).Run(true, base::string16()); return; } @@ -3261,13 +3384,6 @@ void RenderFrameHostImpl::RunBeforeUnloadConfirm( std::move(dialog_closed_callback)); } -void RenderFrameHostImpl::Are3DAPIsBlocked(Are3DAPIsBlockedCallback callback) { - bool blocked = GpuDataManagerImpl::GetInstance()->Are3DAPIsBlocked( - GetMainFrame()->GetLastCommittedURL(), GetProcess()->GetID(), - GetRoutingID(), THREE_D_API_TYPE_WEBGL); - std::move(callback).Run(blocked); -} - void RenderFrameHostImpl::ScaleFactorChanged(float scale) { delegate_->OnPageScaleFactorChanged(this, scale); } @@ -3282,6 +3398,10 @@ void RenderFrameHostImpl::TextAutosizerPageInfoChanged( delegate_->OnTextAutosizerPageInfoChanged(this, std::move(page_info)); } +void RenderFrameHostImpl::FocusPage() { + render_view_host_->OnFocus(); +} + void RenderFrameHostImpl::UpdateFaviconURL( std::vector<blink::mojom::FaviconURLPtr> favicon_urls) { delegate_->UpdateFaviconURL(this, std::move(favicon_urls)); @@ -3289,14 +3409,9 @@ void RenderFrameHostImpl::UpdateFaviconURL( void RenderFrameHostImpl::DownloadURL( blink::mojom::DownloadURLParamsPtr blink_parameters) { - mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token; - if (!VerifyDownloadUrlParams(GetSiteInstance(), blink_parameters.get(), - &blob_url_token)) + if (!VerifyDownloadUrlParams(GetSiteInstance(), *blink_parameters)) return; - mojo::PendingRemote<blink::mojom::Blob> blob_data_remote( - std::move(blink_parameters->data_url_blob), blink::mojom::Blob::Version_); - net::NetworkTrafficAnnotationTag traffic_annotation = net::DefineNetworkTrafficAnnotation("renderer_initiated_download", R"( semantics { @@ -3342,14 +3457,15 @@ void RenderFrameHostImpl::DownloadURL( blink_parameters->initiator_origin.value_or(url::Origin())); parameters->set_download_source(download::DownloadSource::FROM_RENDERER); - if (blob_data_remote) { + if (blink_parameters->data_url_blob) { DataURLBlobReader::ReadDataURLFromBlob( - std::move(blob_data_remote), + std::move(blink_parameters->data_url_blob), base::BindOnce(&OnDataURLRetrieved, std::move(parameters))); return; } - StartDownload(std::move(parameters), std::move(blob_url_token)); + StartDownload(std::move(parameters), + std::move(blink_parameters->blob_url_token)); } void RenderFrameHostImpl::ReportNoBinderForInterface(const std::string& error) { @@ -3369,6 +3485,16 @@ BrowserContext* RenderFrameHostImpl::GetBrowserContext() { return GetProcess()->GetBrowserContext(); } +// TODO(crbug.com/1091720): Would be better to do this directly in the chrome +// layer. See referenced bug for further details. +void RenderFrameHostImpl::ReportHeavyAdIssue( + blink::mojom::HeavyAdResolutionStatus resolution, + blink::mojom::HeavyAdReason reason) { + auto issue = + devtools_instrumentation::GetHeavyAdIssue(this, resolution, reason); + devtools_instrumentation::ReportBrowserInitiatedIssue(this, issue.get()); +} + StoragePartition* RenderFrameHostImpl::GetStoragePartition() { return BrowserContext::GetStoragePartition(GetBrowserContext(), GetSiteInstance()); @@ -3489,6 +3615,10 @@ void RenderFrameHostImpl::DoNotDeleteForTesting() { do_not_delete_for_testing_ = true; } +void RenderFrameHostImpl::ResumeDeletionForTesting() { + do_not_delete_for_testing_ = false; +} + bool RenderFrameHostImpl::IsFeatureEnabled( blink::mojom::FeaturePolicyFeature feature) { return feature_policy_ && feature_policy_->IsFeatureEnabledForOrigin( @@ -3555,7 +3685,8 @@ void RenderFrameHostImpl::UpdateSubresourceLoaderFactories() { CreateURLLoaderFactoryParamsForMainWorld( last_committed_origin_, mojo::Clone(last_committed_client_security_state_), - std::move(coep_reporter_remote)), + std::move(coep_reporter_remote), + DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this)), default_factory_remote.InitWithNewPipeAndPassReceiver()); } @@ -3567,7 +3698,9 @@ void RenderFrameHostImpl::UpdateSubresourceLoaderFactories() { CreateURLLoaderFactoriesForIsolatedWorlds( GetExpectedMainWorldOriginForUrlLoaderFactory(), isolated_worlds_requiring_separate_url_loader_factory_, - mojo::Clone(last_committed_client_security_state_)), + mojo::Clone(last_committed_client_security_state_), + DetermineAfterCommitWhetherToForbidTrustTokenRedemption( + this)), bypass_redirect_checks); GetNavigationControl()->UpdateSubresourceLoaderFactories( std::move(subresource_loader_factories)); @@ -3586,11 +3719,6 @@ void RenderFrameHostImpl::DidAccessInitialDocument() { delegate_->DidAccessInitialDocument(); } -void RenderFrameHostImpl::OnDidChangeOpener(int32_t opener_routing_id) { - frame_tree_node_->render_manager()->DidChangeOpener(opener_routing_id, - GetSiteInstance()); -} - void RenderFrameHostImpl::DidChangeName(const std::string& name, const std::string& unique_name) { if (GetParent() != nullptr) { @@ -3612,8 +3740,22 @@ void RenderFrameHostImpl::DidSetFramePolicyHeaders( network::mojom::WebSandboxFlags sandbox_flags, const blink::ParsedFeaturePolicy& feature_policy_header, const blink::DocumentPolicy::FeatureState& document_policy_header) { - if (!is_active()) + // TODO(https://crbug.com/1093268): Investigate why this IPC can be received + // before the navigation commit. This can be triggered when loading an error + // page using the test: + // CrossOriginOpenerPolicyBrowserTest.NetworkErrorOnSandboxedPopups. + if (lifecycle_state() == LifecycleState::kSpeculative) + return; + + // We should not be updating policy headers when the RenderFrameHost is in + // BackForwardCache. If this is called when the RenderFrameHost is in + // BackForwardCache, evict the document. + if (IsInactiveAndDisallowReactivation()) return; + + // We shouldn't update policy headers for non-current frames. + DCHECK(IsCurrent()); + // Rebuild |feature_policy_| for this frame. ResetFeaturePolicy(); feature_policy_->SetHeaderPolicy(feature_policy_header); @@ -3651,6 +3793,8 @@ void RenderFrameHostImpl::DidSetFramePolicyHeaders( // Save a copy of the now-active sandbox flags on this RFHI. active_sandbox_flags_ = frame_tree_node()->active_sandbox_flags(); + + CheckSandboxFlags(); } void RenderFrameHostImpl::EnforceInsecureRequestPolicy( @@ -3687,8 +3831,7 @@ RenderFrameHostImpl* RenderFrameHostImpl::FindAndVerifyChildInternal( if (!child_frame_or_proxy) return nullptr; - if (child_frame_or_proxy.GetFrameTreeNode()->frame_tree() != - frame_tree_node()->frame_tree()) { + if (child_frame_or_proxy.GetFrameTreeNode()->frame_tree() != frame_tree()) { // Ignore the cases when the child lives in a different frame tree. // This is possible when we create a proxy for inner WebContents (e.g. // for portals) so the |child_frame_or_proxy| points to the root frame @@ -3707,30 +3850,6 @@ RenderFrameHostImpl* RenderFrameHostImpl::FindAndVerifyChildInternal( : child_frame_or_proxy.frame; } -void RenderFrameHostImpl::OnDidChangeFramePolicy( - int32_t frame_routing_id, - const blink::FramePolicy& frame_policy) { - // Ensure that a frame can only update sandbox flags or feature policy for its - // immediate children. If this is not the case, the renderer is considered - // malicious and is killed. - RenderFrameHostImpl* child = FindAndVerifyChild( - // TODO(iclelland): Rename this message - frame_routing_id, bad_message::RFH_SANDBOX_FLAGS); - if (!child) - return; - - child->frame_tree_node()->SetPendingFramePolicy(frame_policy); - - // Notify the RenderFrame if it lives in a different process from its parent. - // The frame's proxies in other processes also need to learn about the updated - // flags and policy, but these notifications are sent later in - // RenderFrameHostManager::CommitPendingFramePolicy(), when the frame - // navigates and the new policies take effect. - if (child->GetSiteInstance() != GetSiteInstance()) { - child->GetAssociatedLocalFrame()->DidUpdateFramePolicy(frame_policy); - } -} - void RenderFrameHostImpl::UpdateTitle( const base::Optional<::base::string16>& title, base::i18n::TextDirection title_direction) { @@ -3767,7 +3886,7 @@ void RenderFrameHostImpl::FrameSizeChanged(const gfx::Size& frame_size) { } void RenderFrameHostImpl::FullscreenStateChanged(bool is_fullscreen) { - if (!is_active()) + if (IsInactiveAndDisallowReactivation()) return; delegate_->FullscreenStateChanged(this, is_fullscreen); } @@ -3815,9 +3934,9 @@ void RenderFrameHostImpl::DocumentAvailableInMainFrame( } void RenderFrameHostImpl::SetNeedsOcclusionTracking(bool needs_tracking) { - // Don't process the IPC if this RFH is pending deletion. See also + // Do not update the parent on behalf of inactive RenderFrameHost. See also // https://crbug.com/972566. - if (!is_active()) + if (IsInactiveAndDisallowReactivation()) return; RenderFrameProxyHost* proxy = @@ -3831,9 +3950,63 @@ void RenderFrameHostImpl::SetNeedsOcclusionTracking(bool needs_tracking) { proxy->GetAssociatedRemoteFrame()->SetNeedsOcclusionTracking(needs_tracking); } -void RenderFrameHostImpl::LifecycleStateChanged( - blink::mojom::FrameLifecycleState state) { - frame_lifecycle_state_ = state; +void RenderFrameHostImpl::SetVirtualKeyboardOverlayPolicy( + bool vk_overlays_content) { + should_virtual_keyboard_overlay_content_ = vk_overlays_content; +} + +bool RenderFrameHostImpl::ShouldVirtualKeyboardOverlayContent() const { + RenderFrameHostImpl* root_frame_host = + frame_tree_->root()->current_frame_host(); + return root_frame_host->should_virtual_keyboard_overlay_content_; +} + +void RenderFrameHostImpl::NotifyVirtualKeyboardOverlayRect( + const gfx::Rect& keyboard_rect) { + DCHECK(ShouldVirtualKeyboardOverlayContent()); + + RenderFrameHostImpl* root_frame_host = + frame_tree_->root()->current_frame_host(); + RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( + root_frame_host->render_view_host_->GetWidget()->GetView()); + if (!view) + return; + + gfx::PointF root_widget_origin(0.f, 0.f); + view->TransformPointToRootSurface(&root_widget_origin); + + gfx::Rect root_widget_rect; + if (!keyboard_rect.IsEmpty()) { + // If the rect is non-empty, we need to transform it to be widget-relative + // window (DIP coordinates). The input is client coordinates for the root + // window. + // Transform the widget rect origin to root relative coords. + root_widget_rect = gfx::Rect(root_widget_origin.x(), root_widget_origin.y(), + view->GetViewBounds().width(), + view->GetViewBounds().height()); + + // Intersect with the keyboard rect and transform back to widget-relative + // coordinates, which will be sent to the renderer. + root_widget_rect.Intersect(keyboard_rect); + root_widget_rect.Offset(-root_widget_origin.x(), -root_widget_origin.y()); + } + + // Notify each SiteInstance a single time. Renderer will take care of ensuring + // the event is dispatched to all relevant listeners in the grouping of frames + // for the SiteInstance. + // TODO(snianu): Transform from the main frame's coordinates to each + // individual frame client coordinates so that these are more usable from + // within iframes. + std::set<SiteInstance*> notified_instances; + for (RenderFrameHostImpl* node = this; node; node = node->GetParent()) { + SiteInstance* site_instance = node->GetSiteInstance(); + if (base::Contains(notified_instances, site_instance)) + continue; + + node->GetAssociatedLocalFrame()->NotifyVirtualKeyboardOverlayRect( + root_widget_rect); + notified_instances.insert(site_instance); + } } #if defined(OS_ANDROID) @@ -3845,7 +4018,6 @@ void RenderFrameHostImpl::UpdateUserGestureCarryoverInfo() { void RenderFrameHostImpl::VisibilityChanged( blink::mojom::FrameVisibility visibility) { visibility_ = visibility; - UpdateFrameFrozenState(); } void RenderFrameHostImpl::DidChangeThemeColor( @@ -3880,10 +4052,11 @@ void RenderFrameHostImpl::DispatchLoad() { TRACE_EVENT1("navigation", "RenderFrameHostImpl::DispatchLoad", "frame_tree_node", frame_tree_node_->frame_tree_node_id()); - // Don't forward the load event if this RFH is pending deletion. This can - // happen in a race where this RenderFrameHost finishes loading just after - // the frame navigates away. See https://crbug.com/626802. - if (!is_active()) + // Don't forward the load event to the parent on behalf of inactive + // RenderFrameHost. This can happen in a race where this inactive + // RenderFrameHost finishes loading just after the frame navigates away. + // See https://crbug.com/626802. + if (IsInactiveAndDisallowReactivation()) return; // We should never be receiving this message from a speculative RFH. @@ -3909,8 +4082,14 @@ void RenderFrameHostImpl::GoToEntryAtOffset(int32_t offset, void RenderFrameHostImpl::HandleAccessibilityFindInPageResult( blink::mojom::FindInPageResultAXParamsPtr params) { + // Only update FindInPageResult on active RenderFrameHost. Note that, it is + // safe to ignore this call for BackForwardCache, as we terminate the + // FindInPage session once the page enters BackForwardCache. + if (lifecycle_state_ != LifecycleState::kActive) + return; + ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); - if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs) && is_active()) { + if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) { BrowserAccessibilityManager* manager = GetOrCreateBrowserAccessibilityManager(); if (manager) { @@ -3922,8 +4101,14 @@ void RenderFrameHostImpl::HandleAccessibilityFindInPageResult( } void RenderFrameHostImpl::HandleAccessibilityFindInPageTermination() { + // Only update FindInPageTermination on active RenderFrameHost. Note that, it + // is safe to ignore this call for BackForwardCache, as we terminate the + // FindInPage session once the page enters BackForwardCache. + if (lifecycle_state_ != LifecycleState::kActive) + return; + ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); - if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs) && is_active()) { + if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) { BrowserAccessibilityManager* manager = GetOrCreateBrowserAccessibilityManager(); if (manager) @@ -3941,10 +4126,11 @@ void RenderFrameHostImpl::DocumentOnLoadCompleted() { void RenderFrameHostImpl::ForwardResourceTimingToParent( blink::mojom::ResourceTimingInfoPtr timing) { - // Don't forward the resource timing if this RFH is pending deletion. This can - // happen in a race where this RenderFrameHost finishes loading just after - // the frame navigates away. See https://crbug.com/626802. - if (!is_active()) + // Don't forward the resource timing of the parent on behalf of inactive + // RenderFrameHost. This can happen in a race where this RenderFrameHost + // finishes loading just after the frame navigates away. See + // https://crbug.com/626802. + if (IsInactiveAndDisallowReactivation()) return; // We should never be receiving this message from a speculative RFH. @@ -3995,6 +4181,28 @@ void RenderFrameHostImpl::SendAccessibilityEventsToManager( } } +bool RenderFrameHostImpl::IsInactiveAndDisallowReactivation() { + switch (lifecycle_state_) { + case LifecycleState::kRunningUnloadHandlers: + case LifecycleState::kReadyToBeDeleted: + return true; + case LifecycleState::kInBackForwardCache: + EvictFromBackForwardCacheWithReason( + BackForwardCacheMetrics::NotRestoredReason::kIgnoreEventAndEvict); + return true; + case LifecycleState::kSpeculative: + // TODO(sreejakshetty): Upgrade NOTREACHED to a CHECK(false) after + // monitoring the DumpWithoutCrashing reports and ensuring this doesn't + // happen in practice. + base::debug::DumpWithoutCrashing(); + NOTREACHED() << "We should not try to ignore events for a speculative " + "RenderFrameHost\n"; + return false; + case LifecycleState::kActive: + return false; + } +} + void RenderFrameHostImpl::EvictFromBackForwardCache() { // TODO(hajimehoshi): This function should take the reason from the renderer // side. @@ -4042,8 +4250,7 @@ void RenderFrameHostImpl::EvictFromBackForwardCacheWithReasons( // A document is evicted from the BackForwardCache, but it has already been // restored. The current document should be reloaded, because it is not // salvageable. - frame_tree_node_->navigator()->GetController()->Reload(ReloadType::NORMAL, - false); + frame_tree()->controller()->Reload(ReloadType::NORMAL, false); return; } @@ -4069,14 +4276,14 @@ void RenderFrameHostImpl::EvictFromBackForwardCacheWithReasons( in_flight_navigation_request->RestartBackForwardCachedNavigation(); } - NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( - frame_tree_node_->navigator()->GetController()); - // Evict the frame and schedule it to be destroyed. Eviction happens // immediately, but destruction is delayed, so that callers don't have to // worry about use-after-free of |this|. top_document->is_evicted_from_back_forward_cache_ = true; - controller->GetBackForwardCache().PostTaskToDestroyEvictedFrames(); + frame_tree() + ->controller() + ->GetBackForwardCache() + .PostTaskToDestroyEvictedFrames(); } bool RenderFrameHostImpl::HasSeenRecentXrOverlaySetup() { @@ -4156,8 +4363,7 @@ void RenderFrameHostImpl::EnterFullscreen( notified_instances.insert(parent_site_instance); } - // TODO(alexmos): See if this can use the last committed origin instead. - delegate_->EnterFullscreenMode(GetLastCommittedURL().GetOrigin(), *options); + delegate_->EnterFullscreenMode(this, *options); delegate_->FullscreenStateChanged(this, true /* is_fullscreen */); // The previous call might change the fullscreen state. We need to make sure @@ -4244,7 +4450,7 @@ void RenderFrameHostImpl::OnDidStopLoading() { // Only inform the FrameTreeNode of a change in load state if the load state // of this RenderFrameHost is being tracked. - if (is_active()) + if (!IsPendingDeletion()) frame_tree_node_->DidStopLoading(); } @@ -4275,8 +4481,12 @@ void RenderFrameHostImpl::DidReceiveFirstUserActivation() { void RenderFrameHostImpl::UpdateUserActivationState( blink::mojom::UserActivationUpdateType update_type) { - if (!is_active()) + // Don't update UserActivationState for non-active RenderFrameHost. In case + // of BackForwardCache, this is only called for tests and it is safe to ignore + // such requests. + if (lifecycle_state_ != LifecycleState::kActive) return; + frame_tree_node_->UpdateUserActivationState(update_type); } @@ -4298,7 +4508,8 @@ void RenderFrameHostImpl::ScrollRectToVisibleInParentFrame( void RenderFrameHostImpl::BubbleLogicalScrollInParentFrame( blink::mojom::ScrollDirection direction, ui::ScrollGranularity granularity) { - if (!is_active()) + // Do not update the parent on behalf of inactive RenderFrameHost. + if (IsInactiveAndDisallowReactivation()) return; RenderFrameProxyHost* proxy = @@ -4315,10 +4526,6 @@ void RenderFrameHostImpl::BubbleLogicalScrollInParentFrame( granularity); } -void RenderFrameHostImpl::OnFrameDidCallFocus() { - delegate_->DidCallFocus(); -} - void RenderFrameHostImpl::RenderFallbackContentInParentProcess() { bool is_object_type = frame_tree_node()->current_replication_state().frame_owner_element_type == @@ -4410,6 +4617,36 @@ void RenderFrameHostImpl::DidChangeFrameOwnerProperties( } } +void RenderFrameHostImpl::DidChangeOpener( + const base::Optional<base::UnguessableToken>& opener_frame_token) { + frame_tree_node_->render_manager()->DidChangeOpener( + opener_frame_token.value_or(base::UnguessableToken()), GetSiteInstance()); +} + +void RenderFrameHostImpl::DidChangeFramePolicy( + const base::UnguessableToken& child_frame_token, + const blink::FramePolicy& frame_policy) { + // Ensure that a frame can only update sandbox flags or feature policy for its + // immediate children. If this is not the case, the renderer is considered + // malicious and is killed. + RenderFrameHostImpl* child = FindAndVerifyChild( + // TODO(iclelland): Rename this message + child_frame_token, bad_message::RFH_SANDBOX_FLAGS); + if (!child) + return; + + child->frame_tree_node()->SetPendingFramePolicy(frame_policy); + + // Notify the RenderFrame if it lives in a different process from its parent. + // The frame's proxies in other processes also need to learn about the updated + // flags and policy, but these notifications are sent later in + // RenderFrameHostManager::CommitPendingFramePolicy(), when the frame + // navigates and the new policies take effect. + if (child->GetSiteInstance() != GetSiteInstance()) { + child->GetAssociatedLocalFrame()->DidUpdateFramePolicy(frame_policy); + } +} + void RenderFrameHostImpl::BindInterfaceProviderReceiver( mojo::PendingReceiver<service_manager::mojom::InterfaceProvider> interface_provider_receiver) { @@ -4457,6 +4694,54 @@ void RenderFrameHostImpl::RequestOverlayRoutingToken( std::move(callback).Run(frame_token_); } +void RenderFrameHostImpl::UpdateState(const PageState& state) { + // TODO(creis): Verify the state's ISN matches the last committed FNE. + + // Without this check, the renderer can trick the browser into using + // filenames it can't access in a future session restore. + if (!CanAccessFilesOfPageState(state)) { + bad_message::ReceivedBadMessage( + GetProcess(), bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE); + return; + } + + delegate_->UpdateStateForFrame(this, state); +} + +void RenderFrameHostImpl::OpenURL(mojom::OpenURLParamsPtr params) { + // Verify and unpack the Mojo payload. + GURL validated_url; + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; + if (!VerifyOpenURLParams(GetSiteInstance(), params, &validated_url, + &blob_url_loader_factory)) { + return; + } + + TRACE_EVENT1("navigation", "RenderFrameHostImpl::OpenURL", "url", + validated_url.possibly_invalid_spec()); + + frame_tree_node_->navigator().RequestOpenURL( + this, validated_url, + GlobalFrameRoutingId(GetProcess()->GetID(), params->initiator_routing_id), + params->initiator_origin, params->post_body, params->extra_headers, + params->referrer.To<content::Referrer>(), params->disposition, + params->should_replace_current_entry, params->user_gesture, + params->triggering_event_info, params->href_translate, + std::move(blob_url_loader_factory), params->impression); +} + +void RenderFrameHostImpl::GetSavableResourceLinksCallback( + blink::mojom::GetSavableResourceLinksReplyPtr reply) { + if (!reply) { + delegate_->SavableResourceLinksError(this); + return; + } + + delegate_->SavableResourceLinksResponse(this, reply->resources_list, + std::move(reply->referrer), + reply->subframes); +} + void RenderFrameHostImpl::DomOperationResponse(const std::string& json_string) { delegate_->DomOperationResponse(json_string); } @@ -4483,7 +4768,8 @@ RenderFrameHostImpl::CreateCrossOriginPrefetchLoaderFactoryBundle() { CreateURLLoaderFactoriesForIsolatedWorlds( GetExpectedMainWorldOriginForUrlLoaderFactory(), isolated_worlds_requiring_separate_url_loader_factory_, - mojo::Clone(last_committed_client_security_state_)), + mojo::Clone(last_committed_client_security_state_), + DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this)), bypass_redirect_checks); } @@ -4644,10 +4930,20 @@ void RenderFrameHostImpl::CreateNewWindow( // Checking sandbox flags of the new frame should be safe at this point, // because the flags should be already inherited by the CreateNewWindow call // above. - main_frame->SetOriginAndIsolationInfoOfNewFrame(GetLastCommittedOrigin()); + main_frame->SetOriginDependentStateOfNewFrame(GetLastCommittedOrigin()); main_frame->cross_origin_opener_policy_ = popup_coop; main_frame->cross_origin_embedder_policy_ = popup_coep; + // If inheriting coop (checking this via |opener_suppressed|) and the original + // coop page has a reporter we make sure the the newly created popup also has + // a reporter. + if (!params->opener_suppressed && GetMainFrame()->coop_reporter()) { + main_frame->set_coop_reporter( + std::make_unique<CrossOriginOpenerPolicyReporter>( + GetProcess()->GetStoragePartition(), this, GetLastCommittedURL(), + popup_coop, popup_coep)); + } + if (main_frame->waiting_for_init_) { // Need to check |waiting_for_init_| as some paths inside CreateNewWindow // call above (eg if WebContentsDelegate::IsWebContentsCreationOverridden() @@ -4685,6 +4981,14 @@ void RenderFrameHostImpl::CreateNewWindow( blink_widget_host_receiver = blink_widget_host.InitWithNewEndpointAndPassReceiver(); + // With this path, RenderViewHostImpl::CreateRenderView is never called + // because RenderView is already created on the renderer side. Thus we need to + // establish the connection here. + mojo::PendingAssociatedRemote<blink::mojom::PageBroadcast> page_broadcast; + mojo::PendingAssociatedReceiver<blink::mojom::PageBroadcast> + page_broadcast_receiver = + page_broadcast.InitWithNewEndpointAndPassReceiver(); + // TODO(danakj): The main frame's RenderWidgetHost has no RenderWidgetHostView // yet here. It seems like it should though? In the meantime we send some // nonsense with a semi-valid but incorrect ScreenInfo (it needs a @@ -4698,16 +5002,17 @@ void RenderFrameHostImpl::CreateNewWindow( std::move(blink_frame_widget)); main_frame->GetLocalRenderWidgetHost()->BindWidgetInterfaces( std::move(blink_widget_host_receiver), std::move(blink_widget)); + main_frame->render_view_host()->BindPageBroadcast(std::move(page_broadcast)); bool wait_for_debugger = devtools_instrumentation::ShouldWaitForDebuggerInWindowOpen(); mojom::CreateNewWindowReplyPtr reply = mojom::CreateNewWindowReply::New( main_frame->GetRenderViewHost()->GetRoutingID(), - main_frame->GetRoutingID(), main_frame->frame_token(), + main_frame->GetRoutingID(), main_frame->GetFrameToken(), main_frame->GetLocalRenderWidgetHost()->GetRoutingID(), visual_properties, std::move(blink_frame_widget_host), std::move(blink_frame_widget_receiver), std::move(blink_widget_host), - std::move(blink_widget_receiver), + std::move(blink_widget_receiver), std::move(page_broadcast_receiver), mojom::DocumentScopedInterfaceBundle::New( std::move(main_frame_interface_provider_info), std::move(browser_interface_broker)), @@ -4738,6 +5043,15 @@ void RenderFrameHostImpl::CreatePortal( return; } + // TODO(crbug.com/1051639): We need to find a long term solution to when/how + // portals should work in sandboxed documents. + if (active_sandbox_flags_ != network::mojom::WebSandboxFlags::kNone) { + mojo::ReportBadMessage( + "RFHI::CreatePortal called in a sandboxed browsing context"); + frame_host_associated_receiver_.reset(); + return; + } + // Note that we don't check |GetLastCommittedOrigin|, since that is inherited // by the initial empty document of a new frame. // TODO(1008989): Once issue 1008989 is fixed we could move this check into @@ -4787,27 +5101,25 @@ void RenderFrameHostImpl::AdoptPortal( } void RenderFrameHostImpl::CreateNewWidget( - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget, CreateNewWidgetCallback callback) { int32_t widget_route_id = GetProcess()->GetNextRoutingID(); std::move(callback).Run(widget_route_id); delegate_->CreateNewWidget(GetProcess()->GetID(), widget_route_id, - std::move(widget), std::move(blink_widget_host), + std::move(blink_widget_host), std::move(blink_widget)); } void RenderFrameHostImpl::CreateNewFullscreenWidget( - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget, CreateNewFullscreenWidgetCallback callback) { int32_t widget_route_id = GetProcess()->GetNextRoutingID(); std::move(callback).Run(widget_route_id); - delegate_->CreateNewFullscreenWidget( - GetProcess()->GetID(), widget_route_id, std::move(widget), - std::move(blink_widget_host), std::move(blink_widget)); + delegate_->CreateNewFullscreenWidget(GetProcess()->GetID(), widget_route_id, + std::move(blink_widget_host), + std::move(blink_widget)); } void RenderFrameHostImpl::IssueKeepAliveHandle( @@ -4842,7 +5154,7 @@ void RenderFrameHostImpl::BeginNavigation( return; } - if (!is_active()) + if (IsInactiveAndDisallowReactivation()) return; TRACE_EVENT2("navigation", "RenderFrameHostImpl::BeginNavigation", @@ -4913,7 +5225,7 @@ void RenderFrameHostImpl::BeginNavigation( return; } - frame_tree_node()->navigator()->OnBeginNavigation( + frame_tree_node()->navigator().OnBeginNavigation( frame_tree_node(), std::move(validated_params), std::move(begin_params), std::move(blob_url_loader_factory), std::move(navigation_client), std::move(navigation_initiator), EnsurePrefetchedSignedExchangeCache(), @@ -4961,7 +5273,8 @@ void RenderFrameHostImpl::HandleAXEvents( RenderWidgetHostViewBase* view = GetViewForAccessibility(); ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); - if (accessibility_mode.is_mode_off() || !view || !is_active()) { + if (accessibility_mode.is_mode_off() || !view || + IsInactiveAndDisallowReactivation()) { std::move(callback).Run(); return; } @@ -4985,6 +5298,7 @@ void RenderFrameHostImpl::HandleAXEvents( dst_update->root_id = src_update.root_id; dst_update->node_id_to_clear = src_update.node_id_to_clear; dst_update->event_from = src_update.event_from; + dst_update->event_intents = src_update.event_intents; dst_update->nodes.resize(src_update.nodes.size()); for (size_t j = 0; j < src_update.nodes.size(); ++j) { AXContentNodeDataToAXNodeData(src_update.nodes[j], &dst_update->nodes[j]); @@ -5019,7 +5333,7 @@ void RenderFrameHostImpl::HandleAXEvents( void RenderFrameHostImpl::HandleAXLocationChanges( std::vector<mojom::LocationChangesPtr> changes) { - if (accessibility_reset_token_ || !is_active()) + if (accessibility_reset_token_ || IsInactiveAndDisallowReactivation()) return; RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( @@ -5053,7 +5367,11 @@ RenderFrameHostImpl::GetRecordAggregateWatchTimeCallback() { } void RenderFrameHostImpl::ResetWaitingState() { - DCHECK(is_active()); + // We don't allow resetting waiting state when the RenderFrameHost is either + // in BackForwardCache or in pending deletion state, as we don't allow + // navigations from either of these two states. + DCHECK(!IsInBackForwardCache()); + DCHECK(!IsPendingDeletion()); // Whenever we reset the RFH state, we should not be waiting for beforeunload // or close acks. We clear them here to be safe, since they can cause @@ -5161,32 +5479,6 @@ CanCommitStatus RenderFrameHostImpl::CanCommitOriginAndUrl( return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL; } -void RenderFrameHostImpl::NavigateToInterstitialURL(const GURL& data_url) { - TRACE_EVENT1("navigation", "RenderFrameHostImpl::NavigateToInterstitialURL", - "frame_tree_node", frame_tree_node_->frame_tree_node_id()); - DCHECK(data_url.SchemeIs(url::kDataScheme)); - NavigationDownloadPolicy download_policy; - download_policy.SetDisallowed(NavigationDownloadType::kInterstitial); - - auto common_params = mojom::CommonNavigationParams::New( - data_url, base::nullopt, blink::mojom::Referrer::New(), - ui::PAGE_TRANSITION_LINK, mojom::NavigationType::DIFFERENT_DOCUMENT, - download_policy, false, GURL(), GURL(), PREVIEWS_OFF, - base::TimeTicks::Now(), "GET", nullptr, - network::mojom::SourceLocation::New(), - false /* started_from_context_menu */, false /* has_user_gesture */, - CreateInitiatorCSPInfo(), std::vector<int>(), std::string(), - false /* is_history_navigation_in_new_child_frame */, base::TimeTicks()); - CommitNavigation(nullptr /* navigation_request */, std::move(common_params), - CreateCommitNavigationParams(), nullptr /* response_head */, - mojo::ScopedDataPipeConsumerHandle(), - network::mojom::URLLoaderClientEndpointsPtr(), false, - base::nullopt, base::nullopt /* subresource_overrides */, - nullptr /* provider_info */, - base::UnguessableToken::Create() /* not traced */, - nullptr /* web_bundle_factory */); -} - void RenderFrameHostImpl::Stop() { TRACE_EVENT1("navigation", "RenderFrameHostImpl::Stop", "frame_tree_node", frame_tree_node_->frame_tree_node_id()); @@ -5293,8 +5585,7 @@ bool RenderFrameHostImpl::CheckOrDispatchBeforeUnloadForSubtree( bool send_ipc, bool is_reload) { bool found_beforeunload = false; - for (FrameTreeNode* node : - frame_tree_node_->frame_tree()->SubtreeNodes(frame_tree_node_)) { + for (FrameTreeNode* node : frame_tree()->SubtreeNodes(frame_tree_node_)) { RenderFrameHostImpl* rfh = node->current_frame_host(); // If |subframes_only| is true, skip this frame and its same-site @@ -5398,8 +5689,7 @@ void RenderFrameHostImpl::StartPendingDeletionOnSubtree() { DCHECK(IsPendingDeletion()); for (std::unique_ptr<FrameTreeNode>& child_frame : children_) { - for (FrameTreeNode* node : - frame_tree_node_->frame_tree()->SubtreeNodes(child_frame.get())) { + for (FrameTreeNode* node : frame_tree()->SubtreeNodes(child_frame.get())) { RenderFrameHostImpl* child = node->current_frame_host(); if (child->IsPendingDeletion()) continue; @@ -5477,9 +5767,10 @@ void RenderFrameHostImpl::UpdateOpener() { GetSiteInstance(), frame_tree_node_); } - int opener_routing_id = - frame_tree_node_->render_manager()->GetOpenerRoutingID(GetSiteInstance()); - Send(new FrameMsg_UpdateOpener(GetRoutingID(), opener_routing_id)); + auto opener_frame_token = + frame_tree_node_->render_manager()->GetOpenerFrameToken( + GetSiteInstance()); + GetAssociatedLocalFrame()->UpdateOpener(opener_frame_token); } void RenderFrameHostImpl::SetFocusedFrame() { @@ -5526,24 +5817,23 @@ void RenderFrameHostImpl::CommitNavigation( base::Optional<SubresourceLoaderParams> subresource_loader_params, base::Optional<std::vector<mojom::TransferrableURLLoaderPtr>> subresource_overrides, - blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info, + blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info, const base::UnguessableToken& devtools_navigation_token, std::unique_ptr<WebBundleHandle> web_bundle_handle) { - web_bundle_handle_ = std::move(web_bundle_handle); - TRACE_EVENT2("navigation", "RenderFrameHostImpl::CommitNavigation", "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", common_params->url.possibly_invalid_spec()); DCHECK(!IsRendererDebugURL(common_params->url)); + DCHECK(navigation_request); - bool is_mhtml_iframe = - navigation_request && navigation_request->IsForMhtmlSubframe(); + bool is_same_document = + NavigationTypeUtils::IsSameDocument(common_params->navigation_type); + bool is_mhtml_iframe = navigation_request->IsForMhtmlSubframe(); // A |response| and a |url_loader_client_endpoints| must always be provided, // except for edge cases, where another way to load the document exist. DCHECK((response_head && url_loader_client_endpoints) || - common_params->url.SchemeIs(url::kDataScheme) || - NavigationTypeUtils::IsSameDocument(common_params->navigation_type) || + common_params->url.SchemeIs(url::kDataScheme) || is_same_document || !IsURLHandledByNetworkStack(common_params->url) || is_mhtml_iframe); // All children of MHTML documents must be MHTML documents. @@ -5575,6 +5865,8 @@ void RenderFrameHostImpl::CommitNavigation( CHECK_EQ(GetSiteInstance(), parent_->GetSiteInstance()); } + // TODO(https://crbug.com/888079): Compute the Origin to commit here. + // If this is an attempt to commit a URL in an incompatible process, capture a // crash dump to diagnose why it is occurring. // TODO(creis): Remove this check after we've gathered enough information to @@ -5606,6 +5898,13 @@ void RenderFrameHostImpl::CommitNavigation( const bool is_first_navigation = !has_committed_any_navigation_; has_committed_any_navigation_ = true; + // If this is NOT for same-document navigation, existing |web_bundle_handle_| + // should be reset to the new one. Otherwise the existing one should be kept + // around so that the subresource requests keep being served from the + // WebBundleURLLoaderFactory held by the handle. + if (!is_same_document) + web_bundle_handle_ = std::move(web_bundle_handle); + UpdatePermissionsForNavigation(*common_params, *commit_params); // Get back to a clean state, in case we start a new navigation without @@ -5625,8 +5924,6 @@ void RenderFrameHostImpl::CommitNavigation( network::mojom::URLResponseHeadPtr head = response_head ? std::move(response_head) : network::mojom::URLResponseHead::New(); - const bool is_same_document = - NavigationTypeUtils::IsSameDocument(common_params->navigation_type); // TODO(crbug.com/979296): Consider changing this code to copy an origin // instead of creating one from a URL which lacks opacity information. @@ -5644,7 +5941,7 @@ void RenderFrameHostImpl::CommitNavigation( } DCHECK(!isolation_info_.IsEmpty()); - if (navigation_request && navigation_request->appcache_handle()) { + if (navigation_request->appcache_handle()) { // AppCache may create a subresource URLLoaderFactory later, so make sure it // has the correct origin to use when calling // ContentBrowserClient::WillCreateURLLoaderFactory(). @@ -5743,21 +6040,23 @@ void RenderFrameHostImpl::CommitNavigation( // NavigationRequest in this function. recreate_default_url_loader_factory_after_network_service_crash_ = true; CrossOriginEmbedderPolicyReporter* const coep_reporter = - navigation_request ? navigation_request->coep_reporter() : nullptr; + navigation_request->coep_reporter(); mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> coep_reporter_remote; if (coep_reporter) { coep_reporter->Clone( coep_reporter_remote.InitWithNewPipeAndPassReceiver()); } + bool bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve( CreateURLLoaderFactoryParamsForMainWorld( main_world_origin_for_url_loader_factory, - navigation_request - ? mojo::Clone(navigation_request->client_security_state()) - : network::mojom::ClientSecurityState::New(), - std::move(coep_reporter_remote)), + mojo::Clone(navigation_request->client_security_state()), + std::move(coep_reporter_remote), + DetermineWhetherToForbidTrustTokenRedemption( + GetParent(), *commit_params, + main_world_origin_for_url_loader_factory)), pending_default_factory.InitWithNewPipeAndPassReceiver()); subresource_loader_factories->set_bypass_redirect_checks( bypass_redirect_checks); @@ -5827,19 +6126,15 @@ void RenderFrameHostImpl::CommitNavigation( StoragePartition* partition = BrowserContext::GetStoragePartition(browser_context, GetSiteInstance()); - std::string storage_domain; - if (site_instance_) { - std::string partition_name; - bool in_memory; - GetContentClient()->browser()->GetStoragePartitionConfigForSite( - browser_context, site_instance_->GetSiteURL(), true, &storage_domain, - &partition_name, &in_memory); - } + auto storage_partition_config = + GetContentClient()->browser()->GetStoragePartitionConfigForSite( + browser_context, site_instance_->GetSiteInfo().site_url()); non_network_url_loader_factories_.emplace( url::kFileSystemScheme, content::CreateFileSystemURLLoaderFactory( process_->GetID(), GetFrameTreeNodeId(), - partition->GetFileSystemContext(), storage_domain)); + partition->GetFileSystemContext(), + storage_partition_config.partition_domain())); non_network_url_loader_factories_.emplace( url::kDataScheme, std::make_unique<DataURLLoaderFactory>()); @@ -5877,9 +6172,10 @@ void RenderFrameHostImpl::CommitNavigation( CreateURLLoaderFactoriesForIsolatedWorlds( main_world_origin_for_url_loader_factory, isolated_worlds_requiring_separate_url_loader_factory_, - navigation_request - ? mojo::Clone(navigation_request->client_security_state()) - : network::mojom::ClientSecurityState::New()); + mojo::Clone(navigation_request->client_security_state()), + DetermineWhetherToForbidTrustTokenRedemption( + GetParent(), *commit_params, + main_world_origin_for_url_loader_factory)); } // It is imperative that cross-document navigations always provide a set of @@ -5949,9 +6245,8 @@ void RenderFrameHostImpl::CommitNavigation( EnsurePrefetchedSignedExchangeCache()); } - mojom::NavigationClient* navigation_client = nullptr; - if (navigation_request) - navigation_client = navigation_request->GetCommitNavigationClient(); + mojom::NavigationClient* navigation_client = + navigation_request->GetCommitNavigationClient(); // Record the metrics about the state of the old main frame at the moment // when we navigate away from it as it matters for whether the page @@ -5971,10 +6266,7 @@ void RenderFrameHostImpl::CommitNavigation( if (!GetParent() && frame_tree_node()->current_frame_host() == this) { if (NavigationEntryImpl* last_committed_entry = NavigationEntryImpl::FromNavigationEntry( - frame_tree_node() - ->navigator() - ->GetController() - ->GetLastCommittedEntry())) { + frame_tree()->controller()->GetLastCommittedEntry())) { if (last_committed_entry->back_forward_cache_metrics()) { last_committed_entry->back_forward_cache_metrics() ->RecordFeatureUsage(this); @@ -6011,7 +6303,7 @@ void RenderFrameHostImpl::CommitNavigation( std::move(url_loader_client_endpoints), std::move(subresource_loader_factories), std::move(subresource_overrides), std::move(controller), - std::move(provider_info), std::move(prefetch_loader_factory), + std::move(container_info), std::move(prefetch_loader_factory), devtools_navigation_token); // |remote_object| is an associated interface ptr, so calls can't be made on @@ -6053,9 +6345,20 @@ void RenderFrameHostImpl::FailedNavigation( // Error page will commit in an opaque origin. // + // The precursor of the opaque origin can be set arbitrarily, because: + // 1) we expect that the error page will not issue network requests + // 2) network::VerifyRequestInitiatorLock doesn't compare precursors + // This observation lets us improve debuggability by using a hardcoded + // precursor below. + // TODO(lukasza): https://crbug.com/1056949: Stop using error.page.invalid as + // the precursor (once https://crbug.com/1056949 is debugged OR once + // network::VerifyRequestInitiatorLock starts to compare precursors). + // // TODO(lukasza): https://crbug.com/888079: Use this origin when committing // later on. - url::Origin origin = url::Origin(); + url::Origin error_page_origin = + url::Origin::Create(GURL("https://error.page.invalid")) + .DeriveNewOpaqueOrigin(); isolation_info_ = net::IsolationInfo::CreateTransient(); std::unique_ptr<blink::PendingURLLoaderFactoryBundle> @@ -6063,8 +6366,10 @@ void RenderFrameHostImpl::FailedNavigation( mojo::PendingRemote<network::mojom::URLLoaderFactory> default_factory_remote; bool bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve( CreateURLLoaderFactoryParamsForMainWorld( - origin, mojo::Clone(navigation_request->client_security_state()), - /*coep_reporter=*/mojo::NullRemote()), + error_page_origin, + mojo::Clone(navigation_request->client_security_state()), + /*coep_reporter=*/mojo::NullRemote(), + network::mojom::TrustTokenRedemptionPolicy::kForbid), default_factory_remote.InitWithNewPipeAndPassReceiver()); subresource_loader_factories = std::make_unique<blink::PendingURLLoaderFactoryBundle>( @@ -6096,7 +6401,7 @@ void RenderFrameHostImpl::HandleRendererDebugURL(const GURL& url) { // the renderer process is done handling the URL. // TODO(clamy): Remove the test dependency on this behavior. if (!url.SchemeIs(url::kJavaScriptScheme)) { - bool was_loading = frame_tree_node()->frame_tree()->IsLoading(); + bool was_loading = frame_tree()->IsLoading(); is_loading_ = true; frame_tree_node()->DidStartLoading(true, was_loading); } @@ -6203,9 +6508,6 @@ void RenderFrameHostImpl::SetUpMojoIfNeeded() { remote_interfaces.InitWithNewPipeAndPassReceiver()); remote_interfaces_.reset(new service_manager::InterfaceProvider); remote_interfaces_->Bind(std::move(remote_interfaces)); - - remote_interfaces_->GetInterface( - frame_input_handler_.BindNewPipeAndPassReceiver()); } void RenderFrameHostImpl::InvalidateMojoConnection() { @@ -6215,7 +6517,6 @@ void RenderFrameHostImpl::InvalidateMojoConnection() { local_frame_.reset(); local_main_frame_.reset(); navigation_control_.reset(); - frame_input_handler_.reset(); find_in_page_.reset(); render_accessibility_.reset(); @@ -6246,7 +6547,7 @@ bool RenderFrameHostImpl::IsFocused() { bool RenderFrameHostImpl::CreateWebUI(const GURL& dest_url, int entry_bindings) { // Verify expectation that WebUI should not be created for error pages. - DCHECK_NE(GetSiteInstance()->GetSiteURL(), GURL(kUnreachableWebDataURL)); + DCHECK_NE(GetSiteInstance()->GetSiteInfo(), SiteInfo::CreateForErrorPage()); WebUI::TypeID new_web_ui_type = WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType( @@ -6337,7 +6638,7 @@ void RenderFrameHostImpl::ResetLoadingState() { // When pending deletion, just set the loading state to not loading. // Otherwise, OnDidStopLoading will take care of that, as well as sending // notification to the FrameTreeNode about the change in loading state. - if (!is_active()) + if (IsPendingDeletion() || IsInBackForwardCache()) is_loading_ = false; else OnDidStopLoading(); @@ -6424,6 +6725,15 @@ void RenderFrameHostImpl::RequestAXTreeSnapshot(AXTreeSnapshotCallback callback, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } +void RenderFrameHostImpl::GetSavableResourceLinksFromRenderer() { + if (!IsRenderFrameLive()) + return; + + GetAssociatedLocalFrame()->GetSavableResourceLinks( + base::BindOnce(&RenderFrameHostImpl::GetSavableResourceLinksCallback, + weak_ptr_factory_.GetWeakPtr())); +} + void RenderFrameHostImpl::SetAccessibilityCallbackForTesting( const AccessibilityCallbackForTesting& callback) { accessibility_testing_callback_ = callback; @@ -6431,7 +6741,7 @@ void RenderFrameHostImpl::SetAccessibilityCallbackForTesting( void RenderFrameHostImpl::UpdateAXTreeData() { ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); - if (accessibility_mode.is_mode_off() || !is_active()) { + if (accessibility_mode.is_mode_off() || IsInactiveAndDisallowReactivation()) { return; } @@ -6517,7 +6827,7 @@ bool RenderFrameHostImpl::HasSelection() { RenderFrameHostImpl* RenderFrameHostImpl::GetMainFrame() { // Iteration over the GetParent() chain is used below, because returning - // |frame_tree_node()->frame_tree()->root()->current_frame_host()| might + // |frame_tree()->root()->current_frame_host()| might // give an incorrect result after |this| has been detached from the frame // tree. RenderFrameHostImpl* main_frame = this; @@ -6628,10 +6938,11 @@ RenderFrameHostImpl::CreateURLLoaderFactoryParamsForMainWorld( const url::Origin& main_world_origin, network::mojom::ClientSecurityStatePtr client_security_state, mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> - coep_reporter) { + coep_reporter, + network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy) { return URLLoaderFactoryParamsHelper::CreateForFrame( this, main_world_origin, std::move(client_security_state), - std::move(coep_reporter), GetProcess()); + std::move(coep_reporter), GetProcess(), trust_token_redemption_policy); } bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryAndObserve( @@ -6788,34 +7099,46 @@ void RenderFrameHostImpl::AXContentTreeDataToAXTreeData(ui::AXTreeData* dst) { dst->focused_tree_id = focused_frame->GetAXTreeID(); } -void RenderFrameHostImpl::RequestAXHitTestCallback( - int action_request_id, - mojom::ChildFrameHitTestInfoPtr child_frame_hit_test_info) { - // Not receiving a child_frame_hit_test_info means that the renderer has - // already handled this by emitting the requested event over the object found - // as the result of the hit testing process, so nothing to do. - if (!child_frame_hit_test_info) +void RenderFrameHostImpl::AccessibilityHitTestCallback( + int request_id, + ax::mojom::Event event_to_fire, + base::OnceCallback<void(BrowserAccessibilityManager* hit_manager, + int hit_node_id)> opt_callback, + mojom::HitTestResponsePtr hit_test_response) { + if (!hit_test_response) { + if (opt_callback) + std::move(opt_callback).Run(nullptr, 0); return; + } - // A child frame was found while hit testing on this frame, and so we need to - // request a new hit test over such child frame now. auto frame_or_proxy = LookupRenderFrameHostOrProxy( - GetProcess()->GetID(), child_frame_hit_test_info->child_frame_routing_id); - RenderFrameHostImpl* child_frame = + GetProcess()->GetID(), hit_test_response->hit_frame_token); + RenderFrameHostImpl* hit_frame = frame_or_proxy.proxy ? frame_or_proxy.proxy->frame_tree_node()->current_frame_host() : frame_or_proxy.frame; - if (!child_frame || !child_frame->is_active()) + if (!hit_frame || hit_frame->IsInactiveAndDisallowReactivation()) { + if (opt_callback) + std::move(opt_callback).Run(nullptr, 0); return; + } - ui::AXActionData action_data; - action_data.request_id = action_request_id; - action_data.target_point = child_frame_hit_test_info->transformed_point; - action_data.action = ax::mojom::Action::kHitTest; - action_data.hit_test_event_to_fire = child_frame_hit_test_info->event_to_fire; + // If the hit node's routing ID is the same frame, we're done. If a + // callback was provided, call it with the information about the hit node. + if (hit_frame->GetFrameToken() == frame_token_) { + if (opt_callback) { + std::move(opt_callback) + .Run(hit_frame->browser_accessibility_manager(), + hit_test_response->hit_node_id); + } + return; + } - child_frame->AccessibilityPerformAction(action_data); + // The hit node has a child frame. Do a hit test in that frame's renderer. + hit_frame->AccessibilityHitTest( + hit_test_response->hit_frame_transformed_point, event_to_fire, request_id, + std::move(opt_callback)); } void RenderFrameHostImpl::RequestAXTreeSnapshotCallback( @@ -6843,6 +7166,12 @@ void RenderFrameHostImpl::CreatePaymentManager( } GetProcess()->CreatePaymentManagerForOrigin(GetLastCommittedOrigin(), std::move(receiver)); + + // Blocklist PaymentManager from the back-forward cache as at the moment we + // don't cancel pending payment requests when the RenderFrameHost is stored + // in back-forward cache. + OnSchedulerTrackedFeatureUsed( + blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager); } void RenderFrameHostImpl::CreateWebBluetoothService( @@ -6944,12 +7273,10 @@ void RenderFrameHostImpl::BindScreenEnumerationReceiver( void RenderFrameHostImpl::BindMediaInterfaceFactoryReceiver( mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) { - DCHECK(!media_interface_proxy_); - media_interface_proxy_.reset(new MediaInterfaceProxy( - this, std::move(receiver), - base::BindOnce( - &RenderFrameHostImpl::OnMediaInterfaceFactoryConnectionError, - base::Unretained(this)))); + if (!media_interface_proxy_) { + media_interface_proxy_ = std::make_unique<MediaInterfaceProxy>(this); + } + media_interface_proxy_->Bind(std::move(receiver)); } void RenderFrameHostImpl::BindMediaMetricsProviderReceiver( @@ -7054,17 +7381,14 @@ void RenderFrameHostImpl::CreateDedicatedWorkerHostFactory( // When a dedicated worker is created from the frame script, the frame is both // the creator and the ancestor. - content::CreateDedicatedWorkerHostFactory( - worker_process_id, - /*creator_render_frame_host_id=*/GetGlobalFrameRoutingId(), - /*ancestor_render_frame_host_id=*/GetGlobalFrameRoutingId(), - last_committed_origin_, cross_origin_embedder_policy_, - std::move(coep_reporter), std::move(receiver)); -} - -void RenderFrameHostImpl::OnMediaInterfaceFactoryConnectionError() { - DCHECK(media_interface_proxy_); - media_interface_proxy_.reset(); + mojo::MakeSelfOwnedReceiver( + std::make_unique<DedicatedWorkerHostFactoryImpl>( + worker_process_id, + /*creator_render_frame_host_id=*/GetGlobalFrameRoutingId(), + /*ancestor_render_frame_host_id=*/GetGlobalFrameRoutingId(), + last_committed_origin_, cross_origin_embedder_policy_, + std::move(coep_reporter)), + std::move(receiver)); } #if defined(OS_ANDROID) @@ -7112,6 +7436,8 @@ void RenderFrameHostImpl::GetIdleManager( ->GetIdleManager() ->CreateService(std::move(receiver), GetMainFrame()->GetLastCommittedOrigin()); + OnSchedulerTrackedFeatureUsed( + blink::scheduler::WebSchedulerTrackedFeature::kIdleManager); } void RenderFrameHostImpl::GetPresentationService( @@ -7131,14 +7457,15 @@ void RenderFrameHostImpl::GetSpeechSynthesis( mojo::PendingReceiver<blink::mojom::SpeechSynthesis> receiver) { if (!speech_synthesis_impl_) { speech_synthesis_impl_ = std::make_unique<SpeechSynthesisImpl>( - GetProcess()->GetBrowserContext()); + GetProcess()->GetBrowserContext(), delegate_->GetAsWebContents()); } speech_synthesis_impl_->AddReceiver(std::move(receiver)); -} -void RenderFrameHostImpl::GetFileChooser( - mojo::PendingReceiver<blink::mojom::FileChooser> receiver) { - FileChooserImpl::Create(this, std::move(receiver)); + // Blocklist SpeechSynthesis for BackForwardCache, because currently we do not + // handle speech synthesis after placing the page in BackForwardCache. + // TODO(sreejakshetty): Make SpeechSynthesis compatible with BackForwardCache. + OnSchedulerTrackedFeatureUsed( + blink::scheduler::WebSchedulerTrackedFeature::kSpeechSynthesis); } void RenderFrameHostImpl::GetSensorProvider( @@ -7152,13 +7479,6 @@ void RenderFrameHostImpl::GetSensorProvider( sensor_provider_proxy_->Bind(std::move(receiver)); } -mojo::Remote<blink::mojom::FileChooser> -RenderFrameHostImpl::BindFileChooserForTesting() { - mojo::Remote<blink::mojom::FileChooser> chooser; - FileChooserImpl::Create(this, chooser.BindNewPipeAndPassReceiver()); - return chooser; -} - void RenderFrameHostImpl::BindCacheStorage( mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) { mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> @@ -7253,6 +7573,15 @@ void RenderFrameHostImpl::GetInterface( void RenderFrameHostImpl::CreateAppCacheBackend( mojo::PendingReceiver<blink::mojom::AppCacheBackend> receiver) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + static auto* crash_key = base::debug::AllocateCrashKeyString( + "CreateAppCacheBackend-data", base::debug::CrashKeySize::Size64); + std::string data = base::StringPrintf( + "f=%d br=%d irfl=%d iiand=%d fid=%d", frame_.is_bound(), + broker_receiver_.is_bound(), IsRenderFrameLive(), + GetProcess()->IsInitializedAndNotDead(), + RenderProcessHost::FromID(GetProcess()->GetID()) != nullptr); + base::debug::ScopedCrashKeyString scoped_crash_key(crash_key, data); + auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()); storage_partition_impl->GetAppCacheService()->CreateBackend( @@ -7272,8 +7601,8 @@ void RenderFrameHostImpl::GetContactsManager( void RenderFrameHostImpl::GetFileSystemManager( mojo::PendingReceiver<blink::mojom::FileSystemManager> receiver) { // This is safe because file_system_manager_ is deleted on the IO thread - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&FileSystemManagerImpl::BindReceiver, + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&FileSystemManagerImpl::BindReceiver, base::Unretained(file_system_manager_.get()), std::move(receiver))); } @@ -7298,7 +7627,7 @@ void RenderFrameHostImpl::GetNativeFileSystemManager( auto* manager = storage_partition->GetNativeFileSystemManager(); manager->BindReceiver(NativeFileSystemManagerImpl::BindingContext( GetLastCommittedOrigin(), GetLastCommittedURL(), - GetProcess()->GetID(), routing_id_), + GetGlobalFrameRoutingId()), std::move(receiver)); } @@ -7352,7 +7681,9 @@ void RenderFrameHostImpl::GetVirtualAuthenticatorManager( mojo::PendingReceiver<blink::test::mojom::VirtualAuthenticatorManager> receiver) { #if !defined(OS_ANDROID) - if (base::FeatureList::IsEnabled(features::kWebAuth)) { + if (base::FeatureList::IsEnabled(features::kWebAuth) && + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWebAuthDeprecatedMojoTestingApi)) { auto* environment_singleton = AuthenticatorEnvironmentImpl::GetInstance(); environment_singleton->EnableVirtualAuthenticatorFor(frame_tree_node_); environment_singleton->AddVirtualAuthenticatorReceiver(frame_tree_node_, @@ -7374,9 +7705,17 @@ RenderFrameHostImpl::CreateNavigationRequestForCommit( cross_origin_embedder_policy_.reporting_endpoint, cross_origin_embedder_policy_.report_only_reporting_endpoint); } - return NavigationRequest::CreateForCommit(frame_tree_node_, this, params, - std::move(coep_reporter), - is_same_document); + std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info; + if (is_same_document && web_bundle_handle_ && + web_bundle_handle_->navigation_info()) { + // Need to set |web_bundle_navigation_info| of NavigationRequest. This + // will be passed to FrameNavigationEntry, and will be used for subsequent + // history navigations. + web_bundle_navigation_info = web_bundle_handle_->navigation_info()->Clone(); + } + return NavigationRequest::CreateForCommit( + frame_tree_node_, this, params, std::move(coep_reporter), + is_same_document, std::move(web_bundle_navigation_info)); } bool RenderFrameHostImpl::NavigationRequestWasIntendedForPendingEntry( @@ -7384,7 +7723,7 @@ bool RenderFrameHostImpl::NavigationRequestWasIntendedForPendingEntry( const FrameHostMsg_DidCommitProvisionalLoad_Params& params, bool same_document) { NavigationEntryImpl* pending_entry = NavigationEntryImpl::FromNavigationEntry( - frame_tree_node()->navigator()->GetController()->GetPendingEntry()); + frame_tree()->controller()->GetPendingEntry()); if (!pending_entry) return false; if (request->nav_entry_id() != pending_entry->GetUniqueID()) @@ -7409,27 +7748,27 @@ void RenderFrameHostImpl::BeforeUnloadTimeout() { SimulateBeforeUnloadCompleted(true /* proceed */); } -void RenderFrameHostImpl::SetLastCommittedSiteUrl(const GURL& url) { - GURL site_url = url.is_empty() - ? GURL() - : SiteInstanceImpl::GetSiteForURL( - GetSiteInstance()->GetIsolationContext(), url); +void RenderFrameHostImpl::SetLastCommittedSiteInfo(const GURL& url) { + SiteInfo site_info = url.is_empty() + ? SiteInfo() + : SiteInstanceImpl::ComputeSiteInfo( + GetSiteInstance()->GetIsolationContext(), url); - if (last_committed_site_url_ == site_url) + if (last_committed_site_info_ == site_info) return; - if (!last_committed_site_url_.is_empty()) { + if (!last_committed_site_info_.site_url().is_empty()) { RenderProcessHostImpl::RemoveFrameWithSite( - frame_tree_node_->navigator()->GetController()->GetBrowserContext(), - GetProcess(), last_committed_site_url_); + frame_tree()->controller()->GetBrowserContext(), GetProcess(), + last_committed_site_info_); } - last_committed_site_url_ = site_url; + last_committed_site_info_ = site_info; - if (!last_committed_site_url_.is_empty()) { + if (!last_committed_site_info_.site_url().is_empty()) { RenderProcessHostImpl::AddFrameWithSite( - frame_tree_node_->navigator()->GetController()->GetBrowserContext(), - GetProcess(), last_committed_site_url_); + frame_tree()->controller()->GetBrowserContext(), GetProcess(), + last_committed_site_info_); } } @@ -7544,30 +7883,18 @@ bool RenderFrameHostImpl::ValidateDidCommitParams( // Error pages may sometimes commit a URL in the wrong process, which requires // an exception for the CanCommitOriginAndUrl() checks. This is ok as long // as the origin is opaque. - bool bypass_checks_for_error_page = false; - if (SiteIsolationPolicy::IsErrorPageIsolationEnabled( - frame_tree_node_->IsMainFrame())) { - if (site_instance_->GetSiteURL() == GURL(content::kUnreachableWebDataURL)) { - // Commits in the error page process must only be failures, otherwise - // successful navigations could commit documents from origins different - // than the chrome-error://chromewebdata/ one and violate expectations. - if (!params->url_is_unreachable) { - DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin); - bad_message::ReceivedBadMessage( - process, bad_message::RFH_ERROR_PROCESS_NON_ERROR_COMMIT); - return false; - } - // With error page isolation, any URL can commit in an error page process. - bypass_checks_for_error_page = true; - } - } else { - // Without error page isolation, a blocked navigation is expected to - // commit in the old renderer process. This may be true for subframe - // navigations even when error page isolation is enabled for main frames. - if (navigation_request && - navigation_request->GetNetErrorCode() == net::ERR_BLOCKED_BY_CLIENT) { - bypass_checks_for_error_page = true; - } + bool should_commit_unreachable_url = false; + bool bypass_checks_for_error_page = ShouldBypassChecksForErrorPage( + this, navigation_request, &should_commit_unreachable_url); + + // Commits in the error page process must only be failures, otherwise + // successful navigations could commit documents from origins different + // than the chrome-error://chromewebdata/ one and violate expectations. + if (should_commit_unreachable_url && !params->url_is_unreachable) { + DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin); + bad_message::ReceivedBadMessage( + process, bad_message::RFH_ERROR_PROCESS_NON_ERROR_COMMIT); + return false; } // Error pages must commit in a opaque origin. Terminate the renderer @@ -7650,12 +7977,6 @@ bool RenderFrameHostImpl::ValidateDidCommitParams( // renderer to load the URL and grant the renderer the privileges to request // the URL. To prevent this attack, we block the renderer from inserting // banned URLs into the navigation controller in the first place. - // - // TODO(https://crbug.com/172694): Currently, when FilterURL detects a bad URL - // coming from the renderer, it overwrites that URL to about:blank, which - // requires |params| to be mutable. Once we kill the renderer - // instead, the signature of RenderFrameHostImpl::DidCommitProvisionalLoad can - // be modified to take |params| by const reference. process->FilterURL(false, ¶ms->url); process->FilterURL(true, ¶ms->referrer.url); for (auto it(params->redirects.begin()); it != params->redirects.end(); @@ -7671,25 +7992,22 @@ bool RenderFrameHostImpl::ValidateDidCommitParams( return false; } - // A cross-document navigation requires an embedding token for all embedded - // frames (a child frame to a remote parent). Embedding tokens should not - // exist for other cases. - if (!is_same_document_navigation) { - if (frame_tree_node()->IsMainFrame() && - params->embedding_token.has_value()) { - bad_message::ReceivedBadMessage( - process, bad_message::RFH_UNEXPECTED_EMBEDDING_TOKEN); - return false; - } else if (IsCrossProcessSubframe() && - !params->embedding_token.has_value()) { + // A cross-document navigation requires an embedding token. Navigations + // served from the back-forward cache do not require new embedding tokens as + // the token is already set. + bool is_served_from_bfcache = + navigation_request && navigation_request->IsServedFromBackForwardCache(); + if (!is_served_from_bfcache) { + if (!is_same_document_navigation && !params->embedding_token.has_value()) { bad_message::ReceivedBadMessage(process, bad_message::RFH_MISSING_EMBEDDING_TOKEN); return false; + } else if (is_same_document_navigation && + params->embedding_token.has_value()) { + bad_message::ReceivedBadMessage( + process, bad_message::RFH_UNEXPECTED_EMBEDDING_TOKEN); + return false; } - } else if (params->embedding_token.has_value()) { - bad_message::ReceivedBadMessage( - process, bad_message::RFH_UNEXPECTED_EMBEDDING_TOKEN); - return false; } return true; @@ -7698,9 +8016,9 @@ bool RenderFrameHostImpl::ValidateDidCommitParams( void RenderFrameHostImpl::UpdateSiteURL(const GURL& url, bool url_is_unreachable) { if (url_is_unreachable) { - SetLastCommittedSiteUrl(GURL()); + SetLastCommittedSiteInfo(GURL()); } else { - SetLastCommittedSiteUrl(url); + SetLastCommittedSiteInfo(url); } } @@ -7769,7 +8087,7 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal( // racy DidStopLoading IPC resets the loading state that was set to true in // CommitNavigation. if (!is_loading()) { - bool was_loading = frame_tree_node()->frame_tree()->IsLoading(); + bool was_loading = frame_tree()->IsLoading(); is_loading_ = true; frame_tree_node()->DidStartLoading(!is_same_document_navigation, was_loading); @@ -7815,18 +8133,39 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal( accessibility_reset_count_ = 0; appcache_handle_ = navigation_request->TakeAppCacheHandle(); - if (!is_same_document_navigation && - !navigation_request->IsServedFromBackForwardCache()) { + bool created_new_document = + !is_same_document_navigation && + !navigation_request->IsServedFromBackForwardCache(); + + if (created_new_document) { + // IsWaitingToCommit can be false inside DidCommitNavigationInternal only in + // specific circumstances listed above, and specifically for the fake + // initial navigations triggered by the blank window.open() and creating a + // blank iframe. In that case we do not want to reset the per-document + // states as we are not really creating a new Document and we want to + // preserve the states set by WebContentsCreated delegate notification + // (which among other things create tab helpers) or RenderFrameCreated. + if (!navigation_request->IsWaitingToCommit()) + created_new_document = false; + } + + // TODO(crbug.com/936696): Remove this after we have RenderDocument. + if (created_new_document) { + TRACE_EVENT1("content", "DidCommitProvisionalLoad_StateResetForNewDocument", + "render_frame_host", this); if (navigation_request->IsInMainFrame()) { render_view_host_->ResetPerPageState(); } last_committed_cross_document_navigation_id_ = navigation_request->GetNavigationId(); - if (IsCurrent()) { + if (IsCurrent() && !committed_speculative_rfh_before_navigation_commit_) { // Clear all the user data associated with the non speculative // RenderFrameHost when the navigation is a cross-document navigation not - // served from the back-forward cache. + // served from the back-forward cache. Make sure the data doesn't get + // cleared for the cases when the RenderFrameHost commits before the + // navigation commits. This happens when the current RenderFrameHost + // crashes before navigating to a new URL. document_associated_data_.ClearAllUserData(); } @@ -7836,6 +8175,11 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal( } } + // Keep track of the sandbox policy of the document that has just committed. + // It will be compared with the value computed from the renderer. The latter + // is expected to be received in DidSetFramePolicyHeaders(..). + active_sandbox_flags_control_ = navigation_request->SandboxFlagsToCommit(); + // If we still have a PeakGpuMemoryTracker, then the loading it was observing // never completed. Cancel it's callback so that we don't report partial // loads to UMA. @@ -7862,9 +8206,27 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal( weak_ptr_factory_.GetWeakPtr(), std::move(receiver))); } - frame_tree_node()->navigator()->DidNavigate(this, *params, - std::move(navigation_request), - is_same_document_navigation); + std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter = + navigation_request->TakeCoopReporter(); + + // If this navigation had a COOP BrowsingInstance swap that severed an opener, + // and we have a reporter on the page we're going to, report it here. + if (navigation_request->coop_status() + .had_opener_before_browsing_instance_swap && + coop_reporter) { + coop_reporter->QueueOpenerBreakageReport( + coop_reporter->GetPreviousDocumentUrlForReporting( + navigation_request->GetRedirectChain(), + navigation_request->common_params().referrer->url), + false /* is_reported_from_document */, false /* is_report_only */); + } + + frame_tree_node()->navigator().DidNavigate(this, *params, + std::move(navigation_request), + is_same_document_navigation); + + // Reset back the state to false after navigation commits. + committed_speculative_rfh_before_navigation_commit_ = false; if (IsBackForwardCacheEnabled()) { // Store the Commit params so they can be reused if the page is ever @@ -7873,12 +8235,11 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal( } if (!is_same_document_navigation) { - cookie_no_samesite_deprecation_url_hashes_.clear(); - cookie_samesite_none_insecure_deprecation_url_hashes_.clear(); renderer_reported_scheduler_tracked_features_ = 0; browser_reported_scheduler_tracked_features_ = 0; last_committed_client_security_state_ = std::move(client_security_state); coep_reporter_ = std::move(coep_reporter); + coop_reporter_ = std::move(coop_reporter); } RecordCrossOriginIsolationMetrics(this); @@ -7900,7 +8261,7 @@ void RenderFrameHostImpl::OnSameDocumentCommitProcessed( if (result == blink::mojom::CommitResult::RestartCrossDocument) { // The navigation could not be committed as a same-document navigation. // Restart the navigation cross-document. - frame_tree_node_->navigator()->RestartNavigationAsCrossDocument( + frame_tree_node_->navigator().RestartNavigationAsCrossDocument( std::move(same_document_navigation_request_)); } @@ -8016,36 +8377,18 @@ void RenderFrameHostImpl::SendCommitNavigation( base::Optional<std::vector<::content::mojom::TransferrableURLLoaderPtr>> subresource_overrides, blink::mojom::ControllerServiceWorkerInfoPtr controller, - blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info, + blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info, mojo::PendingRemote<network::mojom::URLLoaderFactory> prefetch_loader_factory, const base::UnguessableToken& devtools_navigation_token) { - // For committed interstitials, we do not have a NavigationRequest and use the - // old NavigationControl mojo interface. For anything else we should have a - // NavigationRequest, containing a NavigationClient, and commit through it. - // TODO(ahemery): Update when https://crbug.com/448486 is done. - if (navigation_client) { - navigation_client->CommitNavigation( - std::move(common_params), std::move(commit_params), - std::move(response_head), std::move(response_body), - std::move(url_loader_client_endpoints), - std::move(subresource_loader_factories), - std::move(subresource_overrides), std::move(controller), - std::move(provider_info), std::move(prefetch_loader_factory), - devtools_navigation_token, - BuildCommitNavigationCallback(navigation_request)); - } else { - DCHECK(!navigation_request); - GetNavigationControl()->CommitNavigation( - std::move(common_params), std::move(commit_params), - std::move(response_head), std::move(response_body), - std::move(url_loader_client_endpoints), - std::move(subresource_loader_factories), - std::move(subresource_overrides), std::move(controller), - std::move(provider_info), std::move(prefetch_loader_factory), - devtools_navigation_token, - mojom::FrameNavigationControl::CommitNavigationCallback()); - } + navigation_client->CommitNavigation( + std::move(common_params), std::move(commit_params), + std::move(response_head), std::move(response_body), + std::move(url_loader_client_endpoints), + std::move(subresource_loader_factories), std::move(subresource_overrides), + std::move(controller), std::move(container_info), + std::move(prefetch_loader_factory), devtools_navigation_token, + BuildCommitNavigationCallback(navigation_request)); } void RenderFrameHostImpl::SendCommitFailedNavigation( @@ -8073,6 +8416,15 @@ void RenderFrameHostImpl::DidCommitNavigation( std::unique_ptr<NavigationRequest> committing_navigation_request, std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params, mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params) { + // BackForwardCacheImpl::CanStoreRenderFrameHost prevents placing the pages + // with in-flight navigation requests in the back-forward cache and it's not + // possible to start/commit a new one after the RenderFrameHost is in the + // BackForwardCache (see the check IsInactiveAndDisallowReactivation in + // RFH::DidCommitSameDocumentNavigation() and RFH::BeginNavigation()) so it + // isn't possible to get a DidCommitNavigation IPC from the renderer in + // kInBackForwardCache state. + DCHECK(!IsInBackForwardCache()); + NavigationRequest* request; if (committing_navigation_request) { request = committing_navigation_request.get(); @@ -8089,8 +8441,8 @@ void RenderFrameHostImpl::DidCommitNavigation( // DidCommitProvisionalLoad IPC should be associated with the URL being // committed (not with the *last* committed URL that most other IPCs are // associated with). - ScopedActiveURL scoped_active_url( - params->url, frame_tree_node()->frame_tree()->root()->current_origin()); + ScopedActiveURL scoped_active_url(params->url, + frame_tree()->root()->current_origin()); ScopedCommitStateResetter commit_state_resetter(this); RenderProcessHost* process = GetProcess(); @@ -8122,7 +8474,7 @@ void RenderFrameHostImpl::DidCommitNavigation( // destroying this RenderFrameHost. Note that we intentionally do not ignore // commits that happen while the current tab is being closed - see // https://crbug.com/805705. - if (!is_active()) + if (IsPendingDeletion()) return; // Retroactive sanity check: @@ -8253,24 +8605,6 @@ void RenderFrameHostImpl::RemoveServiceWorkerContainerHost( service_worker_container_hosts_.erase(uuid); } -void RenderFrameHostImpl::UpdateFrameFrozenState() { - // TODO(http://crbug.com/1014212): remove this. - CHECK(frame_); - if (!IsFeatureEnabled( - blink::mojom::FeaturePolicyFeature::kExecutionWhileNotRendered) && - visibility_ == blink::mojom::FrameVisibility::kNotRendered) { - frame_->SetLifecycleState(blink::mojom::FrameLifecycleState::kFrozen); - } else if (!IsFeatureEnabled(blink::mojom::FeaturePolicyFeature:: - kExecutionWhileOutOfViewport) && - visibility_ == - blink::mojom::FrameVisibility::kRenderedOutOfViewport) { - frame_->SetLifecycleState( - blink::mojom::FrameLifecycleState::kFrozenAutoResumeMedia); - } else { - frame_->SetLifecycleState(blink::mojom::FrameLifecycleState::kRunning); - } -} - bool RenderFrameHostImpl::MaybeInterceptCommitCallback( NavigationRequest* navigation_request, FrameHostMsg_DidCommitProvisionalLoad_Params* params, @@ -8368,108 +8702,16 @@ void RenderFrameHostImpl::AddMessageToConsoleImpl( discard_duplicates); } -void RenderFrameHostImpl::AddSameSiteCookieDeprecationMessage( - const std::string& cookie_url, - net::CanonicalCookie::CookieInclusionStatus status, - bool is_lax_by_default_enabled, - bool is_none_requires_secure_enabled) { - std::string deprecation_message; - // The status will have, at most, one of these warning messages at any given - // time. - if (status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus::WarningReason:: - WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT)) { - if (!ShouldAddCookieSameSiteDeprecationMessage( - cookie_url, &cookie_no_samesite_deprecation_url_hashes_)) { - return; - } - std::string warning_or_blocked_message = - (is_lax_by_default_enabled - ? "It has been blocked, as Chrome now only delivers " - : "A future release of Chrome will only deliver "); - deprecation_message = - "A cookie associated with a cross-site resource at " + cookie_url + - " was set without the `SameSite` attribute. " + - warning_or_blocked_message + - "cookies with " - "cross-site requests if they are set with `SameSite=None` and " - "`Secure`. You can review cookies in developer tools under " - "Application>Storage>Cookies and see more details at " - "https://www.chromestatus.com/feature/5088147346030592 and " - "https://www.chromestatus.com/feature/5633521622188032."; - } else if (status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus::WarningReason:: - WARN_SAMESITE_NONE_INSECURE)) { - if (!ShouldAddCookieSameSiteDeprecationMessage( - cookie_url, - &cookie_samesite_none_insecure_deprecation_url_hashes_)) { - return; - } - std::string warning_or_blocked_message = - (is_none_requires_secure_enabled - ? "It has been blocked, as Chrome now only delivers " - : "A future release of Chrome will only deliver "); - deprecation_message = - "A cookie associated with a resource at " + cookie_url + - " was set with `SameSite=None` but without `Secure`. " + - warning_or_blocked_message + - "cookies marked " - "`SameSite=None` if they are also marked `Secure`. You " - "can review cookies in developer tools under " - "Application>Storage>Cookies and see more details at " - "https://www.chromestatus.com/feature/5633521622188032."; - } else if (status.HasWarningReason( - net::CanonicalCookie::CookieInclusionStatus::WarningReason:: - WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE)) { - if (!ShouldAddCookieSameSiteDeprecationMessage( - cookie_url, &cookie_lax_allow_unsafe_deprecation_url_hashes_)) { - return; - } - deprecation_message = - "A cookie associated with a resource at " + cookie_url + - " set without a `SameSite` attribute was sent with a non-idempotent " - "top-level cross-site request because it was less than " + - base::NumberToString(net::kLaxAllowUnsafeMaxAge.InMinutes()) + - " minutes old. A future release of Chrome will treat such cookies as " - "if they were set with `SameSite=Lax` and will only allow them to be " - "sent with top-level cross-site requests if the HTTP method is safe. " - "See more details at " - "https://www.chromestatus.com/feature/5088147346030592."; - } - - if (deprecation_message.empty()) - return; - - AddUniqueMessageToConsole(blink::mojom::ConsoleMessageLevel::kWarning, - deprecation_message); -} - void RenderFrameHostImpl::AddInspectorIssue( blink::mojom::InspectorIssueInfoPtr info) { GetAssociatedLocalFrame()->AddInspectorIssue(std::move(info)); } -bool RenderFrameHostImpl::ShouldAddCookieSameSiteDeprecationMessage( - const std::string& cookie_url, - base::circular_deque<size_t>* already_seen_url_hashes) { - DCHECK_LE(already_seen_url_hashes->size(), kMaxCookieSameSiteDeprecationUrls); - size_t cookie_url_hash = base::FastHash(cookie_url); - if (base::Contains(*already_seen_url_hashes, cookie_url_hash)) - return false; - - // Put most recent ones at the front because we are likely to have multiple - // consecutive cookies with the same URL. - if (already_seen_url_hashes->size() == kMaxCookieSameSiteDeprecationUrls) - already_seen_url_hashes->pop_back(); - already_seen_url_hashes->push_front(cookie_url_hash); - return true; -} - void RenderFrameHostImpl::LogCannotCommitUrlCrashKeys( const GURL& url, bool is_same_document_navigation, NavigationRequest* navigation_request) { - LogRendererKillCrashKeys(GetSiteInstance()->GetSiteURL()); + LogRendererKillCrashKeys(GetSiteInstance()->GetSiteInfo()); // Temporary instrumentation to debug the root cause of renderer process // terminations. See https://crbug.com/931895. @@ -8558,8 +8800,8 @@ void RenderFrameHostImpl::LogCannotCommitUrlCrashKeys( base::debug::AllocateCrashKeyString("starting_site_instance", base::debug::CrashKeySize::Size64), navigation_request->GetStartingSiteInstance() - ->GetSiteURL() - .possibly_invalid_spec()); + ->GetSiteInfo() + .GetDebugString()); // Recompute the target SiteInstance to see if it matches the current // one at commit time. @@ -8582,10 +8824,9 @@ void RenderFrameHostImpl::MaybeEvictFromBackForwardCache() { while (RenderFrameHostImpl* parent = top_document->GetParent()) top_document = parent; - NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( - frame_tree_node_->navigator()->GetController()); auto can_store = - controller->GetBackForwardCache().CanStoreDocument(top_document); + frame_tree()->controller()->GetBackForwardCache().CanStoreDocument( + top_document); TRACE_EVENT1("navigation", "RenderFrameHostImpl::MaybeEvictFromBackForwardCache", "can_store", can_store.ToString()); @@ -8599,7 +8840,7 @@ void RenderFrameHostImpl::MaybeEvictFromBackForwardCache() { void RenderFrameHostImpl::LogCannotCommitOriginCrashKeys( bool is_same_document_navigation, NavigationRequest* navigation_request) { - LogRendererKillCrashKeys(GetSiteInstance()->GetSiteURL()); + LogRendererKillCrashKeys(GetSiteInstance()->GetSiteInfo()); // Temporary instrumentation to debug the root cause of // https://crbug.com/923144. @@ -8615,9 +8856,9 @@ void RenderFrameHostImpl::LogCannotCommitOriginCrashKeys( bool_to_crash_key(!frame_tree_node_->IsMainFrame())); base::debug::SetCrashKeyString( - base::debug::AllocateCrashKeyString("is_active", + base::debug::AllocateCrashKeyString("lifecycle_state", base::debug::CrashKeySize::Size32), - bool_to_crash_key(is_active())); + LifecycleStateToString(lifecycle_state_)); base::debug::SetCrashKeyString( base::debug::AllocateCrashKeyString("is_current", @@ -8661,8 +8902,8 @@ void RenderFrameHostImpl::LogCannotCommitOriginCrashKeys( base::debug::AllocateCrashKeyString("starting_site_instance", base::debug::CrashKeySize::Size64), navigation_request->GetStartingSiteInstance() - ->GetSiteURL() - .possibly_invalid_spec()); + ->GetSiteInfo() + .GetDebugString()); } } @@ -8671,7 +8912,7 @@ void RenderFrameHostImpl::EnableMojoJsBindings() { DCHECK_NE(WebUI::kNoWebUI, WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType( GetSiteInstance()->GetBrowserContext(), - site_instance_->GetSiteURL())); + site_instance_->GetSiteInfo().site_url())); if (!frame_bindings_control_) GetRemoteAssociatedInterfaces()->GetInterface(&frame_bindings_control_); @@ -8680,9 +8921,7 @@ void RenderFrameHostImpl::EnableMojoJsBindings() { BackForwardCacheMetrics* RenderFrameHostImpl::GetBackForwardCacheMetrics() { NavigationEntryImpl* navigation_entry = - static_cast<NavigationControllerImpl*>( - frame_tree_node_->navigator()->GetController()) - ->GetEntryWithUniqueID(nav_entry_id()); + frame_tree()->controller()->GetEntryWithUniqueID(nav_entry_id()); if (!navigation_entry) return nullptr; return navigation_entry->back_forward_cache_metrics(); @@ -8804,32 +9043,43 @@ void RenderFrameHostImpl::SetLifecycleStateToActive() { } void RenderFrameHostImpl::SetLifecycleState(LifecycleState state) { - // Cross-verify that |lifecycle_state_| transition happens correctly. - switch (state) { - case LifecycleState::kSpeculative: - // RenderFrameHost is only set speculative during its creation and no - // transitions happen to this state during its lifetime. - NOTREACHED(); - break; - case LifecycleState::kActive: - DCHECK(lifecycle_state_ == LifecycleState::kSpeculative || - lifecycle_state_ == LifecycleState::kInBackForwardCache) - << "Unexpected LifeCycleState " << int(lifecycle_state_); - break; - case LifecycleState::kInBackForwardCache: - DCHECK_EQ(lifecycle_state_, LifecycleState::kActive) - << "Unexpected LifeCycleState " << int(lifecycle_state_); - break; - case LifecycleState::kRunningUnloadHandlers: - DCHECK_EQ(lifecycle_state_, LifecycleState::kActive) - << "Unexpected LifeCycleState " << int(lifecycle_state_); - break; - case LifecycleState::kReadyToBeDeleted: - DCHECK_NE(lifecycle_state_, LifecycleState::kReadyToBeDeleted) - << "Unexpected LifeCycleState " << int(lifecycle_state_); - break; - } + TRACE_EVENT2("content", "RenderFrameHostImpl::SetLifecycleState", + "render_frame_host", this, "state", + LifecycleStateToString(state)); +#if DCHECK_IS_ON() + static const base::NoDestructor<StateTransitions<LifecycleState>> + allowed_transitions( + // For a graph of state transitions, see + // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/render-frame-host-lifecycle-state.png + // To update the graph, see the corresponding .gv file. + + // RenderFrameHost is only set speculative during its creation and no + // transitions happen to this state during its lifetime. + StateTransitions<LifecycleState>({ + {LifecycleState::kSpeculative, + {LifecycleState::kActive, LifecycleState::kReadyToBeDeleted}}, + + {LifecycleState::kActive, + {LifecycleState::kInBackForwardCache, + LifecycleState::kRunningUnloadHandlers, + LifecycleState::kReadyToBeDeleted}}, + + {LifecycleState::kInBackForwardCache, + {LifecycleState::kActive, LifecycleState::kReadyToBeDeleted}}, + + {LifecycleState::kRunningUnloadHandlers, + {LifecycleState::kReadyToBeDeleted}}, + + {LifecycleState::kReadyToBeDeleted, {}}, + })); + DCHECK_STATE_TRANSITION(allowed_transitions, /*old_state=*/lifecycle_state_, + /*new_state*/ state); +#endif // DCHECK_IS_ON() + + LifecycleState old_state = lifecycle_state_; lifecycle_state_ = state; + // Notify the delegate about change in |lifecycle_state_|. + delegate_->RenderFrameHostStateChanged(this, old_state, lifecycle_state_); } void RenderFrameHostImpl::BindReportingObserver( @@ -8862,4 +9112,22 @@ void RenderFrameHostImpl::OnCookiesAccessed( delegate_->OnCookiesAccessed(this, blocked); } +void RenderFrameHostImpl::CheckSandboxFlags() { + if (is_mhtml_document_) + return; + + if (!active_sandbox_flags_control_) + return; + + if (active_sandbox_flags_ == *active_sandbox_flags_control_) + return; + + DCHECK(false); +} + +std::ostream& operator<<(std::ostream& o, + const RenderFrameHostImpl::LifecycleState& s) { + return o << LifecycleStateToString(s); +} + } // namespace content diff --git a/chromium/content/browser/frame_host/render_frame_host_impl.h b/chromium/content/browser/frame_host/render_frame_host_impl.h index 513fca6f861..d2af86857f5 100644 --- a/chromium/content/browser/frame_host/render_frame_host_impl.h +++ b/chromium/content/browser/frame_host/render_frame_host_impl.h @@ -39,18 +39,19 @@ #include "content/browser/feature_observer.h" #include "content/browser/frame_host/back_forward_cache_metrics.h" #include "content/browser/frame_host/should_swap_browsing_instance.h" +#include "content/browser/net/cross_origin_opener_policy_reporter.h" #include "content/browser/renderer_host/media/render_frame_audio_input_stream_factory.h" #include "content/browser/renderer_host/media/render_frame_audio_output_stream_factory.h" #include "content/browser/site_instance_impl.h" #include "content/browser/webui/web_ui_impl.h" #include "content/common/ax_content_node_data.h" +#include "content/common/ax_content_tree_update.h" #include "content/common/buildflags.h" #include "content/common/content_export.h" #include "content/common/dom_automation_controller.mojom.h" #include "content/common/frame.mojom.h" #include "content/common/frame_delete_intention.h" #include "content/common/frame_replication_state.h" -#include "content/common/input/input_handler.mojom.h" #include "content/common/input/input_injector.mojom-forward.h" #include "content/common/navigation_params.mojom.h" #include "content/common/render_accessibility.mojom.h" @@ -78,6 +79,7 @@ #include "net/base/isolation_info.h" #include "net/base/network_isolation_key.h" #include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_inclusion_status.h" #include "net/http/http_response_headers.h" #include "services/device/public/mojom/sensor_provider.mojom.h" #include "services/device/public/mojom/wake_lock_context.mojom.h" @@ -111,6 +113,7 @@ #include "third_party/blink/public/mojom/idle/idle_manager.mojom.h" #include "third_party/blink/public/mojom/image_downloader/image_downloader.mojom.h" #include "third_party/blink/public/mojom/input/focus_type.mojom-forward.h" +#include "third_party/blink/public/mojom/input/input_handler.mojom.h" #include "third_party/blink/public/mojom/installedapp/installed_app_provider.mojom.h" #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-forward.h" #include "third_party/blink/public/mojom/native_file_system/native_file_system_manager.mojom-forward.h" @@ -152,7 +155,6 @@ #endif class GURL; -struct FrameHostMsg_OpenURL_Params; namespace blink { class AssociatedInterfaceProvider; @@ -262,6 +264,9 @@ class CONTENT_EXPORT RenderFrameHostImpl static RenderFrameHostImpl* FromID(GlobalFrameRoutingId id); static RenderFrameHostImpl* FromID(int process_id, int routing_id); + static RenderFrameHostImpl* FromFrameToken( + int process_id, + const base::UnguessableToken& frame_token); static RenderFrameHostImpl* FromAXTreeID(ui::AXTreeID ax_tree_id); static RenderFrameHostImpl* FromOverlayRoutingToken( const base::UnguessableToken& token); @@ -273,6 +278,7 @@ class CONTENT_EXPORT RenderFrameHostImpl // RenderFrameHost int GetRoutingID() override; + const base::UnguessableToken& GetFrameToken() override; ui::AXTreeID GetAXTreeID() override; SiteInstanceImpl* GetSiteInstance() override; RenderProcessHost* GetProcess() override; @@ -341,8 +347,6 @@ class CONTENT_EXPORT RenderFrameHostImpl bool IsFeatureEnabled(blink::mojom::DocumentPolicyFeature feature, blink::PolicyValue threshold_value) override; void ViewSource() override; - mojo::Remote<blink::mojom::PauseSubresourceLoadingHandle> - PauseSubresourceLoading() override; void ExecuteMediaPlayerActionAtLocation( const gfx::Point&, const blink::mojom::MediaPlayerAction& action) override; @@ -376,6 +380,8 @@ class CONTENT_EXPORT RenderFrameHostImpl ukm::SourceId GetPageUkmSourceId() override; StoragePartition* GetStoragePartition() override; BrowserContext* GetBrowserContext() override; + void ReportHeavyAdIssue(blink::mojom::HeavyAdResolutionStatus resolution, + blink::mojom::HeavyAdReason reason) override; // Determines if a clipboard paste using |data| of type |data_type| is allowed // in this renderer frame. The implementation delegates to @@ -388,6 +394,28 @@ class CONTENT_EXPORT RenderFrameHostImpl void SendAccessibilityEventsToManager( const AXEventNotificationDetails& details); + // Returns true iff the RenderFrameHost is inactive i.e., when the + // |lifecycle_state_| of this RenderFrameHost is in either kInBackForwardCache + // or kRunningUnloadHandlers or kReadyToBeDeleted states. This function should + // be used when we are unsure if inactive RenderFrameHost can be properly + // handled and their processing shouldn't be deferred until the + // RenderFrameHost becomes active again. + // + // This method additionally has a side effect for back-forward cache: it + // disallows reactivating by evicting the document from the cache and + // triggering deletion. This avoids reactivating the frame as restoring would + // be unsafe after dropping an event, which means that the frame will never be + // shown to the user again and the event can be safely ignored. + // + // Note that if |IsInactiveAndDisallowReactivation()| returns false, then + // IsCurrent() returns false as well. + // We shouldn't be calling this for a speculative RenderFrameHost as we don't + // want to support disallowing activation before the document became active + // for the first time. In that case |IsInactiveAndDisallowReactivation()| + // returns false along with logging a DumpWithoutCrashing to understand the + // root cause. + bool IsInactiveAndDisallowReactivation(); + void EvictFromBackForwardCacheWithReason( BackForwardCacheMetrics::NotRestoredReason reason); void EvictFromBackForwardCacheWithReasons( @@ -414,6 +442,12 @@ class CONTENT_EXPORT RenderFrameHostImpl gfx::NativeViewAccessible AccessibilityGetNativeViewAccessibleForWindow() override; WebContents* AccessibilityWebContents() override; + void AccessibilityHitTest( + const gfx::Point& point_in_frame_pixels, + ax::mojom::Event opt_event_to_fire, + int opt_request_id, + base::OnceCallback<void(BrowserAccessibilityManager* hit_manager, + int hit_node_id)> opt_callback) override; bool AccessibilityIsMainFrame() override; // RenderProcessHostObserver implementation. @@ -438,20 +472,19 @@ class CONTENT_EXPORT RenderFrameHostImpl void PerformAction(const ui::AXActionData& data) override; bool RequiresPerformActionPointInPixels() const override; - mojom::FrameInputHandler* GetFrameInputHandler(); - viz::mojom::InputTargetClient* GetInputTargetClient() { return input_target_client_; } // Creates a RenderFrame in the renderer process. - bool CreateRenderFrame(int previous_routing_id, - int opener_routing_id, - int parent_routing_id, - int previous_sibling_routing_id); + bool CreateRenderFrame( + int previous_routing_id, + const base::Optional<base::UnguessableToken>& opener_frame_token, + int parent_routing_id, + int previous_sibling_routing_id); // Deletes the RenderFrame in the renderer process. - // Postcondition: |is_active()| will return false. + // Postcondition: |IsPendingDeletion()| is true. void DeleteRenderFrame(FrameDeleteIntention intent); // Tracks whether the RenderFrame for this RenderFrameHost has been created in @@ -464,6 +497,10 @@ class CONTENT_EXPORT RenderFrameHostImpl // Returns true if the frame recently plays an audio. bool is_audible() const { return is_audible_; } + + // Toggles the audible state of this render frame. This should only be called + // from AudioStreamMonitor, and should not be invoked with the same value + // successively. void OnAudibleStateChanged(bool is_audible); int routing_id() const { return routing_id_; } @@ -505,6 +542,7 @@ class CONTENT_EXPORT RenderFrameHostImpl RenderViewHostImpl* render_view_host() { return render_view_host_.get(); } RenderFrameHostDelegate* delegate() { return delegate_; } + FrameTree* frame_tree() const { return frame_tree_; } FrameTreeNode* frame_tree_node() const { return frame_tree_node_; } // Methods to add/remove/reset/query child FrameTreeNodes of this frame. @@ -560,6 +598,17 @@ class CONTENT_EXPORT RenderFrameHostImpl void GetCanonicalUrlForSharing( mojom::Frame::GetCanonicalUrlForSharingCallback callback); + // Get HTML data for this RenderFrame by serializing contents on the renderer + // side and replacing all links to both same-site and cross-site resources + // with paths to local copies as specified by |url_map| and |frame_token_map|. + void GetSerializedHtmlWithLocalLinks( + const base::flat_map<GURL, base::FilePath>& url_map, + const base::flat_map<base::UnguessableToken, base::FilePath>& + frame_token_map, + bool save_with_empty_url, + mojo::PendingRemote<mojom::FrameHTMLSerializerHandler> + serializer_handler); + // Returns the associated WebUI or null if none applies. WebUIImpl* web_ui() const { return web_ui_.get(); } WebUI::TypeID web_ui_type() const { return web_ui_type_; } @@ -659,7 +708,7 @@ class CONTENT_EXPORT RenderFrameHostImpl // Remove this frame and its children. This happens asynchronously, an IPC // round trip with the renderer process is needed to ensure children's unload // handlers are run. - // Postcondition: is_active() is false. + // Postcondition: |IsPendingDeletion()| is true. void DetachFromProxy(); // Whether an ongoing navigation in this frame is waiting for a BeforeUnload @@ -681,24 +730,12 @@ class CONTENT_EXPORT RenderFrameHostImpl // out. void OnUnloaded(); - // This method returns true from the time this RenderFrameHost is created - // until it is pending deletion. Pending deletion starts when Unload() is - // called on the frame or one of its ancestors. - // Returns false when the frame is in the BackForwardCache. - // TODO(https://crbug.com/1073449): Replace all occurrences of is_active. - bool is_active() const { - return lifecycle_state_ == LifecycleState::kActive || - lifecycle_state_ == LifecycleState::kSpeculative; - } - - // Navigates to an interstitial page represented by the provided data URL. - void NavigateToInterstitialURL(const GURL& data_url); // Stop the load in progress. void Stop(); // Defines different states the RenderFrameHost can be in during its lifetime - // i.e., from point of creation to deletion. + // i.e., from point of creation to deletion. See |SetLifecycleState|. enum class LifecycleState { // This state corresponds to when a speculative RenderFrameHost is created // for an ongoing navigation (to new URL) but hasn't been swapped in the @@ -878,7 +915,7 @@ class CONTENT_EXPORT RenderFrameHostImpl base::Optional<SubresourceLoaderParams> subresource_loader_params, base::Optional<std::vector<mojom::TransferrableURLLoaderPtr>> subresource_overrides, - blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info, + blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info, const base::UnguessableToken& devtools_navigation_token, std::unique_ptr<WebBundleHandle> web_bundle_handle); @@ -938,7 +975,9 @@ class CONTENT_EXPORT RenderFrameHostImpl void ResetLoadingState(); // Returns the feature policy which should be enforced on this RenderFrame. - blink::FeaturePolicy* feature_policy() { return feature_policy_.get(); } + const blink::FeaturePolicy* feature_policy() const { + return feature_policy_.get(); + } void ClearFocusedElement(); @@ -984,8 +1023,7 @@ class CONTENT_EXPORT RenderFrameHostImpl // or speculative RenderFrameHost (that has not committed) should be avoided. void SetVisibilityForChildViews(bool visible); - const base::UnguessableToken& frame_token() const { return frame_token_; } - const base::UnguessableToken& GetTopFrameToken() const; + const base::UnguessableToken& GetTopFrameToken(); // Returns an unguessable token for this RFHI. This provides a temporary way // to identify a RenderFrameHost that's compatible with IPC. Else, one needs @@ -1062,8 +1100,6 @@ class CONTENT_EXPORT RenderFrameHostImpl // for unload handler processing. void SetSubframeUnloadTimeoutForTesting(const base::TimeDelta& timeout); - mojo::Remote<blink::mojom::FileChooser> BindFileChooserForTesting(); - // Called when the WebAudio AudioContext given by |audio_context_id| has // started (or stopped) playing audible audio. void AudioContextPlaybackStarted(int audio_context_id); @@ -1124,12 +1160,14 @@ class CONTENT_EXPORT RenderFrameHostImpl void NavigationRequestCancelled(NavigationRequest* navigation_request); // Called on the main frame of a page embedded in a Portal when it is - // activated. The frame has the option to adopt the previous page as a portal - // containing the contents |predecessor_web_contents|. The activation - // can optionally include a message |data| dispatched with the - // PortalActivateEvent. + // activated. The frame has the option to adopt the previous page, + // |predecessor|, as a portal. The activation can optionally include a message + // |data| dispatched with the PortalActivateEvent. void OnPortalActivated( - std::unique_ptr<WebContents> predecessor_web_contents, + std::unique_ptr<Portal> predecessor, + mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal, + mojo::PendingAssociatedReceiver<blink::mojom::PortalClient> + client_receiver, blink::TransferableMessage data, base::OnceCallback<void(blink::mojom::PortalActivateResult)> callback); @@ -1152,6 +1190,9 @@ class CONTENT_EXPORT RenderFrameHostImpl // Returns true if the frame is embedded in a Portal. bool InsidePortal(); + bool ShouldVirtualKeyboardOverlayContent() const; + void NotifyVirtualKeyboardOverlayRect(const gfx::Rect& keyboard_rect); + blink::mojom::FrameVisibility visibility() const { return visibility_; } // A CommitCallbackInterceptor is used to modify parameters for or cancel a @@ -1214,21 +1255,13 @@ class CONTENT_EXPORT RenderFrameHostImpl blink::mojom::ConsoleMessageLevel level, const std::string& message); - // Add cookie SameSite deprecation messages to the DevTools console. - // TODO(crbug.com/977040): Remove when no longer needed. - void AddSameSiteCookieDeprecationMessage( - const std::string& cookie_url, - net::CanonicalCookie::CookieInclusionStatus status, - bool is_lax_by_default_enabled, - bool is_none_requires_secure_enabled); - // Notify the scheduler that this frame used a feature which impacts the // scheduling policy (e.g. whether the frame can be frozen or put into the // back-forward cache). void OnSchedulerTrackedFeatureUsed( blink::scheduler::WebSchedulerTrackedFeature feature); - // Returns true if frame is frozen. + // Returns true if the frame is frozen. bool IsFrozen(); void CreateAppCacheBackend( @@ -1273,9 +1306,6 @@ class CONTENT_EXPORT RenderFrameHostImpl void CreateIDBFactory( mojo::PendingReceiver<blink::mojom::IDBFactory> receiver); - void GetFileChooser( - mojo::PendingReceiver<blink::mojom::FileChooser> receiver); - void GetSensorProvider( mojo::PendingReceiver<device::mojom::SensorProvider> receiver); @@ -1380,6 +1410,10 @@ class CONTENT_EXPORT RenderFrameHostImpl CrossOriginEmbedderPolicyReporter* coep_reporter() { return coep_reporter_.get(); } + void set_coop_reporter( + std::unique_ptr<CrossOriginOpenerPolicyReporter>&& reporter) { + coop_reporter_ = std::move(reporter); + } // Semi-formal definition of COOP: // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e @@ -1389,6 +1423,9 @@ class CONTENT_EXPORT RenderFrameHostImpl void set_cross_origin_opener_policy(network::CrossOriginOpenerPolicy policy) { cross_origin_opener_policy_ = policy; } + CrossOriginOpenerPolicyReporter* coop_reporter() { + return coop_reporter_.get(); + } const network::mojom::ClientSecurityStatePtr& last_committed_client_security_state() const { @@ -1407,8 +1444,7 @@ class CONTENT_EXPORT RenderFrameHostImpl // Sets the embedding token corresponding to the document in this // RenderFrameHost. - void SetEmbeddingToken( - const base::Optional<base::UnguessableToken>& embedding_token); + void SetEmbeddingToken(const base::UnguessableToken& embedding_token); // Return true if the process this RenderFrameHost is using has crashed and we // are replacing RenderFrameHosts for crashed frames rather than reusing them. @@ -1419,7 +1455,7 @@ class CONTENT_EXPORT RenderFrameHostImpl bool must_be_replaced() const { return must_be_replaced_; } // Resets the must_be_replaced after the RFH has been reinitialized. Do not // add any more usages of this. - // TODO(https://crbug.com/1006814): Remove this. + // TODO(https://crbug.com/936696): Remove this. void reset_must_be_replaced() { must_be_replaced_ = false; } std::unique_ptr<blink::PendingURLLoaderFactoryBundle> @@ -1459,6 +1495,11 @@ class CONTENT_EXPORT RenderFrameHostImpl // WebContents. RenderFrameHostImpl* ParentOrOuterDelegateFrame(); + void SetIsOuterDelegateFrame(bool is_outer_frame) { + is_outer_delegate_frame_ = is_outer_frame; + } + bool IsOuterDelegateFrame() { return is_outer_delegate_frame_; } + scoped_refptr<WebAuthRequestSecurityChecker> GetWebAuthRequestSecurityChecker(); @@ -1480,12 +1521,13 @@ class CONTENT_EXPORT RenderFrameHostImpl void DidContainInsecureFormAction() override; void DocumentAvailableInMainFrame(bool uses_temporary_zoom_level) override; void SetNeedsOcclusionTracking(bool needs_tracking) override; - void LifecycleStateChanged(blink::mojom::FrameLifecycleState state) override; + void SetVirtualKeyboardOverlayPolicy(bool vk_overlays_content) override; void EvictFromBackForwardCache() override; void VisibilityChanged(blink::mojom::FrameVisibility) override; void DidChangeThemeColor(const base::Optional<SkColor>& theme_color) override; void DidFailLoadWithError(const GURL& url, int32_t error_code) override; void DidFocusFrame() override; + void DidCallFocus() override; void DidAddContentSecurityPolicies( std::vector<network::mojom::ContentSecurityPolicyPtr> policies) override; void EnforceInsecureRequestPolicy( @@ -1532,7 +1574,6 @@ class CONTENT_EXPORT RenderFrameHostImpl RunModalPromptDialogCallback callback) override; void RunBeforeUnloadConfirm(bool is_reload, RunBeforeUnloadConfirmCallback callback) override; - void Are3DAPIsBlocked(Are3DAPIsBlockedCallback callback) override; void UpdateFaviconURL( std::vector<blink::mojom::FaviconURLPtr> favicon_urls) override; void DownloadURL(blink::mojom::DownloadURLParamsPtr params) override; @@ -1555,12 +1596,17 @@ class CONTENT_EXPORT RenderFrameHostImpl void DidChangeFrameOwnerProperties( const base::UnguessableToken& child_frame_token, blink::mojom::FrameOwnerPropertiesPtr frame_owner_properties) override; + void DidChangeOpener( + const base::Optional<base::UnguessableToken>& opener_frame) override; + void DidChangeFramePolicy(const base::UnguessableToken& child_frame_token, + const blink::FramePolicy& frame_policy) override; // blink::LocalMainFrameHost overrides: void ScaleFactorChanged(float scale) override; void ContentsPreferredSizeChanged(const gfx::Size& pref_size) override; void TextAutosizerPageInfoChanged( blink::mojom::TextAutosizerPageInfoPtr page_info) override; + void FocusPage() override; void ReportNoBinderForInterface(const std::string& error); @@ -1582,6 +1628,13 @@ class CONTENT_EXPORT RenderFrameHostImpl // - Ignore any OnUnloadACK sent by the renderer process. void DoNotDeleteForTesting(); + // This method will unset the flag |do_not_delete_for_testing_| to resume + // deletion on the RenderFrameHost. Deletion will only be triggered if + // RenderFrameHostImpl::OnDetach() is called for the RenderFrameHost. This is + // a counterpart for DoNotDeleteForTesting() which sets the flag + // |do_not_delete_for_testing_|. + void ResumeDeletionForTesting(); + // Document-associated data. This is cleared whenever a new document is hosted // by this RenderFrameHost. Please refer to the description at // content/public/browser/render_document_host_user_data.h for more details. @@ -1600,6 +1653,15 @@ class CONTENT_EXPORT RenderFrameHostImpl document_associated_data_.RemoveUserData(key); } + // Called when we commit speculative RFH early due to not having an alive + // current frame. This happens when the renderer crashes before navigating to + // a new URL using speculative RenderFrameHost. + // TODO(https://crbug.com/1072817): Undo this plumbing after removing the + // early post-crash CommitPending() call. + void OnCommittedSpeculativeBeforeNavigationCommit() { + committed_speculative_rfh_before_navigation_commit_ = true; + } + // Returns the child RenderFrameHostImpl if |child_frame_routing_id| is an // immediate child of this FrameTreeNode. |child_frame_routing_id| is // considered untrusted, so the renderer process is killed if it refers to a @@ -1622,6 +1684,11 @@ class CONTENT_EXPORT RenderFrameHostImpl void OnCookiesAccessed( network::mojom::CookieAccessDetailsPtr details) override; + // mojom::FrameHost: + void OpenURL(mojom::OpenURLParamsPtr params) override; + + void GetSavableResourceLinksFromRenderer(); + protected: friend class RenderFrameHostFactory; @@ -1642,7 +1709,7 @@ class CONTENT_EXPORT RenderFrameHostImpl LifecycleState lifecycle_state); // The SendCommit* functions below are wrappers for commit calls - // made to mojom::FrameNavigationControl and mojom::NavigationClient. + // made to mojom::NavigationClient. // These exist to be overridden in tests to retain mojo callbacks. // Note: |navigation_id| is used in test overrides, but is unused otherwise. virtual void SendCommitNavigation( @@ -1659,7 +1726,7 @@ class CONTENT_EXPORT RenderFrameHostImpl subresource_overrides, blink::mojom::ControllerServiceWorkerInfoPtr controller_service_worker_info, - blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info, + blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info, mojo::PendingRemote<network::mojom::URLLoaderFactory> prefetch_loader_factory, const base::UnguessableToken& devtools_navigation_token); @@ -1782,15 +1849,10 @@ class CONTENT_EXPORT RenderFrameHostImpl // IPC Message handlers. void OnDetach(); - void OnOpenURL(const FrameHostMsg_OpenURL_Params& params); - void OnUpdateState(const PageState& state); void OnUnloadACK(); void OnContextMenu(const UntrustworthyContextMenuParams& params); void OnVisualStateResponse(uint64_t id); - void OnDidChangeOpener(int32_t opener_routing_id); - void OnDidChangeFramePolicy(int32_t frame_routing_id, - const blink::FramePolicy& frame_policy); void OnForwardResourceTimingToParent( const ResourceTimingInfo& resource_timing); void OnDidStopLoading(); @@ -1798,7 +1860,6 @@ class CONTENT_EXPORT RenderFrameHostImpl uint32_t offset, const gfx::Range& range); void OnSetNeedsOcclusionTracking(bool needs_tracking); - void OnFrameDidCallFocus(); void OnSaveImageFromDataURL(const std::string& url_str); // Computes the IsolationInfo for both navigations and subresources. @@ -1824,13 +1885,11 @@ class CONTENT_EXPORT RenderFrameHostImpl void AdoptPortal(const base::UnguessableToken& portal_token, AdoptPortalCallback callback) override; void CreateNewWidget( - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget, CreateNewWidgetCallback callback) override; void CreateNewFullscreenWidget( - mojo::PendingRemote<mojom::Widget> widget, mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host, mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget, @@ -1886,6 +1945,7 @@ class CONTENT_EXPORT RenderFrameHostImpl bool user_gesture) override; void RequestOverlayRoutingToken( RequestOverlayRoutingTokenCallback callback) override; + void UpdateState(const PageState& state) override; #if defined(OS_ANDROID) void UpdateUserGestureCarryoverInfo() override; #endif @@ -1963,7 +2023,8 @@ class CONTENT_EXPORT RenderFrameHostImpl const url::Origin& main_world_origin, network::mojom::ClientSecurityStatePtr client_security_state, mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> - coep_reporter); + coep_reporter, + network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy); // Creates a Network Service-backed factory from appropriate |NetworkContext| // and sets a connection error handler to trigger @@ -1995,11 +2056,14 @@ class CONTENT_EXPORT RenderFrameHostImpl // AXTreeData structure. void AXContentTreeDataToAXTreeData(ui::AXTreeData* dst); - // Callback that will be called as a response to the request to perform a hit - // test over the accessibility object at given point. - void RequestAXHitTestCallback( + // Callback in response to an accessibility hit test triggered by + // AccessibilityHitTest. + void AccessibilityHitTestCallback( int action_request_id, - mojom::ChildFrameHitTestInfoPtr child_frame_hit_test_result); + ax::mojom::Event event_to_fire, + base::OnceCallback<void(BrowserAccessibilityManager* hit_manager, + int hit_node_id)> opt_callback, + mojom::HitTestResponsePtr hit_test_response); // Callback that will be called as a response to the call to the method // content::mojom::RenderAccessibility::SnapshotAccessibilityTree(). The @@ -2008,6 +2072,12 @@ class CONTENT_EXPORT RenderFrameHostImpl void RequestAXTreeSnapshotCallback(AXTreeSnapshotCallback callback, const AXContentTreeUpdate& snapshot); + // Callback that will be called as a response to the call to the method + // blink::mojom::LocalFrame::GetSavableResourceLinks(). The |reply| passed + // will be a nullptr when the url is not the savable URLs or valid. + void GetSavableResourceLinksCallback( + blink::mojom::GetSavableResourceLinksReplyPtr reply); + // Returns the RenderWidgetHostView used for accessibility. For subframes, // this function will return the platform view on the main frame; for main // frames, it will return the current frame's view. @@ -2021,9 +2091,6 @@ class CONTENT_EXPORT RenderFrameHostImpl void DeleteWebBluetoothService( WebBluetoothServiceImpl* web_bluetooth_service); - // Callback for connection error on the media::mojom::InterfaceFactory client. - void OnMediaInterfaceFactoryConnectionError(); - #if !defined(OS_ANDROID) void BindAuthenticatorReceiver( mojo::PendingReceiver<blink::mojom::Authenticator> receiver); @@ -2106,22 +2173,21 @@ class CONTENT_EXPORT RenderFrameHostImpl // Update this frame's last committed origin. void SetLastCommittedOrigin(const url::Origin& origin); - // Set the |last_committed_origin_| and |isolation_info_| of |this| frame, - // inheriting the origin from |new_frame_creator| as appropriate (e.g. - // depending on whether |this| frame should be sandboxed / should have an - // opaque origin instead). - void SetOriginAndIsolationInfoOfNewFrame( - const url::Origin& new_frame_creator); - - // Called when a navigation commits succesfully to |url|. This will update - // |last_committed_site_url_| with the site URL corresponding to |url|. - // Note that this will recompute the site URL from |url| rather than using - // GetSiteInstance()->GetSiteURL(), so that |last_committed_site_url_| is + // Set the |last_committed_origin_|, |isolation_info_|, and |feature_policy_| + // of |this| frame, inheriting the origin from |new_frame_creator| as + // appropriate (e.g. depending on whether |this| frame should be sandboxed / + // should have an opaque origin instead). + void SetOriginDependentStateOfNewFrame(const url::Origin& new_frame_creator); + + // Called when a navigation commits successfully to |url|. This will update + // |last_committed_site_info_| with the SiteInfo corresponding to |url|. + // Note that this will recompute the SiteInfo from |url| rather than using + // GetSiteInstance()->GetSiteInfo(), so that |last_committed_site_info_| is // always meaningful: e.g., without site isolation, b.com could commit in a // SiteInstance for a.com, but this function will still compute the last - // committed site URL as b.com. For example, this can be used to track which + // committed SiteInfo as b.com. For example, this can be used to track which // sites have committed in which process. - void SetLastCommittedSiteUrl(const GURL& url); + void SetLastCommittedSiteInfo(const GURL& url); // Clears any existing policy and constructs a new policy for this frame, // based on its parent frame. @@ -2191,6 +2257,8 @@ class CONTENT_EXPORT RenderFrameHostImpl // Called by the renderer process when it is done processing a cross-document // commit request. + // TODO(https://crbug.com/1020175): this is only called with + // blink::mojom::CommitResult::Aborted. void OnCrossDocumentCommitProcessed(NavigationRequest* navigation_request, blink::mojom::CommitResult result); @@ -2204,7 +2272,8 @@ class CONTENT_EXPORT RenderFrameHostImpl CreateURLLoaderFactoriesForIsolatedWorlds( const url::Origin& main_world_origin, const base::flat_set<url::Origin>& isolated_world_origins, - network::mojom::ClientSecurityStatePtr client_security_state); + network::mojom::ClientSecurityStatePtr client_security_state, + network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy); // Based on the termination |status| and |exit_code|, may generate a crash // report to be routed to the Reporting API. @@ -2237,10 +2306,6 @@ class CONTENT_EXPORT RenderFrameHostImpl // immediately deletes the RenderFrameHost. void OnUnloadTimeout(); - // Update the frozen state of the frame applying current inputs (visibility, - // loaded state) to determine the new state. - void UpdateFrameFrozenState(); - // Runs interception set up in testing code, if any. // Returns true if we should proceed to the Commit callback, false otherwise. bool MaybeInterceptCommitCallback( @@ -2270,13 +2335,6 @@ class CONTENT_EXPORT RenderFrameHostImpl const std::string& message, bool discard_duplicates); - // Returns whether a cookie SameSite deprecation message should be sent for - // the given cookie url. - // TODO(crbug.com/977040): Remove when no longer needed. - bool ShouldAddCookieSameSiteDeprecationMessage( - const std::string& cookie_url, - base::circular_deque<size_t>* already_seen_url_hashes); - // Helper functions for logging crash keys when ValidateDidCommitParams() // determines it cannot commit a URL or origin. void LogCannotCommitUrlCrashKeys(const GURL& url, @@ -2319,6 +2377,13 @@ class CONTENT_EXPORT RenderFrameHostImpl mojo::PendingReceiver<blink::mojom::ReportingObserver> reporting_observer_receiver); + // Check the renderer provided sandbox flags matches with what the browser + // process computed on its own. This triggers DCHECK and DumpWithoutCrashing() + // + // TODO(https://crbug.com/1041376) Remove this when we are confident the value + // computed from the browser is always matching. + void CheckSandboxFlags(); + // The RenderViewHost that this RenderFrameHost is associated with. // // It is kept alive as long as any RenderFrameHosts or RenderFrameProxyHosts @@ -2381,9 +2446,9 @@ class CONTENT_EXPORT RenderFrameHostImpl network::CrossOriginOpenerPolicy cross_origin_opener_policy_; - // Track the site URL of the last site we committed successfully, as obtained - // from SiteInstance::GetSiteURL. - GURL last_committed_site_url_; + // Track the SiteInfo of the last site we committed successfully, as obtained + // from SiteInstanceImpl::GetSiteInfoForURL(). + SiteInfo last_committed_site_info_; // The most recent non-error URL to commit in this frame. // TODO(clamy): Remove this in favor of GetLastCommittedURL(). @@ -2414,6 +2479,11 @@ class CONTENT_EXPORT RenderFrameHostImpl // the renderer process. bool render_frame_created_; + // Tracks whether the RenderFrame has ever been created for this + // RenderFrameHost or not. This starts out as false, becomes true after the + // first call to SetRenderFrameCreated(true), and stays true thereafter. + bool was_render_frame_ever_created_ = false; + // When the last BeforeUnload message was sent. base::TimeTicks send_before_unload_start_time_; @@ -2588,6 +2658,12 @@ class CONTENT_EXPORT RenderFrameHostImpl // audio streams). bool is_audible_; + // If true, then the Virtual keyboard rectangle that occludes the content is + // sent to the VirtualKeyboard API where it fires overlaygeometrychange JS + // event notifying the web authors that Virtual keyboard has occluded the + // content. + bool should_virtual_keyboard_overlay_content_; + // Used for tracking the latest size of the RenderFrame. base::Optional<gfx::Size> frame_size_; @@ -2686,6 +2762,16 @@ class CONTENT_EXPORT RenderFrameHostImpl // deletion. network::mojom::WebSandboxFlags active_sandbox_flags_; + // Same as |active_sandbox_flags_|, except this is computed: + // - outside of the renderer process. + // - before loading the document. + // + // For now, this is simply used to double check this matches the renderer + // computation. Later this will be used as the source of truth. + // + // [OutOfBlinkSandbox](https://crbug.com/1041376) + base::Optional<network::mojom::WebSandboxFlags> active_sandbox_flags_control_; + // Tracks the document policy which has been set on this frame. std::unique_ptr<blink::DocumentPolicy> document_policy_; @@ -2756,7 +2842,6 @@ class CONTENT_EXPORT RenderFrameHostImpl const base::UnguessableToken frame_token_; viz::mojom::InputTargetClient* input_target_client_ = nullptr; - mojo::Remote<mojom::FrameInputHandler> frame_input_handler_; // Binding to remote implementation of mojom::RenderAccessibility. Note that // this binding is done on-demand (in UpdateAccessibilityMode()) and will only @@ -2866,20 +2951,6 @@ class CONTENT_EXPORT RenderFrameHostImpl // committed origin. net::IsolationInfo isolation_info_; - // Hold onto hashes of the last |kMaxCookieSameSiteDeprecationUrls| cookie - // URLs that we have seen since the last committed navigation, in order to - // partially deduplicate the corresponding cookie SameSite deprecation - // messages. - // TODO(crbug.com/977040): Remove when no longer needed. - base::circular_deque<size_t> cookie_no_samesite_deprecation_url_hashes_; - base::circular_deque<size_t> - cookie_samesite_none_insecure_deprecation_url_hashes_; - base::circular_deque<size_t> cookie_lax_allow_unsafe_deprecation_url_hashes_; - - // The lifecycle state of the frame. - blink::mojom::FrameLifecycleState frame_lifecycle_state_ = - blink::mojom::FrameLifecycleState::kRunning; - // The factory to load resources from the WebBundle source bound to // this file. std::unique_ptr<WebBundleHandle> web_bundle_handle_; @@ -2936,10 +3007,19 @@ class CONTENT_EXPORT RenderFrameHostImpl }; DocumentAssociatedData document_associated_data_; + // Keeps track of the scenario when RenderFrameHostManager::CommitPending is + // called before the navigation commits. This becomes true if the previous + // RenderFrameHost is not alive and the speculative RenderFrameHost is + // committed early (see RenderFrameHostManager::GetFrameHostForNavigation for + // more details). While |committed_speculative_rfh_before_navigation_commit_| + // is true the RenderFrameHost which we commit early will be live. + bool committed_speculative_rfh_before_navigation_commit_ = false; + // This time is used to record the last WebXR DOM Overlay setup request. base::TimeTicks last_xr_overlay_setup_time_; std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter_; + std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter_; // Navigation ID for the last committed cross-document non-bfcached navigation // in this RenderFrameHost. @@ -2954,7 +3034,11 @@ class CONTENT_EXPORT RenderFrameHostImpl // stuck in pending deletion. bool do_not_delete_for_testing_ = false; - // Embedding token for the document in this RenderFrameHost. + // Embedding token for the document in this RenderFrameHost. This differs from + // |frame_token_| in that |frame_token_| has a lifetime matching that of the + // corresponding RenderFrameHostImpl, and is intended to be used for IPCs for + // identifying frames just like routing IDs. |embedding_token_| has a document + // scoped lifetime and changes on cross-document navigations. base::Optional<base::UnguessableToken> embedding_token_; // Observers listening to cookie access notifications for the current document @@ -2967,12 +3051,21 @@ class CONTENT_EXPORT RenderFrameHostImpl // TODO(crbug.com/936696): Remove this warning after the RDH ships. mojo::ReceiverSet<network::mojom::CookieAccessObserver> cookie_observers_; + // Indicates whether this frame is an outer delegate frame for some other + // RenderFrameHost. + bool is_outer_delegate_frame_ = false; + // NOTE: This must be the last member. base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(RenderFrameHostImpl); }; +// Used when DCHECK_STATE_TRANSITION triggers. +CONTENT_EXPORT std::ostream& operator<<( + std::ostream& o, + const RenderFrameHostImpl::LifecycleState& s); + } // namespace content #endif // CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_IMPL_H_ diff --git a/chromium/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/chromium/content/browser/frame_host/render_frame_host_impl_browsertest.cc index fb399217074..089a0f08c96 100644 --- a/chromium/content/browser/frame_host/render_frame_host_impl_browsertest.cc +++ b/chromium/content/browser/frame_host/render_frame_host_impl_browsertest.cc @@ -71,7 +71,6 @@ #include "services/service_manager/public/cpp/interface_provider.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/mojom/browser_interface_broker.mojom-test-utils.h" -#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" #include "url/gurl.h" #include "url/origin.h" @@ -2434,17 +2433,10 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, // process. Currently, this is done by the renderer process, which commits an // empty document with success instead. EXPECT_TRUE(navigation_observer.has_committed()); - if (base::FeatureList::IsEnabled( - network::features::kOutOfBlinkFrameAncestors)) { EXPECT_TRUE(navigation_observer.is_error()); EXPECT_EQ(blocked_url, frame->GetLastCommittedURL()); EXPECT_EQ(net::ERR_BLOCKED_BY_RESPONSE, navigation_observer.net_error_code()); - } else { - EXPECT_FALSE(navigation_observer.is_error()); - EXPECT_EQ(GURL("data:,"), navigation_observer.last_committed_url()); - EXPECT_EQ(net::Error::OK, navigation_observer.net_error_code()); - } } IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, @@ -2866,39 +2858,6 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, // Pass if this didn't crash. } -void FileChooserCallback(base::RunLoop* run_loop, - blink::mojom::FileChooserResultPtr result) { - run_loop->Quit(); -} - -IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, - FileChooserAfterRfhDeath) { - EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); - auto* rfh = static_cast<RenderFrameHostImpl*>( - shell()->web_contents()->GetMainFrame()); - mojo::Remote<blink::mojom::FileChooser> chooser = - rfh->BindFileChooserForTesting(); - - // Kill the renderer process. - RenderProcessHostWatcher crash_observer( - rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); - rfh->GetProcess()->Shutdown(0); - crash_observer.Wait(); - - // Call FileChooser methods. The browser process should not crash. - base::RunLoop run_loop1; - chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(), - base::BindOnce(FileChooserCallback, &run_loop1)); - run_loop1.Run(); - - base::RunLoop run_loop2; - chooser->EnumerateChosenDirectory( - base::FilePath(), base::BindOnce(FileChooserCallback, &run_loop2)); - run_loop2.Run(); - - // Pass if this didn't crash. -} - // Verify that adding an <object> tag which resource is blocked by the network // stack does not result in terminating the renderer process. // See https://crbug.com/955777. @@ -2946,142 +2905,6 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, EXPECT_TRUE(crash_observer.did_exit_normally()); } -// Test deduplication of SameSite cookie deprecation messages. -// TODO(crbug.com/976475): This test is flaky. -IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, - DISABLED_DeduplicateSameSiteCookieDeprecationMessages) { -#if defined(OS_ANDROID) - // TODO(crbug.com/974701): This test is broken on Android that is - // Marshmallow or older. - if (base::android::BuildInfo::GetInstance()->sdk_int() <= - base::android::SDK_VERSION_MARSHMALLOW) { - return; - } -#endif // defined(OS_ANDROID) - - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(features::kCookieDeprecationMessages); - - WebContentsConsoleObserver console_observer(shell()->web_contents()); - - // Test deprecation messages for SameSiteByDefault. - // Set a cookie without SameSite on b.com, then access it in a cross-site - // context. - GURL url = - embedded_test_server()->GetURL("b.com", "/set-cookie?nosamesite=1"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - ASSERT_EQ(0u, console_observer.messages().size()); - url = embedded_test_server()->GetURL( - "a.com", "/cross_site_iframe_factory.html?a(b(),b())"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // Only 1 message even though there are 2 cross-site iframes. - EXPECT_EQ(1u, console_observer.messages().size()); - - // Test deprecation messages for CookiesWithoutSameSiteMustBeSecure. - // Set a cookie with SameSite=None but without Secure. - url = embedded_test_server()->GetURL( - "c.com", "/set-cookie?samesitenoneinsecure=1;SameSite=None"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // The 1 message from before, plus the (different) message for setting the - // SameSite=None insecure cookie. - EXPECT_EQ(2u, console_observer.messages().size()); - // Another copy of the message appears because we have navigated. - EXPECT_TRUE(NavigateToURL(shell(), url)); - EXPECT_EQ(3u, console_observer.messages().size()); - EXPECT_EQ(console_observer.messages()[1].message, - console_observer.messages()[2].message); -} - -// Enable SameSiteByDefaultCookies to test deprecation messages for -// Lax-allow-unsafe. -class RenderFrameHostImplSameSiteByDefaultCookiesBrowserTest - : public RenderFrameHostImplBrowserTest { - public: - void SetUp() override { - feature_list_.InitWithFeatures({features::kCookieDeprecationMessages, - net::features::kSameSiteByDefaultCookies}, - {}); - RenderFrameHostImplBrowserTest::SetUp(); - } - - private: - base::test::ScopedFeatureList feature_list_; -}; - -IN_PROC_BROWSER_TEST_F(RenderFrameHostImplSameSiteByDefaultCookiesBrowserTest, - DisplaySameSiteCookieDeprecationMessages) { - WebContentsConsoleObserver console_observer(shell()->web_contents()); - - // Test deprecation messages for SameSiteByDefault. - // Set a cookie without SameSite on b.com, then access it in a cross-site - // context. - base::Time set_cookie_time = base::Time::Now(); - GURL url = - embedded_test_server()->GetURL("x.com", "/set-cookie?nosamesite=1"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // Message does not appear in same-site context (main frame is x). - ASSERT_EQ(0u, console_observer.messages().size()); - url = embedded_test_server()->GetURL( - "a.com", "/cross_site_iframe_factory.html?a(x())"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // Message appears in cross-site context (a framing x). - EXPECT_EQ(1u, console_observer.messages().size()); - - // Test deprecation messages for CookiesWithoutSameSiteMustBeSecure. - // Set a cookie with SameSite=None but without Secure. - url = embedded_test_server()->GetURL( - "c.com", "/set-cookie?samesitenoneinsecure=1;SameSite=None"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // The 1 message from before, plus the (different) message for setting the - // SameSite=None insecure cookie. - EXPECT_EQ(2u, console_observer.messages().size()); - - // Test deprecation messages for Lax-allow-unsafe. - url = embedded_test_server()->GetURL("a.com", - "/form_that_posts_cross_site.html"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // Submit the form to make a cross-site POST request to x.com. - TestNavigationObserver form_post_observer(shell()->web_contents(), 1); - EXPECT_TRUE(ExecJs(shell(), "document.getElementById('text-form').submit()")); - form_post_observer.Wait(); - - // The test should not take more than 2 minutes. - ASSERT_LT(base::Time::Now() - set_cookie_time, net::kLaxAllowUnsafeMaxAge); - EXPECT_EQ(3u, console_observer.messages().size()); - - // Check that the messages were all distinct. - EXPECT_NE(console_observer.messages()[0].message, - console_observer.messages()[1].message); - EXPECT_NE(console_observer.messages()[0].message, - console_observer.messages()[2].message); - EXPECT_NE(console_observer.messages()[1].message, - console_observer.messages()[2].message); -} - -// Test that the SameSite-by-default console warnings are not emitted -// if the cookie would have been rejected for other reasons. -// Regression test for https://crbug.com/1027318. -IN_PROC_BROWSER_TEST_F(RenderFrameHostImplSameSiteByDefaultCookiesBrowserTest, - NoMessagesIfCookieWouldBeRejectedForOtherReasons) { - WebContentsConsoleObserver console_observer(shell()->web_contents()); - - GURL url = embedded_test_server()->GetURL( - "x.com", "/set-cookie?cookiewithpath=1;path=/set-cookie"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - url = embedded_test_server()->GetURL("sub.x.com", - "/set-cookie?cookieforsubdomain=1"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - - ASSERT_EQ(0u, console_observer.messages().size()); - url = embedded_test_server()->GetURL( - "a.com", "/cross_site_iframe_factory.html?a(x())"); - EXPECT_TRUE(NavigateToURL(shell(), url)); - // No messages appear even though x.com is accessed in a cross-site - // context, because the cookies would have been rejected for mismatching path - // or domain anyway. - EXPECT_EQ(0u, console_observer.messages().size()); -} - IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, SchedulerTrackedFeatures) { EXPECT_TRUE( @@ -3764,14 +3587,8 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, RenderFrameDeletedObserver delete_rfh_b(rfh_b); EXPECT_EQ(LifecycleState::kActive, rfh_b->lifecycle_state()); - // 2) Disable the unload timer to ensure that the unload timer doesn't fire - // before we call OnDetach(). Act as if there was a slow unload handler on - // rfh_b. The non navigating frames are waiting for FrameHostMsg_Detach. - rfh_b->DisableUnloadTimerForTesting(); - auto detach_filter = base::MakeRefCounted<DropMessageFilter>( - FrameMsgStart, FrameHostMsg_Detach::ID); - rfh_b->GetProcess()->AddFilter(detach_filter.get()); - EXPECT_TRUE(ExecuteScript(rfh_b->frame_tree_node(), onunload_script)); + // 2) Leave rfh_b in pending deletion state. + LeaveInPendingDeletionState(rfh_b); // 3) Check the IsCurrent state of rfh_a, rfh_b before navigating to C. EXPECT_TRUE(rfh_a->IsCurrent()); @@ -3788,8 +3605,9 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, EXPECT_FALSE(rfh_b->IsCurrent()); EXPECT_TRUE(rfh_c->IsCurrent()); - // 6) Run detach on rfh_b to delete its frame. + // 6) Resume deletion on rfh_b and run detach on rfh_b to delete its frame. EXPECT_FALSE(delete_rfh_b.deleted()); + rfh_b->ResumeDeletionForTesting(); rfh_b->OnDetach(); EXPECT_TRUE(delete_rfh_b.deleted()); } diff --git a/chromium/content/browser/frame_host/render_frame_host_impl_mac_browsertest.mm b/chromium/content/browser/frame_host/render_frame_host_impl_mac_browsertest.mm index 6c74902c88d..5bc2610df21 100644 --- a/chromium/content/browser/frame_host/render_frame_host_impl_mac_browsertest.mm +++ b/chromium/content/browser/frame_host/render_frame_host_impl_mac_browsertest.mm @@ -6,6 +6,7 @@ #include "base/mac/scoped_nsobject.h" #include "base/run_loop.h" +#include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/content_browser_test.h" @@ -33,8 +34,8 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserMacTest, [pboard setFindText:@"test"]; EXPECT_NSEQ(@"test", [pboard findText]); - auto* rfhi = static_cast<RenderFrameHostImpl*>(web_contents->GetMainFrame()); - auto* input_handler = rfhi->GetFrameInputHandler(); + auto* input_handler = static_cast<WebContentsImpl*>(web_contents) + ->GetFocusedFrameWidgetInputHandler(); input_handler->SelectAll(); input_handler->CopyToFindPboard(); diff --git a/chromium/content/browser/frame_host/render_frame_host_impl_unittest.cc b/chromium/content/browser/frame_host/render_frame_host_impl_unittest.cc index f35049157c9..174044481da 100644 --- a/chromium/content/browser/frame_host/render_frame_host_impl_unittest.cc +++ b/chromium/content/browser/frame_host/render_frame_host_impl_unittest.cc @@ -82,11 +82,7 @@ TEST_F(RenderFrameHostImplTest, ExpectedMainWorldOrigin) { // Create a full screen popup RenderWidgetHost and View. TEST_F(RenderFrameHostImplTest, CreateFullscreenWidget) { - mojo::PendingRemote<mojom::Widget> widget; - std::unique_ptr<MockWidgetImpl> widget_impl = - std::make_unique<MockWidgetImpl>(widget.InitWithNewPipeAndPassReceiver()); main_test_rfh()->CreateNewFullscreenWidget( - std::move(widget), mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost>(), mojo::PendingAssociatedRemote<blink::mojom::Widget>(), base::DoNothing()); } diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.cc b/chromium/content/browser/frame_host/render_frame_host_manager.cc index 73050ff98a7..72ecbed1a29 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.cc +++ b/chromium/content/browser/frame_host/render_frame_host_manager.cc @@ -35,6 +35,7 @@ #include "content/browser/frame_host/render_frame_host_factory.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/render_frame_proxy_host.h" +#include "content/browser/net/cross_origin_opener_policy_reporter.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/renderer_host/render_view_host_factory.h" #include "content/browser/renderer_host/render_view_host_impl.h" @@ -46,6 +47,7 @@ #include "content/common/page_messages.h" #include "content/common/unfreezable_frame_messages.h" #include "content/common/view_messages.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/render_process_host_observer.h" @@ -118,140 +120,6 @@ bool ShouldSwapBrowsingInstancesForDynamicIsolation( future_isolation_context, destination_effective_url); } -ShouldSwapBrowsingInstance ShouldProactivelySwapBrowsingInstance( - RenderFrameHostImpl* current_rfh, - const GURL& destination_effective_url) { - // Back-forward cache triggers proactive swap when the current url can be - // stored in the back-forward cache. - if (!IsProactivelySwapBrowsingInstanceEnabled() && - !IsBackForwardCacheEnabled()) - return ShouldSwapBrowsingInstance::kNo_ProactiveSwapDisabled; - - // Only main frames are eligible to swap BrowsingInstances. - if (!current_rfh->frame_tree_node()->IsMainFrame()) - return ShouldSwapBrowsingInstance::kNo_NotMainFrame; - - // Skip cases when there are other windows that might script this one. - SiteInstanceImpl* current_instance = current_rfh->GetSiteInstance(); - if (current_instance->GetRelatedActiveContentsCount() > 1u) - return ShouldSwapBrowsingInstance::kNo_HasRelatedActiveContents; - - // "about:blank" and chrome-native-URL do not "use" a SiteInstance. This - // allows the SiteInstance to be reused cross-site. Starting a new - // BrowsingInstance would prevent the SiteInstance to be reused, that's why - // this case is excluded here. - if (!current_instance->HasSite()) - return ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite; - - // Exclude non http(s) schemes. Some tests don't expect navigations to - // data-URL or to about:blank to switch to a different BrowsingInstance. - const GURL& current_url = current_rfh->GetLastCommittedURL(); - if (!current_url.SchemeIsHTTPOrHTTPS()) - return ShouldSwapBrowsingInstance::kNo_SourceURLSchemeIsNotHTTPOrHTTPS; - - if (!destination_effective_url.SchemeIsHTTPOrHTTPS()) - return ShouldSwapBrowsingInstance::kNo_DestinationURLSchemeIsNotHTTPOrHTTPS; - - // Nothing prevents two pages with the same website to live in different - // BrowsingInstance. However many tests are making this assumption. The scope - // of ProactivelySwapBrowsingInstance experiment doesn't include them. The - // cost of getting a new process on same-site navigation would (probably?) be - // too high. - if (SiteInstanceImpl::IsSameSite(current_instance->GetIsolationContext(), - current_url, destination_effective_url, - true)) { - return ShouldSwapBrowsingInstance::kNo_SameSiteNavigation; - } - - if (IsProactivelySwapBrowsingInstanceEnabled()) - return ShouldSwapBrowsingInstance::kYes_ProactiveSwap; - - // If BackForwardCache is enabled, swap BrowsingInstances only when needed - // for back-forward cache. - DCHECK(IsBackForwardCacheEnabled()); - NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( - current_rfh->frame_tree_node()->navigator()->GetController()); - if (controller->GetBackForwardCache().IsAllowed( - current_rfh->GetLastCommittedURL())) { - return ShouldSwapBrowsingInstance::kYes_ProactiveSwap; - } else { - return ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache; - } -} - -// This function implements the COOP matching algorithm as detailed in [1]. -// Note that COEP is also provided since the COOP enum does not have a -// "same-origin + COEP" value. -// [1] https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e -bool CrossOriginOpenerPolicyMatch( - network::mojom::CrossOriginOpenerPolicyValue initiator_coop, - network::mojom::CrossOriginEmbedderPolicyValue initiator_coep, - const url::Origin& initiator_origin, - network::mojom::CrossOriginOpenerPolicyValue destination_coop, - network::mojom::CrossOriginEmbedderPolicyValue destination_coep, - const url::Origin& destination_origin) { - if (initiator_coop != destination_coop) - return false; - if (initiator_coop == - network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone) { - return true; - } - if (initiator_coop == - network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin && - initiator_coep != destination_coep) { - return false; - } - if (!initiator_origin.IsSameOriginWith(destination_origin)) - return false; - return true; -} - -// This function returns whether the BrowsingInstance should change following -// COOP rules defined in: -// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e#changes-to-navigation -// TODO(ahemery): This should be a member of NavigationRequest. -bool ShouldSwapBrowsingInstanceForCrossOriginOpenerPolicy( - network::mojom::CrossOriginOpenerPolicyValue initiator_coop, - network::mojom::CrossOriginEmbedderPolicyValue initiator_coep, - const url::Origin& initiator_origin, - bool is_initiator_aboutblank, - network::mojom::CrossOriginOpenerPolicyValue destination_coop, - network::mojom::CrossOriginEmbedderPolicyValue destination_coep, - const url::Origin& destination_origin) { - using network::mojom::CrossOriginEmbedderPolicyValue; - using network::mojom::CrossOriginOpenerPolicyValue; - - if (!base::FeatureList::IsEnabled( - network::features::kCrossOriginOpenerPolicy)) - return false; - - // If policies match there is no reason to switch BrowsingInstances. - if (CrossOriginOpenerPolicyMatch(initiator_coop, initiator_coep, - initiator_origin, destination_coop, - destination_coep, destination_origin)) { - return false; - } - - // "same-origin-allow-popups" is used to stay in the same BrowsingInstance - // despite COOP mismatch. This case is defined in the spec [1] as follow. - // ``` - // If the result of matching currentCOOP, currentOrigin, potentialCOOP, and - // potentialOrigin is false and one of the following is false: - // - doc is the initial about:blank document - // - currentCOOP is "same-origin-allow-popups" - // - potentialCOOP is "unsafe-none" - // Then create a new browsing context group. - // ``` - // [1] - // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e#changes-to-navigation - if (is_initiator_aboutblank && - initiator_coop == CrossOriginOpenerPolicyValue::kSameOriginAllowPopups && - destination_coop == CrossOriginOpenerPolicyValue::kUnsafeNone) { - return false; - } - return true; -} - bool IsSiteInstanceCompatibleWithErrorIsolation(SiteInstance* site_instance, bool is_main_frame, bool is_failure) { @@ -266,7 +134,8 @@ bool IsSiteInstanceCompatibleWithErrorIsolation(SiteInstance* site_instance, // SiteInstance but the navigation will fail and actually need an error page // SiteInstance. bool is_site_instance_for_failures = - site_instance->GetSiteURL() == GURL(kUnreachableWebDataURL); + static_cast<SiteInstanceImpl*>(site_instance)->GetSiteInfo() == + SiteInfo::CreateForErrorPage(); return is_site_instance_for_failures == is_failure; } @@ -489,12 +358,12 @@ void RenderFrameHostManager::CommitPendingIfNecessary( } void RenderFrameHostManager::DidChangeOpener( - int opener_routing_id, + const base::UnguessableToken& opener_frame_token, SiteInstance* source_site_instance) { FrameTreeNode* opener = nullptr; - if (opener_routing_id != MSG_ROUTING_NONE) { - RenderFrameHostImpl* opener_rfhi = RenderFrameHostImpl::FromID( - source_site_instance->GetProcess()->GetID(), opener_routing_id); + if (opener_frame_token) { + RenderFrameHostImpl* opener_rfhi = RenderFrameHostImpl::FromFrameToken( + source_site_instance->GetProcess()->GetID(), opener_frame_token); // If |opener_rfhi| is null, the opener RFH has already disappeared. In // this case, clear the opener rather than keeping the old opener around. if (opener_rfhi) @@ -637,8 +506,9 @@ void RenderFrameHostManager::UnloadOldFrame( DeleteRenderFrameProxyHost(it.second->GetSiteInstance()); // Ensures RenderViewHosts are not reused while they are in the cache. - for (RenderViewHostImpl* rvh : old_render_view_hosts) + for (RenderViewHostImpl* rvh : old_render_view_hosts) { rvh->EnterBackForwardCache(); + } auto entry = std::make_unique<BackForwardCacheImpl::Entry>( std::move(old_render_frame_host), std::move(old_proxy_hosts), @@ -666,10 +536,10 @@ void RenderFrameHostManager::UnloadOldFrame( // |old_render_frame_host| will be deleted when its unload ACK is received, // or when the timer times out, or when the RFHM itself is deleted (whichever // comes first). - pending_delete_hosts_.push_back(std::move(old_render_frame_host)); - + auto insertion = + pending_delete_hosts_.insert(std::move(old_render_frame_host)); // Tell the old RenderFrameHost to swap out and be replaced by the proxy. - pending_delete_hosts_.back()->Unload(proxy, true); + (*insertion.first)->Unload(proxy, true); } void RenderFrameHostManager::DiscardUnusedFrame( @@ -721,14 +591,11 @@ void RenderFrameHostManager::DiscardUnusedFrame( bool RenderFrameHostManager::DeleteFromPendingList( RenderFrameHostImpl* render_frame_host) { - for (auto iter = pending_delete_hosts_.begin(); - iter != pending_delete_hosts_.end(); iter++) { - if (iter->get() == render_frame_host) { - pending_delete_hosts_.erase(iter); - return true; - } - } - return false; + auto it = pending_delete_hosts_.find(render_frame_host); + if (it == pending_delete_hosts_.end()) + return false; + pending_delete_hosts_.erase(it); + return true; } void RenderFrameHostManager::RestoreFromBackForwardCache( @@ -793,10 +660,10 @@ RenderFrameHostImpl* RenderFrameHostManager::GetFrameHostForNavigation( << "Don't call this method for JavaScript URLs as those create a " "temporary NavigationRequest and we don't want to reset an ongoing " "navigation's speculative RFH."; - // A frame that's pending deletion should never be navigated. If this happens, - // log a DumpWithoutCrashing to understand the root cause. - // See https://crbug.com/926820 and https://crbug.com/927705. - if (!current_frame_host()->is_active()) { + // Inactive frames should never be navigated. If this happens, log a + // DumpWithoutCrashing to understand the root cause. See + // https://crbug.com/926820 and https://crbug.com/927705. + if (current_frame_host()->IsInactiveAndDisallowReactivation()) { NOTREACHED() << "Navigation in an inactive frame"; DEBUG_ALIAS_FOR_GURL(url, request->common_params().url); base::debug::DumpWithoutCrashing(); @@ -820,23 +687,8 @@ RenderFrameHostImpl* RenderFrameHostManager::GetFrameHostForNavigation( // If a crashed RenderFrameHost must not be reused, replace it by a // new one immediately. - if (render_frame_host_->must_be_replaced()) { + if (render_frame_host_->must_be_replaced()) use_current_rfh = false; - // TODO(https://crbug.com/1006814): Remove this. - if (render_frame_host_->render_view_host() - ->GetWidget() - ->renderer_initialized()) { - static auto* crash_key = base::debug::AllocateCrashKeyString( - "IsRenderFrameLive", base::debug::CrashKeySize::Size32); - std::string message = base::StringPrintf( - "rdu=%d", IsRendererDebugURL(request->common_params().url)); - // This string is whitelisted for collection from Android Webview. It must - // only contain booleans to avoid leaking any PII. - base::debug::SetCrashKeyString(crash_key, message); - base::debug::DumpWithoutCrashing(); - NOTREACHED(); - } - } // Force using a different RenderFrameHost when RenderDocument is enabled. // TODO(arthursonzogni, fergal): Add support for the main frame. @@ -955,10 +807,13 @@ RenderFrameHostImpl* RenderFrameHostManager::GetFrameHostForNavigation( // done earlier to keep browser and renderer state in sync. This is // important to do before CommitPending(), which destroys the // corresponding proxy. See https://crbug.com/487872. + // TODO(https://crbug.com/1072817): Make this logic more robust to + // consider the case for failed navigations after CommitPending. if (GetRenderFrameProxyHost(dest_site_instance.get())) navigation_rfh->SwapIn(); + navigation_rfh->OnCommittedSpeculativeBeforeNavigationCommit(); CommitPending(std::move(speculative_render_frame_host_), nullptr, - request->require_coop_browsing_instance_swap()); + request->coop_status().require_browsing_instance_swap); } } DCHECK(navigation_rfh && @@ -1222,12 +1077,13 @@ void RenderFrameHostManager::TransferUserActivationFrom( for (const auto& pair : proxy_hosts_) { SiteInstance* site_instance = pair.second->GetSiteInstance(); if (site_instance != source_rfh->GetSiteInstance()) { - int32_t source_routing_id = + base::Optional<base::UnguessableToken> source_frame_token = source_rfh->frame_tree_node() ->render_manager() - ->GetRoutingIdForSiteInstance(site_instance); - pair.second->Send(new FrameMsg_TransferUserActivationFrom( - pair.second->GetRoutingID(), source_routing_id)); + ->GetFrameTokenForSiteInstance(site_instance); + DCHECK(source_frame_token.has_value()); + pair.second->GetAssociatedRemoteFrame()->TransferUserActivationToRenderer( + source_frame_token.value()); } } } @@ -1281,12 +1137,17 @@ RenderFrameHostManager::ShouldSwapBrowsingInstancesForNavigation( ui::PageTransition transition, bool is_failure, bool is_reload, + bool is_same_document, bool cross_origin_opener_policy_mismatch, - bool was_server_redirect) { + bool was_server_redirect, + bool should_replace_current_entry) { // A subframe must stay in the same BrowsingInstance as its parent. if (!frame_tree_node_->IsMainFrame()) return ShouldSwapBrowsingInstance::kNo_NotMainFrame; + if (is_same_document) + return ShouldSwapBrowsingInstance::kNo_SameDocumentNavigation; + // If this navigation is reloading an error page, do not swap BrowsingInstance // and keep the error page in a related SiteInstance. If later a reload of // this navigation is successful, it will correctly create a new @@ -1425,10 +1286,101 @@ RenderFrameHostManager::ShouldSwapBrowsingInstancesForNavigation( return ShouldSwapBrowsingInstance::kYes_ForceSwap; } - // Experimental mode to swap BrowsingInstances on most cross-site navigations - // when there are no other windows in the BrowsingInstance. - return ShouldProactivelySwapBrowsingInstance(render_frame_host_.get(), - destination_effective_url); + // Experimental mode to swap BrowsingInstances on most navigations when there + // are no other windows in the BrowsingInstance. + return ShouldProactivelySwapBrowsingInstance(destination_url, is_reload, + should_replace_current_entry); +} + +ShouldSwapBrowsingInstance +RenderFrameHostManager::ShouldProactivelySwapBrowsingInstance( + const GURL& destination_url, + bool is_reload, + bool should_replace_current_entry) { + // We should only do proactive swap when either the flag is enabled, or if + // it's needed for the back-forward cache (and the bfcache flag is enabled). + if (!IsProactivelySwapBrowsingInstanceEnabled() && + !IsBackForwardCacheEnabled()) + return ShouldSwapBrowsingInstance::kNo_ProactiveSwapDisabled; + + // Only main frames are eligible to swap BrowsingInstances. + if (!render_frame_host_->frame_tree_node()->IsMainFrame()) + return ShouldSwapBrowsingInstance::kNo_NotMainFrame; + + // Skip cases when there are other windows that might script this one. + SiteInstanceImpl* current_instance = render_frame_host_->GetSiteInstance(); + if (current_instance->GetRelatedActiveContentsCount() > 1u) + return ShouldSwapBrowsingInstance::kNo_HasRelatedActiveContents; + + // "about:blank" and chrome-native-URL do not "use" a SiteInstance. This + // allows the SiteInstance to be reused cross-site. Starting a new + // BrowsingInstance would prevent the SiteInstance to be reused, that's why + // this case is excluded here. + if (!current_instance->HasSite()) + return ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite; + + // Exclude non http(s) schemes. Some tests don't expect navigations to + // data-URL or to about:blank to switch to a different BrowsingInstance. + const GURL& current_url = render_frame_host_->GetLastCommittedURL(); + if (!current_url.SchemeIsHTTPOrHTTPS()) + return ShouldSwapBrowsingInstance::kNo_SourceURLSchemeIsNotHTTPOrHTTPS; + + const GURL& destination_effective_url = SiteInstanceImpl::GetEffectiveURL( + current_instance->GetBrowserContext(), destination_url); + if (!destination_effective_url.SchemeIsHTTPOrHTTPS()) + return ShouldSwapBrowsingInstance::kNo_DestinationURLSchemeIsNotHTTPOrHTTPS; + + // WebView guests currently need to stay in the same SiteInstance and + // BrowsingInstance. + if (current_instance->IsGuest()) + return ShouldSwapBrowsingInstance::kNo_Guest; + + // We should check whether the new page will result in adding a new history + // entry or not. If not, we should not do a proactive BrowsingInstance swap, + // because these navigations are not interesting for bfcache (the old page + // will not get into the bfcache). Cases include: + // 1) When we know we're going to replace the history entry. + if (should_replace_current_entry) + return ShouldSwapBrowsingInstance::kNo_WillReplaceEntry; + // Navigations where we will reuse the history entry: + // 2) Different-document but same-page navigations. These navigations are + // not classified as same-document (which got filtered earlier) so they will + // use a different document, but they will later on be classified as + // SAME_PAGE and will reuse the history entry. + // TODO(crbug.com/536102): When the SAME_PAGE navigation type gets removed, + // we should remove this part as well. + bool is_same_page = current_url.EqualsIgnoringRef(destination_url); + if (is_same_page) + return ShouldSwapBrowsingInstance::kNo_SamePageNavigation; + // 3) Reloads. Note that most reloads will not actually reach this part, as + // ShouldSwapBrowsingInstancesForNavigation will return early if the reload + // has a destination SiteInstance. Reloads that don't have a destination + // SiteInstance include: doing reload after a replaceState call, reloading a + // URL for which we've just installed a hosted app, and duplicating a tab. + if (is_reload) + return ShouldSwapBrowsingInstance::kNo_Reload; + + if (IsCurrentlySameSite( + static_cast<RenderFrameHostImpl*>(render_frame_host_.get()), + destination_url)) { + if (IsProactivelySwapBrowsingInstanceOnSameSiteNavigationEnabled()) + return ShouldSwapBrowsingInstance::kYes_SameSiteProactiveSwap; + return ShouldSwapBrowsingInstance::kNo_SameSiteNavigation; + } + + if (IsProactivelySwapBrowsingInstanceEnabled()) + return ShouldSwapBrowsingInstance::kYes_CrossSiteProactiveSwap; + + // If BackForwardCache is enabled, swap BrowsingInstances only when needed + // for back-forward cache. + DCHECK(IsBackForwardCacheEnabled()); + NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( + render_frame_host_->frame_tree_node()->navigator().GetController()); + if (controller->GetBackForwardCache().IsAllowed(current_url)) { + return ShouldSwapBrowsingInstance::kYes_CrossSiteProactiveSwap; + } else { + return ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache; + } } scoped_refptr<SiteInstance> @@ -1440,10 +1392,12 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( ui::PageTransition transition, bool is_failure, bool is_reload, + bool is_same_document, bool dest_is_restore, bool dest_is_view_source_mode, bool was_server_redirect, - bool cross_origin_opener_policy_mismatch) { + bool cross_origin_opener_policy_mismatch, + bool should_replace_current_entry) { // On renderer-initiated navigations, when the frame initiating the navigation // and the frame being navigated differ, |source_instance| is set to the // SiteInstance of the initiating frame. |dest_instance| is present on session @@ -1476,7 +1430,7 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( !render_frame_host_->last_successful_url().is_empty() ? SiteInstanceImpl::GetEffectiveURL( browser_context, render_frame_host_->last_successful_url()) - : render_frame_host_->GetSiteInstance()->GetSiteURL(); + : render_frame_host_->GetSiteInstance()->GetSiteInfo().site_url(); // Determine if the current RenderFrameHost is in view source mode. // TODO(clamy): If the current_effective_url doesn't match the last committed @@ -1495,9 +1449,13 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( current_effective_url, current_is_view_source_mode, source_instance, static_cast<SiteInstanceImpl*>(current_instance), dest_instance, dest_url, dest_is_view_source_mode, transition, is_failure, is_reload, - cross_origin_opener_policy_mismatch, was_server_redirect); + is_same_document, cross_origin_opener_policy_mismatch, + was_server_redirect, should_replace_current_entry); bool proactive_swap = - (should_swap_result == ShouldSwapBrowsingInstance::kYes_ProactiveSwap); + (should_swap_result == + ShouldSwapBrowsingInstance::kYes_CrossSiteProactiveSwap || + should_swap_result == + ShouldSwapBrowsingInstance::kYes_SameSiteProactiveSwap); bool should_swap = (should_swap_result == ShouldSwapBrowsingInstance::kYes_ForceSwap) || proactive_swap; @@ -1553,11 +1511,18 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( REUSE_PENDING_OR_COMMITTED_SITE); } } - + bool is_same_site_proactive_swap = + (should_swap_result == + ShouldSwapBrowsingInstance::kYes_SameSiteProactiveSwap); // If we're doing a proactive BI swap, we should try to reuse the current // SiteInstance's process for the new SiteInstance if possible. + // It might not be possible to reuse the process in some cases, including when + // the current SiteInstance needs a dedicated process (unless this is a + // same-site navigation). if (IsProactivelySwapBrowsingInstanceWithProcessReuseEnabled() && - proactive_swap && !current_instance->RequiresDedicatedProcess()) { + proactive_swap && + (!current_instance->RequiresDedicatedProcess() || + is_same_site_proactive_swap)) { DCHECK(frame_tree_node_->IsMainFrame()); new_instance_impl->ReuseCurrentProcessIfPossible( current_instance->GetProcess()); @@ -1565,11 +1530,12 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( return new_instance; } -bool RenderFrameHostManager::InitializeRenderFrameForImmediateUse() { +bool RenderFrameHostManager::InitializeMainRenderFrameForImmediateUse() { // TODO(jam): this copies some logic inside GetFrameHostForNavigation, which // also duplicates logic in Navigate. They should all use this method, but // that involves slight reordering. // http://crbug.com/794229 + DCHECK(frame_tree_node_->IsMainFrame()); if (render_frame_host_->IsRenderFrameLive()) return true; @@ -1590,10 +1556,8 @@ bool RenderFrameHostManager::InitializeRenderFrameForImmediateUse() { // RenderFrameHostManager are completely initialized. This should be // removed once the process manager moves away from NotificationService. // See https://crbug.com/462682. - if (frame_tree_node_->IsMainFrame()) { - delegate_->NotifyMainFrameSwappedFromRenderManager( - nullptr, render_frame_host_.get()); - } + delegate_->NotifyMainFrameSwappedFromRenderManager(nullptr, + render_frame_host_.get()); return true; } @@ -1716,11 +1680,11 @@ RenderFrameHostManager::DetermineSiteInstanceForURL( // thus use the correct process. DCHECK_EQ(controller.GetBrowserContext(), current_instance_impl->GetBrowserContext()); - const GURL dest_site_url = SiteInstanceImpl::GetSiteForURL( + const SiteInfo dest_site_info = SiteInstanceImpl::ComputeSiteInfo( current_instance_impl->GetIsolationContext(), dest_url); bool use_process_per_site = - RenderProcessHost::ShouldUseProcessPerSite( - current_instance_impl->GetBrowserContext(), dest_site_url) && + RenderProcessHostImpl::ShouldUseProcessPerSite( + current_instance_impl->GetBrowserContext(), dest_site_info) && RenderProcessHostImpl::GetSoleProcessHostForURL( current_instance_impl->GetIsolationContext(), dest_url); if (current_instance_impl->HasRelatedSiteInstance(dest_url) || @@ -1799,11 +1763,14 @@ RenderFrameHostManager::DetermineSiteInstanceForURL( } // Keep subframes in the parent's SiteInstance unless a dedicated process is - // required for either the parent or the subframe's destination URL. This - // isn't a strict invariant but rather a heuristic to avoid unnecessary - // OOPIFs; see https://crbug.com/711006. - // - // TODO(alexmos): Remove this check after fixing https://crbug.com/787576. + // required for either the parent or the subframe's destination URL. Although + // this consolidation is usually handled by default SiteInstances, there are + // some corner cases in which default SiteInstances cannot currently be used, + // such as file: URLs. This logic prevents unneeded OOPIFs in those cases. + // This turns out to be important for correctness on Android Webview, which + // does not yet support OOPIFs (https://crbug.com/1101214). + // TODO(https://crbug.com/1103352): Remove this block when default + // SiteInstances support file: URLs. // // Also if kProcessSharingWithStrictSiteInstances is enabled, don't lump the // subframe into the same SiteInstance as the parent. These separate @@ -2188,9 +2155,6 @@ RenderFrameHostManager::CreateSpeculativeRenderFrame(SiteInstance* instance) { RenderViewHostImpl* render_view_host = new_render_frame_host->render_view_host(); - // TODO(https://crbug.com/1006814): Remove this. - bool recreated_main_frame = false; - bool widget_renderer_initialized = false; if (frame_tree_node_->IsMainFrame()) { if (render_view_host == render_frame_host_->render_view_host()) { // We are replacing the main frame's host with |new_render_frame_host|. @@ -2200,9 +2164,6 @@ RenderFrameHostManager::CreateSpeculativeRenderFrame(SiteInstance* instance) { // GetFrameHostForNavigation() before yielding to other tasks. render_view_host->SetMainFrameRoutingId( new_render_frame_host->GetRoutingID()); - recreated_main_frame = true; - widget_renderer_initialized = - render_view_host->GetWidget()->renderer_initialized(); } if (!InitRenderView(render_view_host, GetRenderFrameProxyHost(instance))) @@ -2219,32 +2180,6 @@ RenderFrameHostManager::CreateSpeculativeRenderFrame(SiteInstance* instance) { } DCHECK(render_view_host->IsRenderViewLive()); - // TODO(https://crbug.com/1006814): Remove this. - if (recreated_main_frame && !new_render_frame_host->IsRenderFrameLive()) { - static auto* crash_key = base::debug::AllocateCrashKeyString( - "IsRenderFrameLive", base::debug::CrashKeySize::Size256); - int main_rfh_routing_id = - render_view_host->GetMainFrameRoutingIdForCrbug1006814(); - std::string message = base::StringPrintf( - "created=%d,process=%d,proxy=%d,widget=%d,main_rfh=%d,new_rfh=%d," - "in_pdo=%d,in_cpdo=%d,wi=%d", - new_render_frame_host->IsRenderFrameCreated(), - new_render_frame_host->GetProcess()->IsInitializedAndNotDead(), - !!GetRenderFrameProxyHost(instance), widget_renderer_initialized, - main_rfh_routing_id, new_render_frame_host->routing_id(), - static_cast<RenderProcessHostImpl*>( - render_view_host->GetWidget()->GetProcess()) - ->GetWithinProcessDiedObserverForCrbug1006814(), - static_cast<RenderProcessHostImpl*>( - render_view_host->GetWidget()->GetProcess()) - ->GetWithinCleanupProcessDiedObserverForCrbug1006814(), - render_view_host->GetWidget()->get_initializer_for_crbug_1006814()); - // This string is whitelisted for collection from Android Webview. It must - // only contain booleans to avoid leaking any PII. - base::debug::SetCrashKeyString(crash_key, message); - base::debug::DumpWithoutCrashing(); - NOTREACHED() << message; - } // RenderViewHost for |instance| might exist prior to calling // CreateRenderFrame. In such a case, InitRenderView will not create the // RenderFrame in the renderer process and it needs to be done @@ -2373,18 +2308,12 @@ bool RenderFrameHostManager::InitRenderView( if (render_view_host->IsRenderViewLive()) return true; - int opener_frame_routing_id = - GetOpenerRoutingID(render_view_host->GetSiteInstance()); + auto opener_frame_token = + GetOpenerFrameToken(render_view_host->GetSiteInstance()); bool created = delegate_->CreateRenderViewForRenderManager( - render_view_host, opener_frame_routing_id, - proxy ? proxy->GetRoutingID() : MSG_ROUTING_NONE, - proxy - ? proxy->GetFrameToken() - : static_cast<RenderFrameHostImpl*>(render_view_host->GetMainFrame()) - ->frame_token(), - frame_tree_node_->devtools_frame_token(), - frame_tree_node_->current_replication_state()); + render_view_host, opener_frame_token, + proxy ? proxy->GetRoutingID() : MSG_ROUTING_NONE); if (created && proxy) { proxy->SetRenderFrameProxyCreated(true); @@ -2436,50 +2365,15 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( request->common_params().navigation_type == mojom::NavigationType::RELOAD_ORIGINAL_REQUEST_URL; - // Retrieve COOP and COEP from the response headers. If we don't have the - // headers yet we try to inherit the current page COOP/COEP to have a - // relevant speculative RFH. - network::mojom::CrossOriginOpenerPolicyValue coop; - network::mojom::CrossOriginEmbedderPolicyValue coep; - if (auto* response = request->response()) { - coop = response->parsed_headers->cross_origin_opener_policy.value; - coep = response->parsed_headers->cross_origin_embedder_policy.value; - } else { - // The heuristic for inheriting is to have the most conservative approach - // towards BrowsingInstance switching. Every same-origin navigation should - // yield a no swap decision. This is done to work with the renderer crash - // optimization that instantly commits the speculative RenderFrameHost. - bool inherit_coop = - render_frame_host_->has_committed_any_navigation() || - render_frame_host_->cross_origin_opener_policy().value == - network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin; - coop = inherit_coop - ? render_frame_host_->cross_origin_opener_policy().value - : network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone; - coep = render_frame_host_->cross_origin_embedder_policy().value; - } - - bool cross_origin_policy_swap = - frame_tree_node_->IsMainFrame() && - !request->common_params().url.IsAboutBlank() && - ShouldSwapBrowsingInstanceForCrossOriginOpenerPolicy( - render_frame_host_->cross_origin_opener_policy().value, - render_frame_host_->cross_origin_embedder_policy().value, - render_frame_host_->GetLastCommittedOrigin(), - !render_frame_host_->has_committed_any_navigation(), coop, coep, - url::Origin::Create(request->common_params().url)); - - if (cross_origin_policy_swap) - request->set_require_coop_browsing_instance_swap(); - scoped_refptr<SiteInstance> dest_site_instance = GetSiteInstanceForNavigation( request->common_params().url, request->GetSourceSiteInstance(), request->dest_site_instance(), candidate_site_instance, request->common_params().transition, request->state() >= NavigationRequest::CANCELING, is_reload, - request->GetRestoreType() != RestoreType::NONE, request->is_view_source(), - request->WasServerRedirect(), - request->require_coop_browsing_instance_swap()); + request->IsSameDocument(), request->GetRestoreType() != RestoreType::NONE, + request->is_view_source(), request->WasServerRedirect(), + request->coop_status().require_browsing_instance_swap, + request->common_params().should_replace_current_entry); // If the NavigationRequest's dest_site_instance was present but incorrect, // then ensure no sensitive state is kept on the request. This can happen for @@ -2499,9 +2393,9 @@ bool RenderFrameHostManager::InitRenderFrame( SiteInstance* site_instance = render_frame_host->GetSiteInstance(); - int opener_routing_id = MSG_ROUTING_NONE; + base::Optional<base::UnguessableToken> opener_frame_token; if (frame_tree_node_->opener()) - opener_routing_id = GetOpenerRoutingID(site_instance); + opener_frame_token = GetOpenerFrameToken(site_instance); int parent_routing_id = MSG_ROUTING_NONE; if (frame_tree_node_->parent()) { @@ -2548,7 +2442,7 @@ bool RenderFrameHostManager::InitRenderFrame( } return delegate_->CreateRenderFrameForRenderManager( - render_frame_host, previous_routing_id, opener_routing_id, + render_frame_host, previous_routing_id, opener_frame_token, parent_routing_id, previous_sibling_routing_id); } @@ -2614,6 +2508,19 @@ int RenderFrameHostManager::GetRoutingIdForSiteInstance( return MSG_ROUTING_NONE; } +base::Optional<base::UnguessableToken> +RenderFrameHostManager::GetFrameTokenForSiteInstance( + SiteInstance* site_instance) { + if (render_frame_host_->GetSiteInstance() == site_instance) + return render_frame_host_->GetFrameToken(); + + RenderFrameProxyHost* proxy = GetRenderFrameProxyHost(site_instance); + if (proxy) + return proxy->GetFrameToken(); + + return base::nullopt; +} + void RenderFrameHostManager::CommitPending( std::unique_ptr<RenderFrameHostImpl> pending_rfh, std::unique_ptr<BackForwardCacheImpl::Entry> pending_bfcache_entry, @@ -3004,11 +2911,10 @@ void RenderFrameHostManager::CreateOpenerProxies( if (!proxy) continue; - int opener_routing_id = - node->render_manager()->GetOpenerRoutingID(instance); - DCHECK_NE(opener_routing_id, MSG_ROUTING_NONE); - proxy->Send( - new FrameMsg_UpdateOpener(proxy->GetRoutingID(), opener_routing_id)); + auto opener_frame_token = + node->render_manager()->GetOpenerFrameToken(instance); + DCHECK(opener_frame_token); + proxy->GetAssociatedRemoteFrame()->UpdateOpener(opener_frame_token); } } @@ -3030,13 +2936,14 @@ void RenderFrameHostManager::CreateOpenerProxiesForFrameTree( frame_tree->CreateProxiesForSiteInstance(skip_this_node, instance); } -int RenderFrameHostManager::GetOpenerRoutingID(SiteInstance* instance) { +base::Optional<base::UnguessableToken> +RenderFrameHostManager::GetOpenerFrameToken(SiteInstance* instance) { if (!frame_tree_node_->opener()) - return MSG_ROUTING_NONE; + return base::nullopt; return frame_tree_node_->opener() ->render_manager() - ->GetRoutingIdForSiteInstance(instance); + ->GetFrameTokenForSiteInstance(instance); } void RenderFrameHostManager::SendPageMessage(IPC::Message* msg, @@ -3166,8 +3073,8 @@ void RenderFrameHostManager::NotifyPrepareForInnerDelegateAttachComplete( int32_t routing_id = success ? render_frame_host_->GetRoutingID() : MSG_ROUTING_NONE; // Invoking the callback asynchronously to meet the APIs promise. - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce( [](RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback, int32_t process_id, int32_t routing_id) { diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.h b/chromium/content/browser/frame_host/render_frame_host_manager.h index a7fa84f147d..9d03c5a3f42 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.h +++ b/chromium/content/browser/frame_host/render_frame_host_manager.h @@ -10,12 +10,14 @@ #include <list> #include <map> #include <memory> +#include <set> #include <unordered_map> #include <unordered_set> -#include "base/logging.h" +#include "base/containers/unique_ptr_adapters.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" #include "content/browser/frame_host/back_forward_cache_impl.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/should_swap_browsing_instance.h" @@ -31,6 +33,7 @@ #include "url/origin.h" namespace content { +class FrameTree; class FrameTreeNode; class NavigationControllerImpl; class NavigationEntry; @@ -42,7 +45,6 @@ class RenderViewHost; class RenderViewHostImpl; class RenderWidgetHostView; class TestWebContents; -struct FrameReplicationState; // Manages RenderFrameHosts for a FrameTreeNode. It maintains a // current_frame_host() which is the content currently visible to the user. When @@ -116,17 +118,14 @@ class CONTENT_EXPORT RenderFrameHostManager // automatically called from LoadURL. virtual bool CreateRenderViewForRenderManager( RenderViewHost* render_view_host, - int opener_frame_routing_id, - int proxy_routing_id, - const base::UnguessableToken& frame_token, - const base::UnguessableToken& devtools_frame_token, - const FrameReplicationState& replicated_frame_state) = 0; + const base::Optional<base::UnguessableToken>& opener_frame_token, + int proxy_routing_id) = 0; virtual void CreateRenderWidgetHostViewForRenderManager( RenderViewHost* render_view_host) = 0; virtual bool CreateRenderFrameForRenderManager( RenderFrameHost* render_frame_host, int proxy_routing_id, - int opener_routing_id, + const base::Optional<base::UnguessableToken>& opener_frame_token, int parent_routing_id, int previous_sibling_routing_id) = 0; virtual void BeforeUnloadFiredFromRenderManager( @@ -259,12 +258,12 @@ class CONTENT_EXPORT RenderFrameHostManager const blink::FramePolicy& frame_policy); // Called when this frame's opener is changed to the frame specified by - // |opener_routing_id| in |source_site_instance|'s process. This change + // |opener_frame_token| in |source_site_instance|'s process. This change // could come from either the current RenderFrameHost or one of the // proxies (e.g., window.open that targets a RemoteFrame by name). The // updated opener will be forwarded to any other RenderFrameProxies and // RenderFrames for this FrameTreeNode. - void DidChangeOpener(int opener_routing_id, + void DidChangeOpener(const base::UnguessableToken& opener_frame_token, SiteInstance* source_site_instance); // Creates and initializes a RenderFrameHost. @@ -305,6 +304,12 @@ class CONTENT_EXPORT RenderFrameHostManager // RenderFrameHostManager. Returns MSG_ROUTING_NONE if none is found. int GetRoutingIdForSiteInstance(SiteInstance* site_instance); + // Returns the frame token for a RenderFrameHost or RenderFrameProxyHost + // that has the given SiteInstance and is associated with this + // RenderFrameHostManager. Returns base::nullopt if none is found. + base::Optional<base::UnguessableToken> GetFrameTokenForSiteInstance( + SiteInstance* site_instance); + // Notifies the RenderFrameHostManager that a new NavigationRequest has been // created and set in the FrameTreeNode so that it can speculatively create a // new RenderFrameHost (and potentially a new process) if needed. @@ -396,12 +401,13 @@ class CONTENT_EXPORT RenderFrameHostManager // https://crbug.com/511474. void CreateProxiesForNewNamedFrame(); - // Returns a routing ID for the current FrameTreeNode's opener node in the - // given SiteInstance. May return a routing ID of either a RenderFrameHost - // (if opener's current or pending RFH has SiteInstance |instance|) or a - // RenderFrameProxyHost. Returns MSG_ROUTING_NONE if there is no opener, or - // if the opener node doesn't have a proxy for |instance|. - int GetOpenerRoutingID(SiteInstance* instance); + // Returns a base::UnguessableToken for the current FrameTreeNode's opener + // node in the given SiteInstance. May return a frame token of either a + // RenderFrameHost (if opener's current or pending RFH has SiteInstance + // |instance|) or a RenderFrameProxyHost. Returns base::nullopt if there is + // no opener, or if the opener node doesn't have a proxy for |instance|. + base::Optional<base::UnguessableToken> GetOpenerFrameToken( + SiteInstance* instance); // Called on the RFHM of the inner WebContents to create a // RenderFrameProxyHost in its outer WebContents's SiteInstance, @@ -482,11 +488,11 @@ class CONTENT_EXPORT RenderFrameHostManager scoped_refptr<SiteInstance> GetSiteInstanceForNavigationRequest( NavigationRequest* navigation_request); - // Helper to initialize the current RenderFrame if it's not initialized. - // TODO(https://crbug.com/1006814): Remove this. For now debug URLs and + // Helper to initialize the main RenderFrame if it's not initialized. + // TODO(https://crbug.com/936696): Remove this. For now debug URLs and // WebView JS execution are an exception to replacing all crashed frames for // RenderDocument. This is a no-op if the frame is already initialized. - bool InitializeRenderFrameForImmediateUse(); + bool InitializeMainRenderFrameForImmediateUse(); // Prepares the FrameTreeNode for attaching an inner WebContents. This step // may involve replacing |current_frame_host()| with a new RenderFrameHost @@ -599,8 +605,15 @@ class CONTENT_EXPORT RenderFrameHostManager ui::PageTransition transition, bool is_failure, bool is_reload, + bool is_same_document, bool cross_origin_opener_policy_mismatch, - bool was_server_redirect); + bool was_server_redirect, + bool should_replace_current_entry); + + ShouldSwapBrowsingInstance ShouldProactivelySwapBrowsingInstance( + const GURL& destination_url, + bool is_reload, + bool should_replace_current_entry); // Returns the SiteInstance to use for the navigation. scoped_refptr<SiteInstance> GetSiteInstanceForNavigation( @@ -611,10 +624,12 @@ class CONTENT_EXPORT RenderFrameHostManager ui::PageTransition transition, bool is_failure, bool is_reload, + bool is_same_document, bool dest_is_restore, bool dest_is_view_source_mode, bool was_server_redirect, - bool cross_origin_opener_policy_mismatch); + bool cross_origin_opener_policy_mismatch, + bool should_replace_current_entry); // Returns a descriptor of the appropriate SiteInstance object for the given // |dest_url|, possibly reusing the current, source or destination @@ -821,9 +836,10 @@ class CONTENT_EXPORT RenderFrameHostManager // Proxy hosts, indexed by site instance ID. RenderFrameProxyHostMap proxy_hosts_; - // A list of RenderFrameHosts waiting to shut down after swapping out. - using RFHPendingDeleteList = std::list<std::unique_ptr<RenderFrameHostImpl>>; - RFHPendingDeleteList pending_delete_hosts_; + // A set of RenderFrameHosts waiting to shut down after swapping out. + using RFHPendingDeleteSet = + std::set<std::unique_ptr<RenderFrameHostImpl>, base::UniquePtrComparator>; + RFHPendingDeleteSet pending_delete_hosts_; // Stores a speculative RenderFrameHost which is created early in a navigation // so a renderer process can be started in parallel, if needed. diff --git a/chromium/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/chromium/content/browser/frame_host/render_frame_host_manager_browsertest.cc index b0028e5be49..8be9a86361d 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager_browsertest.cc +++ b/chromium/content/browser/frame_host/render_frame_host_manager_browsertest.cc @@ -5660,7 +5660,7 @@ IN_PROC_BROWSER_TEST_P( static_cast<SiteInstanceImpl*>( web_contents->GetMainFrame()->GetSiteInstance()); - // Check that A and B are in different SiteInstances (both are in default + // Check that A and B are in different BrowsingInstances (both are in default // SiteInstances of different BrowsingInstances) but have the same renderer // process. EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); @@ -5695,7 +5695,7 @@ IN_PROC_BROWSER_TEST_P( static_cast<SiteInstanceImpl*>( web_contents->GetMainFrame()->GetSiteInstance()); - // Check that A and B are in different SiteInstances (both are in default + // Check that A and B are in different BrowsingInstances (both are in default // SiteInstances of different BrowsingInstances) and renderer processes. EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance()); @@ -5755,7 +5755,7 @@ IN_PROC_BROWSER_TEST_P( static_cast<SiteInstanceImpl*>( web_contents->GetMainFrame()->GetSiteInstance()); - // Check that A and B are in different SiteInstances (both are in default + // Check that A and B are in different BrowsingInstances (both are in default // SiteInstances of different BrowsingInstances) but have the same renderer // process. EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); @@ -5775,7 +5775,7 @@ IN_PROC_BROWSER_TEST_P( static_cast<SiteInstanceImpl*>( web_contents->GetMainFrame()->GetSiteInstance()); - // Check that B and C are in different SiteInstances (both are in default + // Check that B and C are in different BrowsingInstances (both are in default // SiteInstances of different BrowsingInstances) and renderer processes. EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(c_site_instance.get())); EXPECT_TRUE(c_site_instance->IsDefaultSiteInstance()); @@ -5852,7 +5852,7 @@ IN_PROC_BROWSER_TEST_P( static_cast<SiteInstanceImpl*>( web_contents->GetMainFrame()->GetSiteInstance()); - // Check that A and B are in different SiteInstances (both are in default + // Check that A and B are in different BrowsingInstances (both are in default // SiteInstances of different BrowsingInstances) but B should use the sole // process assigned to site B. EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); @@ -5868,6 +5868,529 @@ IN_PROC_BROWSER_TEST_P( SetBrowserClientForTesting(old_client); } +// We should not reuse the current process on renderer-initiated navigations to +// sites that require a dedicated process. +IN_PROC_BROWSER_TEST_P( + ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest, + NavigationToSiteThatRequiresDedicatedProcess) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); + + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + // The client will make sure b.com require a dedicated process. + EffectiveURLContentBrowserClient modified_client( + b_url /* url_to_modify */, b_url, true /* requires_dedicated_process */); + ContentBrowserClient* old_client = + SetBrowserClientForTesting(&modified_client); + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), a_url)); + scoped_refptr<SiteInstanceImpl> a_site_instance = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_FALSE(a_site_instance->RequiresDedicatedProcess()); + + // 2) Navigate cross-site to B. The navigation is document/renderer initiated. + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url)); + scoped_refptr<SiteInstanceImpl> b_site_instance = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_TRUE(b_site_instance->RequiresDedicatedProcess()); + + // Check that A and B are in different BrowsingInstances and processes. + EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); + EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess()); + SetBrowserClientForTesting(old_client); +} + +// We should not reuse the current process on renderer-initiated navigations to +// sites that require a dedicated process. +IN_PROC_BROWSER_TEST_P( + ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest, + NavigationFromSiteThatRequiresDedicatedProcess) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); + + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + // The client will make sure a.com require a dedicated process. + EffectiveURLContentBrowserClient modified_client( + a_url /* url_to_modify */, a_url, true /* requires_dedicated_process */); + ContentBrowserClient* old_client = + SetBrowserClientForTesting(&modified_client); + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), a_url)); + scoped_refptr<SiteInstanceImpl> a_site_instance = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_TRUE(a_site_instance->RequiresDedicatedProcess()); + + // 2) Navigate cross-site to B. The navigation is document/renderer initiated. + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url)); + scoped_refptr<SiteInstanceImpl> b_site_instance = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_FALSE(b_site_instance->RequiresDedicatedProcess()); + + // Check that A and B are in different BrowsingInstances and processes. + EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get())); + EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess()); + SetBrowserClientForTesting(old_client); +} + +class ProactivelySwapBrowsingInstancesSameSiteTest + : public RenderFrameHostManagerTest { + public: + ProactivelySwapBrowsingInstancesSameSiteTest() { + std::map<std::string, std::string> parameters; + parameters[kProactivelySwapBrowsingInstanceLevelParameterName] = "SameSite"; + feature_list_.InitAndEnableFeatureWithParameters( + features::kProactivelySwapBrowsingInstance, parameters); + } + + ~ProactivelySwapBrowsingInstancesSameSiteTest() override = default; + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + RendererInitiatedSameSiteNavigationReusesProcess) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Navigate to title2.html. The navigation is document/renderer initiated. + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that title1.html and title2.html are in different BrowsingInstances + // but have the same renderer process. + EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); + EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); +} + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + BrowserInitiatedSameSiteNavigationReusesProcess) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // 2) Navigate to title2.html. The navigation is browser initiated. + EXPECT_TRUE(NavigateToURL(shell(), url_2)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that title1.html and title2.html are in different BrowsingInstances + // but have the same renderer process. + EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); + EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); + + // 3) Do a back navigation to title1.html. + shell()->web_contents()->GetController().GoBack(); + EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); + EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1); + scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We will reuse the SiteInstance and renderer process of |site_instance_1|. + EXPECT_EQ(site_instance_1_history_nav, site_instance_1); + EXPECT_EQ(site_instance_1_history_nav->GetProcess(), + site_instance_1->GetProcess()); +} + +// If the navigation is classified as NAVIGATION_TYPE_SAME_PAGE, or is a same +// document navigation, we should not do a proactive BrowsingInstance swap. +// TODO(crbug.com/536102): NAVIGATION_TYPE_SAME_PAGE will be removed in the +// future, so we should update this test. +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + SamePageAndSameDocumentNavigationDoesNotSwap) { + ASSERT_TRUE(embedded_test_server()->Start()); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate to title1.html#foo. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("/title1.html#foo"))); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Navigate from title1.html#foo to title1.html. + // This is a different-document, same-page navigation. + EXPECT_TRUE( + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that #1 and #2 are in the same SiteInstance. + EXPECT_EQ(site_instance_1, site_instance_2); + + // 3) Navigate from title1.html to title1.html. + // This is a different-document, same-page navigation. + EXPECT_TRUE( + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); + scoped_refptr<SiteInstanceImpl> site_instance_3 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We should keep the same SiteInstance again. + EXPECT_EQ(site_instance_2, site_instance_3); + + // 4) Navigate from title1.html to title1.html#foo. + // This is a same document navigation. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("/title1.html#foo"))); + scoped_refptr<SiteInstanceImpl> site_instance_4 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We should keep the same SiteInstance again. + EXPECT_EQ(site_instance_3, site_instance_4); + + // 5) Navigate from title1.html#foo to title1.html#foo. + // This is a different-document, same page navigation. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("/title1.html#foo"))); + scoped_refptr<SiteInstanceImpl> site_instance_5 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We should keep the same SiteInstance again. + EXPECT_EQ(site_instance_4, site_instance_5); + + // 6) Navigate from title1.html#foo to title1.html#bar. + // This is a same document navigation. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("/title1.html#bar"))); + scoped_refptr<SiteInstanceImpl> site_instance_6 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We should keep the same SiteInstance again. + EXPECT_EQ(site_instance_5, site_instance_6); + + // 7) Do a history navigation from title1.html#bar to title1.html#foo. + // This is a different-document, same-page history navigation. + shell()->web_contents()->GetController().GoBack(); + EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); + scoped_refptr<SiteInstanceImpl> site_instance_7 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // We should keep the same SiteInstance again. + EXPECT_EQ(site_instance_6, site_instance_7); +} + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + ReloadDoesNotSwap) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url(embedded_test_server()->GetURL("/title1.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + FrameTreeNode* root = web_contents->GetFrameTree()->root(); + + // 1) Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Request a reload to happen when the controller becomes active (e.g. + // after the renderer gets killed in background on Android). + NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( + shell()->web_contents()->GetController()); + ASSERT_FALSE(controller.NeedsReload()); + controller.SetNeedsReload(); + ASSERT_TRUE(controller.NeedsReload()); + + // Set the controller as active, triggering the requested reload. + controller.SetActive(true); + EXPECT_TRUE(WaitForLoadStop(web_contents)); + ASSERT_FALSE(controller.NeedsReload()); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Check that we're still in the same SiteInstance. + EXPECT_EQ(site_instance_1, site_instance_2); + + // 3) Trigger reload using Reload(). + { + TestNavigationObserver reload_observer(shell()->web_contents()); + shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false); + reload_observer.Wait(); + EXPECT_TRUE(reload_observer.last_navigation_succeeded()); + } + scoped_refptr<SiteInstanceImpl> site_instance_3 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Check that we're still in the same SiteInstance. + EXPECT_EQ(site_instance_2, site_instance_3); + + // 4) Trigger reload using location.reload(). + { + TestNavigationObserver reload_observer(shell()->web_contents()); + EXPECT_TRUE(ExecuteScript(shell(), "location.reload();")); + reload_observer.Wait(); + EXPECT_TRUE(reload_observer.last_navigation_succeeded()); + } + scoped_refptr<SiteInstanceImpl> site_instance_4 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Check that we're still in the same SiteInstance. + EXPECT_EQ(site_instance_3, site_instance_4); + + // 5) Do a replaceState to another URL. + { + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + TestNavigationObserver observer(web_contents); + std::string script = + "history.replaceState({}, '', '/" + url_2.spec() + "')"; + EXPECT_TRUE(ExecJs(root, script)); + observer.Wait(); + } + scoped_refptr<SiteInstanceImpl> site_instance_5 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Check that we're still in the same SiteInstance. + EXPECT_EQ(site_instance_4, site_instance_5); + + // 6) Reload after a replaceState by simulating the user hitting Enter in the + // omnibox without changing the URL. + { + TestNavigationObserver observer(web_contents); + web_contents->GetController().LoadURL(web_contents->GetLastCommittedURL(), + Referrer(), ui::PAGE_TRANSITION_LINK, + std::string()); + observer.Wait(); + } + scoped_refptr<SiteInstanceImpl> site_instance_6 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // Check that we're still in the same SiteInstance. + EXPECT_EQ(site_instance_5, site_instance_6); +} + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + SwapOnNavigationToPageThatRedirects) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + // This is a same-site URL, and will redirect to another same-site URL. + GURL same_site_redirector_url( + embedded_test_server()->GetURL("/server-redirect?" + url_2.spec())); + GURL url_3(embedded_test_server()->GetURL("/title3.html")); + // This is a cross-site URL, but will redirect to a same-site URL. + GURL cross_site_redirector_url(embedded_test_server()->GetURL( + "b.com", "/server-redirect?" + url_3.spec())); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Go to a same-site URL that will redirect us same-site to /title2.html. + EXPECT_TRUE(NavigateToURL(shell(), same_site_redirector_url, + url_2 /* expected_commit_url */)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that we are using a different BrowsingInstance but still using the + // same renderer process. + EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); + EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); + + // 3) Go to a cross-site URL that will redirect us same-site to /title3.html. + // Note that we're using a renderer-initiated navigation here. If we do a + // browser-initiated navigation, it will hit the case at crbug.com/1094147 + // where we can't reuse |url_2|'s process even though |url_3| is same-site. + // TODO(crbug.com/1094147): Test with browser-initiated navigation too once + // the issue is fixed. + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_redirector_url, + url_3 /* expected_commit_url */)); + scoped_refptr<SiteInstanceImpl> site_instance_3 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that we are using a different BrowsingInstance but still using the + // same renderer process. + EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get())); + EXPECT_EQ(site_instance_2->GetProcess(), site_instance_3->GetProcess()); +} + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + DoNotSwapWhenReplacingHistoryEntry) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Do a location.replace() to title2.html. + { + TestNavigationObserver navigation_observer(shell()->web_contents(), 1); + EXPECT_TRUE( + ExecJs(shell(), JsReplace("window.location.replace($1)", url_2))); + navigation_observer.Wait(); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2); + } + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_EQ(site_instance_1, site_instance_2); +} + +// When we do a same-document navigation from A to A#foo then a navigation that +// does replacement (e.g., cross-process reload, or location.replace, or other +// client redirects) such that B takes the place of A#foo, we can go back to A +// with the back navigation. In this case, we might want to do a proactive BI +// swap so that page A can be bfcached. +// However, this test is currently disabled because we won't swap on any +// navigation that will replace the current history entry. +// TODO(rakina): Support this case. +IN_PROC_BROWSER_TEST_P( + ProactivelySwapBrowsingInstancesSameSiteTest, + DISABLED_ShouldSwapWhenReplacingEntryWithSameDocumentPreviousEntry) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_1_anchor(embedded_test_server()->GetURL("/title1.html#foo")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate to title1.html. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Navigate same-document to title1.html#foo. + EXPECT_TRUE(NavigateToURL(shell(), url_1_anchor)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_EQ(site_instance_1, site_instance_2); + + // 3) Do a location.replace() to title2.html. + { + TestNavigationObserver navigation_observer(web_contents, 1); + EXPECT_TRUE( + ExecJs(shell(), JsReplace("window.location.replace($1)", url_2))); + navigation_observer.Wait(); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + EXPECT_EQ(web_contents->GetLastCommittedURL(), url_2); + } + scoped_refptr<SiteInstanceImpl> site_instance_3 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + // We should swap BrowsingInstance here so that the page at url_1 (which is + // now the previous history entry) can be bfcached. + EXPECT_NE(site_instance_2, site_instance_3); + + // Assert that a back navigation will go to |url_1|. + { + TestNavigationObserver navigation_observer(web_contents); + web_contents->GetController().GoBack(); + navigation_observer.Wait(); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + EXPECT_EQ(web_contents->GetLastCommittedURL(), url_1); + } +} + +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + DoNotSwapWhenRelatedContentsPresent) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + // 1) Navigate and open a new window. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + OpenPopup(shell(), url_1, "foo"); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // 2) Navigate to title2.html. + EXPECT_TRUE(NavigateToURL(shell(), url_2)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + + // Check that title1.html and title2.html are using the same SiteInstance. + EXPECT_EQ(site_instance_1, site_instance_2); +} + +// We should reuse the current process on same-site navigations even if the +// site requires a dedicated process (because we are still in the same site). +IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest, + NavigationToSiteThatRequiresDedicatedProcess) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL url_1(embedded_test_server()->GetURL("/title1.html")); + GURL url_2(embedded_test_server()->GetURL("/title2.html")); + + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + // Make sure the site require a dedicated process. + EffectiveURLContentBrowserClient modified_client( + url_1 /* url_to_modify */, url_1, /* requires_dedicated_process */ true); + ContentBrowserClient* old_client = + SetBrowserClientForTesting(&modified_client); + + // 1) Navigate to A. + EXPECT_TRUE(NavigateToURL(shell(), url_1)); + scoped_refptr<SiteInstanceImpl> site_instance_1 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_TRUE(site_instance_1->RequiresDedicatedProcess()); + + // 2) Navigate cross-site to B. The navigation is document/renderer initiated. + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2)); + scoped_refptr<SiteInstanceImpl> site_instance_2 = + static_cast<SiteInstanceImpl*>( + web_contents->GetMainFrame()->GetSiteInstance()); + EXPECT_TRUE(site_instance_2->RequiresDedicatedProcess()); + + // Check that A and B are in different BrowsingInstances but reuse the same + // process. + EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); + EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); + SetBrowserClientForTesting(old_client); +} + // Helper class to simplify testing of unload handlers. It allows waiting for // particular HTTP requests to be made to the embedded_test_server(); the tests // use this to wait for termination pings (e.g., navigator.sendBeacon()) made @@ -6879,6 +7402,9 @@ IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, // anything without SiteIsolation. if (!AreAllSitesIsolatedForTesting()) return; + // TODO(https://crbug.com/1064944): Fix this test and remove this. + if (CreateNewHostForSameSiteSubframe()) + return; // 1. Navigate to A1(B2, B3(B4), C5). StartEmbeddedServer(); @@ -7076,6 +7602,9 @@ INSTANTIATE_TEST_SUITE_P( ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, + ProactivelySwapBrowsingInstancesSameSiteTest, + testing::ValuesIn(RenderDocumentFeatureLevelValues())); +INSTANTIATE_TEST_SUITE_P(All, RenderFrameHostManagerUnloadBrowserTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, diff --git a/chromium/content/browser/frame_host/render_frame_host_manager_unittest.cc b/chromium/content/browser/frame_host/render_frame_host_manager_unittest.cc index 7c72abc9193..d7388d6997f 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager_unittest.cc +++ b/chromium/content/browser/frame_host/render_frame_host_manager_unittest.cc @@ -251,6 +251,7 @@ class PluginFaviconMessageObserver : public WebContentsObserver { } void DidUpdateFaviconURL( + RenderFrameHost* render_frame_host, const std::vector<blink::mojom::FaviconURLPtr>& candidates) override { favicon_received_ = true; } @@ -265,6 +266,16 @@ class PluginFaviconMessageObserver : public WebContentsObserver { DISALLOW_COPY_AND_ASSIGN(PluginFaviconMessageObserver); }; +// A shorter version for RenderFrameHostManager::DidNavigateFrame(rfh, ...). +// This provides all the arguments that aren't tested in this file. +void DidNavigateFrame(RenderFrameHostManager* rfh_manager, + RenderFrameHostImpl* rfh) { + rfh_manager->DidNavigateFrame(rfh, true /* was_caused_by_user_gesture */, + false /* is_same_document_navigation */, + false /* clear_proxies_on_commit */, + blink::FramePolicy()); +} + } // namespace // Test that the "level" feature param has the expected effect. @@ -377,7 +388,7 @@ class RenderFrameHostManagerTest static_cast<NavigationControllerImpl*>(manager->current_frame_host() ->frame_tree_node() ->navigator() - ->GetController()); + .GetController()); mojom::NavigationType navigate_type = entry->restore_type() == RestoreType::NONE ? mojom::NavigationType::DIFFERENT_DOCUMENT @@ -899,10 +910,7 @@ TEST_P(RenderFrameHostManagerTest, Navigate) { EXPECT_FALSE(GetPendingFrameHost(manager)); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); // Commit to SiteInstance should be delayed until RenderFrame commit. EXPECT_TRUE(host == manager->current_frame_host()); ASSERT_TRUE(host); @@ -927,10 +935,7 @@ TEST_P(RenderFrameHostManagerTest, Navigate) { EXPECT_FALSE(GetPendingFrameHost(manager)); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); EXPECT_TRUE(host == manager->current_frame_host()); ASSERT_TRUE(host); EXPECT_TRUE(host->GetSiteInstance()->HasSite()); @@ -955,10 +960,7 @@ TEST_P(RenderFrameHostManagerTest, Navigate) { change_observer.Reset(); // Commit. - manager->DidNavigateFrame( - GetPendingFrameHost(manager), true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, blink::FramePolicy()); + DidNavigateFrame(manager, GetPendingFrameHost(manager)); EXPECT_TRUE(host == manager->current_frame_host()); ASSERT_TRUE(host); EXPECT_TRUE(host->GetSiteInstance()->HasSite()); @@ -1015,10 +1017,7 @@ TEST_P(RenderFrameHostManagerTest, WebUI) { EXPECT_TRUE(manager->current_frame_host()->web_ui()); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); EXPECT_TRUE(host->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); } @@ -1035,9 +1034,8 @@ TEST_P(RenderFrameHostManagerTest, WebUIInNewTab) { RenderFrameHostManager* manager1 = web_contents1->GetRenderManagerForTesting(); // Test the case that new RVH is considered live. - manager1->current_host()->CreateRenderView( - -1, MSG_ROUTING_NONE, base::UnguessableToken::Create(), - base::UnguessableToken::Create(), FrameReplicationState(), false); + manager1->current_host()->CreateRenderView(base::nullopt, MSG_ROUTING_NONE, + false); EXPECT_TRUE(manager1->current_host()->IsRenderViewLive()); EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive()); @@ -1056,10 +1054,7 @@ TEST_P(RenderFrameHostManagerTest, WebUIInNewTab) { EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); // Commit and ensure we still have bindings. - manager1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager1, host1); SiteInstance* webui_instance = host1->GetSiteInstance(); EXPECT_EQ(host1, manager1->current_frame_host()); EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); @@ -1071,9 +1066,8 @@ TEST_P(RenderFrameHostManagerTest, WebUIInNewTab) { web_contents2->GetRenderManagerForTesting(); // Make sure the new RVH is considered live. This is usually done in // RenderWidgetHost::Init when opening a new tab from a link. - manager2->current_host()->CreateRenderView( - -1, MSG_ROUTING_NONE, base::UnguessableToken::Create(), - base::UnguessableToken::Create(), FrameReplicationState(), false); + manager2->current_host()->CreateRenderView(base::nullopt, MSG_ROUTING_NONE, + false); EXPECT_TRUE(manager2->current_host()->IsRenderViewLive()); const GURL kUrl2(GetWebUIURL("foo/bar")); @@ -1091,10 +1085,7 @@ TEST_P(RenderFrameHostManagerTest, WebUIInNewTab) { EXPECT_TRUE(host2->web_ui()); EXPECT_TRUE(host2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); - manager2->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager2, host2); } // Tests that a WebUI is correctly reused between chrome:// pages. @@ -1264,7 +1255,7 @@ TEST_P(RenderFrameHostManagerTest, DisownOpener) { EXPECT_NE(site_instance1, rfh2->GetSiteInstance()); // Disown the opener from rfh2. - rfh2->DidChangeOpener(MSG_ROUTING_NONE); + rfh2->SimulateDidChangeOpener(base::UnguessableToken()); // Ensure the opener is cleared. EXPECT_FALSE(contents()->HasOpener()); @@ -1285,7 +1276,7 @@ TEST_P(RenderFrameHostManagerTest, DisownSameSiteOpener) { EXPECT_TRUE(contents()->HasOpener()); // Disown the opener from rfh1. - rfh1->DidChangeOpener(MSG_ROUTING_NONE); + rfh1->SimulateDidChangeOpener(base::UnguessableToken()); // Ensure the opener is cleared even if it is in the same process. EXPECT_FALSE(contents()->HasOpener()); @@ -1321,7 +1312,7 @@ TEST_P(RenderFrameHostManagerTest, DisownOpenerDuringNavigation) { contents()->GetMainFrame()->PrepareForCommit(); // Disown the opener from rfh2. - rfh2->DidChangeOpener(MSG_ROUTING_NONE); + rfh2->SimulateDidChangeOpener(base::UnguessableToken()); // Ensure the opener is cleared. EXPECT_FALSE(contents()->HasOpener()); @@ -1370,7 +1361,7 @@ TEST_P(RenderFrameHostManagerTest, DisownOpenerAfterNavigation) { entry1->GetTransitionType()); // Disown the opener from rfh2. - rfh2->DidChangeOpener(MSG_ROUTING_NONE); + rfh2->SimulateDidChangeOpener(base::UnguessableToken()); EXPECT_FALSE(contents()->HasOpener()); } @@ -1392,9 +1383,8 @@ TEST_P(RenderFrameHostManagerTest, CleanUpProxiesOnProcessCrash) { contents()->SetOpener(opener1.get()); // Make sure the new opener RVH is considered live. - opener1_manager->current_host()->CreateRenderView( - -1, MSG_ROUTING_NONE, base::UnguessableToken::Create(), - base::UnguessableToken::Create(), FrameReplicationState(), false); + opener1_manager->current_host()->CreateRenderView(base::nullopt, + MSG_ROUTING_NONE, false); EXPECT_TRUE(opener1_manager->current_host()->IsRenderViewLive()); EXPECT_TRUE(opener1_manager->current_frame_host()->IsRenderFrameLive()); @@ -1431,8 +1421,8 @@ TEST_P(RenderFrameHostManagerTest, CleanUpProxiesOnProcessCrash) { ->GetRenderViewHost() ->IsRenderViewLive()); EXPECT_EQ( - opener1_manager->GetRoutingIdForSiteInstance(rfh2->GetSiteInstance()), - rfh2->GetRenderViewHost()->opener_frame_route_id()); + opener1_manager->GetFrameTokenForSiteInstance(rfh2->GetSiteInstance()), + rfh2->GetRenderViewHost()->opener_frame_token()); } // Test that we reuse the same guest SiteInstance if we navigate across sites. @@ -1467,10 +1457,7 @@ TEST_P(RenderFrameHostManagerTest, NoSwapOnGuestNavigations) { EXPECT_EQ(manager->current_frame_host()->GetSiteInstance(), instance); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); // Commit to SiteInstance should be delayed until RenderFrame commit. EXPECT_EQ(host, manager->current_frame_host()); ASSERT_TRUE(host); @@ -1493,10 +1480,7 @@ TEST_P(RenderFrameHostManagerTest, NoSwapOnGuestNavigations) { EXPECT_FALSE(manager->speculative_frame_host()); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); EXPECT_EQ(host, manager->current_frame_host()); ASSERT_TRUE(host); EXPECT_EQ(host->GetSiteInstance(), instance); @@ -1551,10 +1535,7 @@ TEST_P(RenderFrameHostManagerTest, NavigateWithEarlyClose) { EXPECT_TRUE(change_observer.DidHostChange()); // Commit. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); // Commit to SiteInstance should be delayed until RenderFrame commits. EXPECT_EQ(host, manager->current_frame_host()); @@ -1866,10 +1847,7 @@ TEST_P(RenderFrameHostManagerTestWithSiteIsolation, DetachPendingChild) { EXPECT_FALSE(GetPendingFrameHost(iframe1)); // Commit. - iframe1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(iframe1, host1); // Commit to SiteInstance should be delayed until RenderFrame commit. EXPECT_TRUE(host1 == iframe1->current_frame_host()); ASSERT_TRUE(host1); @@ -2019,10 +1997,7 @@ TEST_P(RenderFrameHostManagerTestWithSiteIsolation, base::string16() /* title */, ui::PAGE_TRANSITION_LINK, false /* is_renderer_init */, nullptr /* blob_url_loader_factory */); RenderFrameHostImpl* cross_site = NavigateToEntry(iframe, &entry); - iframe->DidNavigateFrame(cross_site, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(iframe, cross_site); // A proxy to the iframe should now exist in the SiteInstance of the main // frames. @@ -2092,9 +2067,55 @@ TEST_P(RenderFrameHostManagerTestWithSiteIsolation, EXPECT_FALSE(bar_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); } +// This class intercepts RenderFrameProxyHost creations, and overrides their +// respective blink::mojom::RemoteFrame instances, so that it can watch the +// updates of opener frames. +class UpdateOpenerProxyObserver { + public: + UpdateOpenerProxyObserver() { + RenderFrameProxyHost::SetCreatedCallbackForTesting(base::BindRepeating( + &UpdateOpenerProxyObserver::RenderFrameProxyHostCreatedCallback, + base::Unretained(this))); + } + ~UpdateOpenerProxyObserver() { + RenderFrameProxyHost::SetCreatedCallbackForTesting( + RenderFrameProxyHost::CreatedCallback()); + } + base::Optional<base::UnguessableToken> OpenerFrameToken( + RenderFrameProxyHost* proxy) { + return remote_frames_[proxy]->opener_frame_token(); + } + + private: + class Remote : public content::FakeRemoteFrame { + public: + explicit Remote(RenderFrameProxyHost* proxy) { + Init(proxy->GetRemoteAssociatedInterfacesTesting()); + } + void UpdateOpener( + const base::Optional<base::UnguessableToken>& frame_token) override { + frame_token_ = frame_token; + } + base::Optional<base::UnguessableToken> opener_frame_token() { + return frame_token_; + } + + private: + base::Optional<base::UnguessableToken> frame_token_; + }; + + void RenderFrameProxyHostCreatedCallback(RenderFrameProxyHost* proxy_host) { + remote_frames_[proxy_host] = std::make_unique<Remote>(proxy_host); + } + + std::map<RenderFrameProxyHost*, std::unique_ptr<Remote>> remote_frames_; +}; + // Test that opener proxies are created properly with a cycle on the opener // chain. TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWithCycleOnOpenerChain) { + UpdateOpenerProxyObserver proxy_observers; + const GURL kUrl1("http://www.google.com/"); const GURL kUrl2 = isolated_cross_site_url(); @@ -2138,28 +2159,26 @@ TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWithCycleOnOpenerChain) { EXPECT_TRUE(tab2_proxy); // Verify that the proxies' openers point to each other. - int tab1_opener_routing_id = - tab1_manager->GetOpenerRoutingID(rfh2->GetSiteInstance()); - int tab2_opener_routing_id = - tab2_manager->GetOpenerRoutingID(rfh2->GetSiteInstance()); - EXPECT_EQ(tab2_proxy->GetRoutingID(), tab1_opener_routing_id); - EXPECT_EQ(tab1_proxy->GetRoutingID(), tab2_opener_routing_id); + auto tab1_opener_frame_token = + tab1_manager->GetOpenerFrameToken(rfh2->GetSiteInstance()); + auto tab2_opener_frame_token = + tab2_manager->GetOpenerFrameToken(rfh2->GetSiteInstance()); + EXPECT_EQ(tab2_proxy->GetFrameToken(), tab1_opener_frame_token); + EXPECT_EQ(tab1_proxy->GetFrameToken(), tab2_opener_frame_token); // Setting tab2_proxy's opener required an extra IPC message to be set, since - // the opener's routing ID wasn't available when tab2_proxy was created. - // Verify that this IPC was sent and that it passed correct routing ID. - const IPC::Message* message = - rfh2->GetProcess()->sink().GetUniqueMessageMatching( - FrameMsg_UpdateOpener::ID); - EXPECT_TRUE(message); - FrameMsg_UpdateOpener::Param params; - EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, ¶ms)); - EXPECT_EQ(tab2_opener_routing_id, std::get<0>(params)); + // the opener's frame token wasn't available when tab2_proxy was created. + // Verify that this IPC was sent and that it passed correct frame token. + base::RunLoop().RunUntilIdle(); + DCHECK(proxy_observers.OpenerFrameToken(tab2_proxy) == + tab2_manager->GetOpenerFrameToken(rfh2->GetSiteInstance())); } // Test that opener proxies are created properly when the opener points // to itself. TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWhenOpenerPointsToSelf) { + UpdateOpenerProxyObserver proxy_observers; + const GURL kUrl1("http://www.google.com/"); const GURL kUrl2 = isolated_cross_site_url(); @@ -2190,20 +2209,16 @@ TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWhenOpenerPointsToSelf) { EXPECT_TRUE(opener_proxy); // Verify that the proxy's opener points to itself. - int opener_routing_id = - opener_manager->GetOpenerRoutingID(rfh2->GetSiteInstance()); - EXPECT_EQ(opener_proxy->GetRoutingID(), opener_routing_id); + auto opener_frame_token = + opener_manager->GetOpenerFrameToken(rfh2->GetSiteInstance()); + EXPECT_EQ(opener_proxy->GetFrameToken(), opener_frame_token); // Setting the opener in opener_proxy required an extra IPC message, since - // the opener's routing ID wasn't available when opener_proxy was created. - // Verify that this IPC was sent and that it passed correct routing ID. - const IPC::Message* message = - rfh2->GetProcess()->sink().GetUniqueMessageMatching( - FrameMsg_UpdateOpener::ID); - EXPECT_TRUE(message); - FrameMsg_UpdateOpener::Param params; - EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, ¶ms)); - EXPECT_EQ(opener_routing_id, std::get<0>(params)); + // the opener's frame_token wasn't available when opener_proxy was created. + // Verify that this IPC was sent and that it passed correct frame token. + base::RunLoop().RunUntilIdle(); + DCHECK(proxy_observers.OpenerFrameToken(opener_proxy) == + opener_manager->GetOpenerFrameToken(rfh2->GetSiteInstance())); } // Build the following frame opener graph and see that it can be properly @@ -2393,14 +2408,8 @@ TEST_P(RenderFrameHostManagerTest, PageFocusPropagatesToSubframeProcesses) { TestRenderFrameHost* host2 = static_cast<TestRenderFrameHost*>(NavigateToEntry(child2, &entryB)); - child1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); - child2->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(child1, host1); + DidNavigateFrame(child2, host2); // Navigate the third subframe to C. NavigationEntryImpl entryC( @@ -2419,10 +2428,7 @@ TEST_P(RenderFrameHostManagerTest, PageFocusPropagatesToSubframeProcesses) { // Create PageFocusRemoteFrame to intercept SetPageFocus to RemoteFrame. PageFocusRemoteFrame remote_frame3(proxyC); - child3->DidNavigateFrame(host3, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(child3, host3); // Make sure the first two subframes and the third subframe are placed in // distinct processes. @@ -2495,10 +2501,7 @@ TEST_P(RenderFrameHostManagerTest, false /* is_renderer_init */, nullptr /* blob_url_loader_factory */); TestRenderFrameHost* hostB = static_cast<TestRenderFrameHost*>(NavigateToEntry(child, &entryB)); - child->DidNavigateFrame(hostB, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(child, hostB); // Ensure that the main page is focused. main_test_rfh()->GetView()->Focus(); @@ -2521,10 +2524,7 @@ TEST_P(RenderFrameHostManagerTest, // Create PageFocusRemoteFrame to intercept SetPageFocus to RemoteFrame. PageFocusRemoteFrame remote_frame(proxyC); - child->DidNavigateFrame(hostC, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(child, hostC); base::RunLoop().RunUntilIdle(); @@ -2581,10 +2581,7 @@ TEST_P(RenderFrameHostManagerTest, RestoreNavigationToWebUI) { EXPECT_TRUE(current_host->web_ui()); // The RenderFrameHost committed. - manager->DidNavigateFrame(current_host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, current_host); EXPECT_EQ(current_host, manager->current_frame_host()); EXPECT_TRUE(current_host->web_ui()); } @@ -2900,10 +2897,7 @@ TEST_P(RenderFrameHostManagerTest, NavigateFromDeadRendererToWebUI) { EXPECT_EQ(web_ui, host->web_ui()); // The RenderFrameHost committed. - manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */, - false /* is_same_document_navigation */, - false /* clear_proxies_on_commit */, - blink::FramePolicy()); + DidNavigateFrame(manager, host); EXPECT_EQ(host, manager->current_frame_host()); EXPECT_FALSE(GetPendingFrameHost(manager)); EXPECT_EQ(web_ui, host->web_ui()); @@ -3244,31 +3238,26 @@ TEST_P(RenderFrameHostManagerTest, BeginNavigationIgnoredWhenNotActive) { // Tests that sandbox flags received after a navigation away has started do not // affect the document being navigated to. TEST_P(RenderFrameHostManagerTest, ReceivedFramePolicyAfterNavigationStarted) { - const GURL kUrl1("http://www.google.com"); - const GURL kUrl2("http://www.chromium.org"); - - contents()->NavigateAndCommit(kUrl1); - TestRenderFrameHost* initial_rfh = main_test_rfh(); - - // The RFH should start out with an empty frame policy. + // The RFH should start out with an fully permissive sandbox policy. EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, - initial_rfh->frame_tree_node()->active_sandbox_flags()); + main_test_rfh()->frame_tree_node()->active_sandbox_flags()); - // Navigate cross-site but don't commit the navigation. - auto navigation_to_kUrl2 = - NavigationSimulator::CreateBrowserInitiated(kUrl2, contents()); - navigation_to_kUrl2->ReadyToCommit(); + // Navigate, but don't commit the navigation. + auto navigation = NavigationSimulator::CreateBrowserInitiated( + GURL("http://a.com"), contents()); + navigation->ReadyToCommit(); // Now send the frame policy for the initial page. - initial_rfh->SendFramePolicy(network::mojom::WebSandboxFlags::kAll, - {} /* feature_policy_header */, - {} /* document_policy_header */); - // Verify that the policy landed in the frame tree. + main_test_rfh()->SendFramePolicy(network::mojom::WebSandboxFlags::kAll, + {} /* feature_policy_header */, + {} /* document_policy_header */); + + // Check 'SendFramePolicy' updated the active sandbox flags. EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, - initial_rfh->frame_tree_node()->active_sandbox_flags()); + main_test_rfh()->frame_tree_node()->active_sandbox_flags()); - // Commit the naviagation; the new frame should have a clear frame policy. - navigation_to_kUrl2->Commit(); + // Commit the navigation. The new frame should have a clear frame policy. + navigation->Commit(); EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, main_test_rfh()->frame_tree_node()->active_sandbox_flags()); } diff --git a/chromium/content/browser/frame_host/render_frame_message_filter.cc b/chromium/content/browser/frame_host/render_frame_message_filter.cc index 33e2c5f972d..75e5dbaa10c 100644 --- a/chromium/content/browser/frame_host/render_frame_message_filter.cc +++ b/chromium/content/browser/frame_host/render_frame_message_filter.cc @@ -16,7 +16,6 @@ #include "base/macros.h" #include "base/strings/string_util.h" #include "base/syslog_logging.h" -#include "base/task/post_task.h" #include "base/unguessable_token.h" #include "build/build_config.h" #include "content/browser/bad_message.h" @@ -283,8 +282,8 @@ void RenderFrameMessageFilter::OnCreateChildFrame( params_reply->frame_token = base::UnguessableToken::Create(); params_reply->devtools_frame_token = base::UnguessableToken::Create(); - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce( &CreateChildFrameOnUI, render_process_id_, params.parent_routing_id, params.scope, params.frame_name, params.frame_unique_name, diff --git a/chromium/content/browser/frame_host/render_frame_message_filter_browsertest.cc b/chromium/content/browser/frame_host/render_frame_message_filter_browsertest.cc index c64a7286604..e2f185d49ae 100644 --- a/chromium/content/browser/frame_host/render_frame_message_filter_browsertest.cc +++ b/chromium/content/browser/frame_host/render_frame_message_filter_browsertest.cc @@ -35,6 +35,7 @@ #include "ipc/ipc_security_test_util.h" #include "net/base/features.h" #include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/cookie_util.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" @@ -78,9 +79,7 @@ void SetCookieDirect(WebContentsImpl* tab, ->SetCanonicalCookie( *cookie_obj, url, options, base::BindLambdaForTesting( - [&](net::CanonicalCookie::CookieInclusionStatus status) { - run_loop.Quit(); - })); + [&](net::CookieInclusionStatus status) { run_loop.Quit(); })); run_loop.Run(); } @@ -93,14 +92,14 @@ std::string GetCookiesDirect(WebContentsImpl* tab, const GURL& url) { base::RunLoop run_loop; BrowserContext::GetDefaultStoragePartition(tab->GetBrowserContext()) ->GetCookieManagerForBrowserProcess() - ->GetCookieList(url, options, - base::BindLambdaForTesting( - [&](const net::CookieStatusList& cookie_list, - const net::CookieStatusList& excluded_cookies) { - result = - net::cookie_util::StripStatuses(cookie_list); - run_loop.Quit(); - })); + ->GetCookieList( + url, options, + base::BindLambdaForTesting( + [&](const net::CookieAccessResultList& cookie_list, + const net::CookieAccessResultList& excluded_cookies) { + result = net::cookie_util::StripAccessResults(cookie_list); + run_loop.Quit(); + })); run_loop.Run(); return net::CanonicalCookie::BuildCookieLine(result); } diff --git a/chromium/content/browser/frame_host/render_frame_proxy_host.cc b/chromium/content/browser/frame_host/render_frame_proxy_host.cc index 425b0b4f6e1..ba3aa600341 100644 --- a/chromium/content/browser/frame_host/render_frame_proxy_host.cc +++ b/chromium/content/browser/frame_host/render_frame_proxy_host.cc @@ -30,11 +30,13 @@ #include "content/public/browser/browser_thread.h" #include "content/public/common/content_client.h" #include "content/public/common/content_features.h" +#include "content/public/common/referrer_type_converters.h" #include "ipc/ipc_message.h" #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "services/network/public/cpp/web_sandbox_flags.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h" +#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h" #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom.h" namespace content { @@ -198,10 +200,6 @@ bool RenderFrameProxyHost::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderFrameProxyHost, msg) IPC_MESSAGE_HANDLER(FrameHostMsg_Detach, OnDetach) - IPC_MESSAGE_HANDLER(FrameHostMsg_OpenURL, OnOpenURL) - IPC_MESSAGE_HANDLER(FrameHostMsg_RouteMessageEvent, OnRouteMessageEvent) - IPC_MESSAGE_HANDLER(FrameHostMsg_DidChangeOpener, OnDidChangeOpener) - IPC_MESSAGE_HANDLER(FrameHostMsg_AdvanceFocus, OnAdvanceFocus) IPC_MESSAGE_HANDLER(FrameHostMsg_PrintCrossProcessSubframe, OnPrintCrossProcessSubframe) IPC_MESSAGE_UNHANDLED(handled = false) @@ -211,12 +209,15 @@ bool RenderFrameProxyHost::OnMessageReceived(const IPC::Message& msg) { bool RenderFrameProxyHost::InitRenderFrameProxy() { DCHECK(!render_frame_proxy_created_); + // We shouldn't be creating proxies for subframes of frames in + // BackForwardCache. + DCHECK(!frame_tree_node_->current_frame_host()->IsInBackForwardCache()); // If the current RenderFrameHost is pending deletion, no new proxies should // be created for it, since this frame should no longer be visible from other // processes. We can get here with postMessage while trying to recreate // proxies for the sender. - if (!frame_tree_node_->current_frame_host()->is_active()) + if (frame_tree_node_->current_frame_host()->IsPendingDeletion()) return false; // It is possible to reach this when the process is dead (in particular, when @@ -252,17 +253,18 @@ bool RenderFrameProxyHost::InitRenderFrameProxy() { CHECK_NE(parent_routing_id, MSG_ROUTING_NONE); } - int opener_routing_id = MSG_ROUTING_NONE; + base::Optional<base::UnguessableToken> opener_frame_token; if (frame_tree_node_->opener()) { - opener_routing_id = frame_tree_node_->render_manager()->GetOpenerRoutingID( - site_instance_.get()); + opener_frame_token = + frame_tree_node_->render_manager()->GetOpenerFrameToken( + site_instance_.get()); } int view_routing_id = frame_tree_node_->frame_tree() ->GetRenderViewHost(site_instance_.get()) ->GetRoutingID(); GetProcess()->GetRendererInterface()->CreateFrameProxy( - routing_id_, view_routing_id, opener_routing_id, parent_routing_id, + routing_id_, view_routing_id, opener_frame_token, parent_routing_id, frame_tree_node_->current_replication_state(), frame_token_, frame_tree_node_->devtools_frame_token()); @@ -300,6 +302,10 @@ void RenderFrameProxyHost::OnAssociatedInterfaceRequest( remote_frame_host_receiver_.Bind( mojo::PendingAssociatedReceiver<blink::mojom::RemoteFrameHost>( std::move(handle))); + } else if (interface_name == blink::mojom::RemoteMainFrameHost::Name_) { + remote_main_frame_host_receiver_.Bind( + mojo::PendingAssociatedReceiver<blink::mojom::RemoteMainFrameHost>( + std::move(handle))); } } @@ -377,9 +383,10 @@ void RenderFrameProxyHost::UpdateOpener() { GetSiteInstance(), frame_tree_node_); } - int opener_routing_id = - frame_tree_node_->render_manager()->GetOpenerRoutingID(GetSiteInstance()); - Send(new FrameMsg_UpdateOpener(GetRoutingID(), opener_routing_id)); + auto opener_frame_token = + frame_tree_node_->render_manager()->GetOpenerFrameToken( + GetSiteInstance()); + GetAssociatedRemoteFrame()->UpdateOpener(opener_frame_token); } void RenderFrameProxyHost::SetFocusedFrame() { @@ -416,65 +423,6 @@ void RenderFrameProxyHost::OnDetach() { frame_tree_node_->current_frame_host()->DetachFromProxy(); } -void RenderFrameProxyHost::OnOpenURL( - const FrameHostMsg_OpenURL_Params& params) { - // Verify and unpack IPC payload. - GURL validated_url; - scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; - if (!VerifyOpenURLParams(GetSiteInstance(), params, &validated_url, - &blob_url_loader_factory)) { - return; - } - - RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host(); - - // The current_rfh may be pending deletion. In this case, ignore the - // navigation, because the frame is going to disappear soon anyway. - if (!current_rfh->is_active()) - return; - - // Verify that we are in the same BrowsingInstance as the current - // RenderFrameHost. - if (!site_instance_->IsRelatedSiteInstance(current_rfh->GetSiteInstance())) - return; - - // Since this navigation targeted a specific RenderFrameProxy, it should stay - // in the current tab. - DCHECK_EQ(WindowOpenDisposition::CURRENT_TAB, params.disposition); - - // Augment |download_policy| for situations that were not covered on the - // renderer side, e.g. status not available on remote frame, etc. - NavigationDownloadPolicy download_policy = params.download_policy; - GetContentClient()->browser()->AugmentNavigationDownloadPolicy( - frame_tree_node_->navigator()->GetController()->GetWebContents(), - current_rfh, params.user_gesture, &download_policy); - - if ((frame_tree_node_->pending_frame_policy().sandbox_flags & - network::mojom::WebSandboxFlags::kDownloads) != - network::mojom::WebSandboxFlags::kNone) { - if (download_policy.blocking_downloads_in_sandbox_enabled) { - download_policy.SetDisallowed(content::NavigationDownloadType::kSandbox); - } else { - download_policy.SetAllowed(content::NavigationDownloadType::kSandbox); - } - } - - // TODO(lfg, lukasza): Remove |extra_headers| parameter from - // RequestTransferURL method once both RenderFrameProxyHost and - // RenderFrameHostImpl call RequestOpenURL from their OnOpenURL handlers. - // See also https://crbug.com/647772. - // TODO(clamy): The transition should probably be changed for POST navigations - // to PAGE_TRANSITION_FORM_SUBMIT. See https://crbug.com/829827. - frame_tree_node_->navigator()->NavigateFromFrameProxy( - current_rfh, validated_url, - GlobalFrameRoutingId(GetProcess()->GetID(), params.initiator_routing_id), - params.initiator_origin, site_instance_.get(), params.referrer, - ui::PAGE_TRANSITION_LINK, params.should_replace_current_entry, - download_policy, params.post_body ? "POST" : "GET", params.post_body, - params.extra_headers, std::move(blob_url_loader_factory), - params.user_gesture, params.impression); -} - void RenderFrameProxyHost::CheckCompleted() { RenderFrameHostImpl* target_rfh = frame_tree_node()->current_frame_host(); target_rfh->GetAssociatedLocalFrame()->CheckCompleted(); @@ -498,8 +446,39 @@ void RenderFrameProxyHost::ChildProcessGone() { GetAssociatedRenderFrameProxy()->ChildProcessGone(); } -void RenderFrameProxyHost::OnRouteMessageEvent( - const FrameMsg_PostMessage_Params& params) { +void RenderFrameProxyHost::DidFocusFrame() { + RenderFrameHostImpl* render_frame_host = + frame_tree_node_->current_frame_host(); + + // We need to handle this case due to a race, see documentation in + // RenderFrameHostImpl::DidFocusFrame for more details. + if (render_frame_host->InsidePortal()) + return; + + render_frame_host->delegate()->SetFocusedFrame(frame_tree_node_, + GetSiteInstance()); +} + +void RenderFrameProxyHost::CapturePaintPreviewOfCrossProcessSubframe( + const gfx::Rect& clip_rect, + const base::UnguessableToken& guid) { + RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host(); + // Do not capture paint on behalf of inactive RenderFrameHost. + if (rfh->IsInactiveAndDisallowReactivation()) + return; + rfh->delegate()->CapturePaintPreviewOfCrossProcessSubframe(clip_rect, guid, + rfh); +} + +void RenderFrameProxyHost::SetIsInert(bool inert) { + cross_process_frame_connector_->SetIsInert(inert); +} + +void RenderFrameProxyHost::RouteMessageEvent( + const base::Optional<base::UnguessableToken>& source_frame_token, + const base::string16& source_origin, + const base::string16& target_origin, + blink::TransferableMessage message) { RenderFrameHostImpl* target_rfh = frame_tree_node()->current_frame_host(); if (!target_rfh->IsRenderFrameLive()) { // Check if there is an inner delegate involved; if so target its main @@ -511,34 +490,34 @@ void RenderFrameProxyHost::OnRouteMessageEvent( return; } - // |targetOrigin| argument of postMessage is already checked by + // |target_origin| argument of postMessage is already checked by // blink::LocalDOMWindow::DispatchMessageEventWithOriginCheck (needed for // messages sent within the same process - e.g. same-site, cross-origin), // but this check needs to be duplicated below in case the recipient renderer // process got compromised (i.e. in case the renderer-side check may be // bypassed). - if (!params.target_origin.empty()) { - url::Origin target_origin = - url::Origin::Create(GURL(base::UTF16ToUTF8(params.target_origin))); + if (!target_origin.empty()) { + url::Origin target_url_origin = + url::Origin::Create(GURL(base::UTF16ToUTF8(target_origin))); // Renderer should send either an empty string (this is how "*" is expressed // in the IPC) or a valid, non-opaque origin. OTOH, there are no security // implications here - the message payload needs to be protected from an // unintended recipient, not from the sender. - DCHECK(!target_origin.opaque()); + DCHECK(!target_url_origin.opaque()); // While the postMessage was in flight, the target might have navigated away // to another origin. In this case, the postMessage should be silently // dropped. - if (target_origin != target_rfh->GetLastCommittedOrigin()) + if (target_url_origin != target_rfh->GetLastCommittedOrigin()) return; } // TODO(lukasza): Move opaque-ness check into ChildProcessSecurityPolicyImpl. auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); - if (params.source_origin != base::UTF8ToUTF16("null") && + if (source_origin != base::UTF8ToUTF16("null") && !policy->CanAccessDataForOrigin(GetProcess()->GetID(), - GURL(params.source_origin))) { + GURL(source_origin))) { bad_message::ReceivedBadMessage( GetProcess(), bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN); return; @@ -557,16 +536,12 @@ void RenderFrameProxyHost::OnRouteMessageEvent( GetSiteInstance())) return; - base::Optional<base::UnguessableToken> translated_source_token; - base::string16 source_origin = params.source_origin; - base::string16 target_origin = params.target_origin; - blink::TransferableMessage message = std::move(params.message->data); - - // If there is a source_routing_id, translate it to the frame token of the + // If there is a |source_frame_token|, translate it to the frame token of the // equivalent RenderFrameProxyHost in the target process. - if (params.source_routing_id != MSG_ROUTING_NONE) { - RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromID( - GetProcess()->GetID(), params.source_routing_id); + base::Optional<base::UnguessableToken> translated_source_token; + if (source_frame_token) { + RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromFrameToken( + GetProcess()->GetID(), source_frame_token.value()); if (source_rfh) { // https://crbug.com/822958: If the postMessage is going to a descendant // frame, ensure that any pending visual properties such as size are sent @@ -609,8 +584,9 @@ void RenderFrameProxyHost::OnRouteMessageEvent( } // If the message source is a cross-process subframe, its proxy will only - // be created in --site-per-process mode. If the proxy wasn't created, - // set the source routing ID to MSG_ROUTING_NONE (see + // be created in --site-per-process mode, which is the case when we set an + // actual non-empty value for |translated_source_token|. Otherwise (if the + // proxy wasn't created), use an empty |translated_source_token| (see // https://crbug.com/485520 for discussion on why this is ok). RenderFrameProxyHost* source_proxy_in_target_site_instance = source_rfh->frame_tree_node() @@ -627,15 +603,83 @@ void RenderFrameProxyHost::OnRouteMessageEvent( target_origin, std::move(message)); } -void RenderFrameProxyHost::OnDidChangeOpener(int32_t opener_routing_id) { - frame_tree_node_->render_manager()->DidChangeOpener(opener_routing_id, - GetSiteInstance()); +void RenderFrameProxyHost::FocusPage() { + frame_tree_node_->current_frame_host()->FocusPage(); +} + +void RenderFrameProxyHost::OpenURL(mojom::OpenURLParamsPtr params) { + // Verify and unpack IPC payload. + GURL validated_url; + scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; + if (!VerifyOpenURLParams(GetSiteInstance(), params, &validated_url, + &blob_url_loader_factory)) { + return; + } + + RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host(); + + // Only active frames can navigate: + // - If the frame is in pending deletion, ignore the navigation, because the + // frame is going to disappear soon anyway. + // - If the frame is in back-forward cache, it's not allowed to navigate as it + // should remain frozen. Ignore the request and evict the document from + // back-forward cache. + if (current_rfh->IsInactiveAndDisallowReactivation()) + return; + + // Verify that we are in the same BrowsingInstance as the current + // RenderFrameHost. + if (!site_instance_->IsRelatedSiteInstance(current_rfh->GetSiteInstance())) + return; + + // Since this navigation targeted a specific RenderFrameProxy, it should stay + // in the current tab. + DCHECK_EQ(WindowOpenDisposition::CURRENT_TAB, params->disposition); + + // Augment |download_policy| for situations that were not covered on the + // renderer side, e.g. status not available on remote frame, etc. + NavigationDownloadPolicy download_policy = params->download_policy; + GetContentClient()->browser()->AugmentNavigationDownloadPolicy( + frame_tree_node_->navigator().GetController()->GetWebContents(), + current_rfh, params->user_gesture, &download_policy); + + if ((frame_tree_node_->pending_frame_policy().sandbox_flags & + network::mojom::WebSandboxFlags::kDownloads) != + network::mojom::WebSandboxFlags::kNone) { + if (download_policy.blocking_downloads_in_sandbox_enabled) { + download_policy.SetDisallowed(content::NavigationDownloadType::kSandbox); + } else { + download_policy.SetAllowed(content::NavigationDownloadType::kSandbox); + } + } + + // TODO(lfg, lukasza): Remove |extra_headers| parameter from + // RequestTransferURL method once both RenderFrameProxyHost and + // RenderFrameHostImpl call RequestOpenURL from their OnOpenURL handlers. + // See also https://crbug.com/647772. + // TODO(clamy): The transition should probably be changed for POST navigations + // to PAGE_TRANSITION_FORM_SUBMIT. See https://crbug.com/829827. + frame_tree_node_->navigator().NavigateFromFrameProxy( + current_rfh, validated_url, + GlobalFrameRoutingId(GetProcess()->GetID(), params->initiator_routing_id), + params->initiator_origin, site_instance_.get(), + params->referrer.To<content::Referrer>(), ui::PAGE_TRANSITION_LINK, + params->should_replace_current_entry, download_policy, + params->post_body ? "POST" : "GET", params->post_body, + params->extra_headers, std::move(blob_url_loader_factory), + params->user_gesture, params->impression); +} + +void RenderFrameProxyHost::DidChangeOpener( + const base::Optional<base::UnguessableToken>& opener_frame_token) { + frame_tree_node_->render_manager()->DidChangeOpener( + opener_frame_token.value_or(base::UnguessableToken()), GetSiteInstance()); } -void RenderFrameProxyHost::OnAdvanceFocus(blink::mojom::FocusType type, - int32_t source_routing_id) { - RenderFrameHostImpl* target_rfh = - frame_tree_node_->render_manager()->current_frame_host(); +void RenderFrameProxyHost::AdvanceFocus( + blink::mojom::FocusType focus_type, + const base::UnguessableToken& source_frame_token) { + RenderFrameHostImpl* target_rfh = frame_tree_node_->current_frame_host(); if (target_rfh->InsidePortal()) { bad_message::ReceivedBadMessage( GetProcess(), bad_message::RFPH_ADVANCE_FOCUS_INTO_PORTAL); @@ -646,46 +690,19 @@ void RenderFrameProxyHost::OnAdvanceFocus(blink::mojom::FocusType type, // RenderFrameProxyHost in the target process. This is needed for continuing // the focus traversal from correct place in a parent frame after one of its // child frames finishes its traversal. - RenderFrameHostImpl* source_rfh = - RenderFrameHostImpl::FromID(GetProcess()->GetID(), source_routing_id); + RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromFrameToken( + GetProcess()->GetID(), source_frame_token); RenderFrameProxyHost* source_proxy = source_rfh ? source_rfh->frame_tree_node() ->render_manager() ->GetRenderFrameProxyHost(target_rfh->GetSiteInstance()) : nullptr; - target_rfh->AdvanceFocus(type, source_proxy); + target_rfh->AdvanceFocus(focus_type, source_proxy); frame_tree_node_->current_frame_host()->delegate()->OnAdvanceFocus( source_rfh); } -void RenderFrameProxyHost::DidFocusFrame() { - RenderFrameHostImpl* render_frame_host = - frame_tree_node_->current_frame_host(); - - // We need to handle this case due to a race, see documentation in - // RenderFrameHostImpl::DidFocusFrame for more details. - if (render_frame_host->InsidePortal()) - return; - - render_frame_host->delegate()->SetFocusedFrame(frame_tree_node_, - GetSiteInstance()); -} - -void RenderFrameProxyHost::CapturePaintPreviewOfCrossProcessSubframe( - const gfx::Rect& clip_rect, - const base::UnguessableToken& guid) { - RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host(); - if (!rfh->is_active()) - return; - rfh->delegate()->CapturePaintPreviewOfCrossProcessSubframe(clip_rect, guid, - rfh); -} - -void RenderFrameProxyHost::SetIsInert(bool inert) { - cross_process_frame_connector_->SetIsInert(inert); -} - bool RenderFrameProxyHost::IsInertForTesting() { return cross_process_frame_connector_->IsInert(); } diff --git a/chromium/content/browser/frame_host/render_frame_proxy_host.h b/chromium/content/browser/frame_host/render_frame_proxy_host.h index 4f9511a5ddf..695f18320b6 100644 --- a/chromium/content/browser/frame_host/render_frame_proxy_host.h +++ b/chromium/content/browser/frame_host/render_frame_proxy_host.h @@ -12,17 +12,16 @@ #include "base/callback_forward.h" #include "base/macros.h" #include "content/browser/site_instance_impl.h" +#include "content/common/frame.mojom.h" #include "content/common/frame_proxy.mojom.h" #include "ipc/ipc_listener.h" #include "ipc/ipc_sender.h" #include "mojo/public/cpp/bindings/associated_receiver.h" #include "third_party/blink/public/mojom/frame/frame.mojom.h" #include "third_party/blink/public/mojom/input/focus_type.mojom-forward.h" +#include "third_party/blink/public/mojom/messaging/transferable_message.mojom-forward.h" #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-forward.h" -struct FrameHostMsg_OpenURL_Params; -struct FrameMsg_PostMessage_Params; - namespace blink { class AssociatedInterfaceProvider; } @@ -70,7 +69,8 @@ class CONTENT_EXPORT RenderFrameProxyHost : public IPC::Listener, public IPC::Sender, public mojom::RenderFrameProxyHost, - public blink::mojom::RemoteFrameHost { + public blink::mojom::RemoteFrameHost, + public blink::mojom::RemoteMainFrameHost { public: using CreatedCallback = base::RepeatingCallback<void(RenderFrameProxyHost*)>; @@ -160,6 +160,21 @@ class CONTENT_EXPORT RenderFrameProxyHost const gfx::Rect& clip_rect, const base::UnguessableToken& guid) override; void SetIsInert(bool inert) override; + void DidChangeOpener(const base::Optional<base::UnguessableToken>& + opener_frame_token) override; + void AdvanceFocus(blink::mojom::FocusType focus_type, + const base::UnguessableToken& source_frame_token) override; + void RouteMessageEvent( + const base::Optional<base::UnguessableToken>& source_frame_token, + const base::string16& source_origin, + const base::string16& target_origin, + blink::TransferableMessage message) override; + + // blink::mojom::RemoteMainFrameHost overrides: + void FocusPage() override; + + // mojom::RenderFrameProxyHost: + void OpenURL(mojom::OpenURLParamsPtr params) override; // Returns associated remote for the content::mojom::RenderFrameProxy Mojo // interface. @@ -180,12 +195,11 @@ class CONTENT_EXPORT RenderFrameProxyHost const base::UnguessableToken& GetFrameToken() const { return frame_token_; } private: + // The interceptor needs access to frame_host_receiver_for_testing(). + friend class RouteMessageEventInterceptor; + // IPC Message handlers. void OnDetach(); - void OnOpenURL(const FrameHostMsg_OpenURL_Params& params); - void OnRouteMessageEvent(const FrameMsg_PostMessage_Params& params); - void OnDidChangeOpener(int32_t opener_routing_id); - void OnAdvanceFocus(blink::mojom::FocusType type, int32_t source_routing_id); void OnPrintCrossProcessSubframe(const gfx::Rect& rect, int document_cookie); // IPC::Listener @@ -195,6 +209,12 @@ class CONTENT_EXPORT RenderFrameProxyHost blink::AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces(); + // Needed for tests to be able to swap the implementation and intercept calls. + mojo::AssociatedReceiver<blink::mojom::RemoteFrameHost>& + frame_host_receiver_for_testing() { + return remote_frame_host_receiver_; + } + // This RenderFrameProxyHost's routing id. int routing_id_; @@ -244,6 +264,9 @@ class CONTENT_EXPORT RenderFrameProxyHost mojo::AssociatedReceiver<blink::mojom::RemoteFrameHost> remote_frame_host_receiver_{this}; + mojo::AssociatedReceiver<blink::mojom::RemoteMainFrameHost> + remote_main_frame_host_receiver_{this}; + base::UnguessableToken frame_token_ = base::UnguessableToken::Create(); DISALLOW_COPY_AND_ASSIGN(RenderFrameProxyHost); diff --git a/chromium/content/browser/frame_host/should_swap_browsing_instance.h b/chromium/content/browser/frame_host/should_swap_browsing_instance.h index b593e950b6a..daf1dfaa02d 100644 --- a/chromium/content/browser/frame_host/should_swap_browsing_instance.h +++ b/chromium/content/browser/frame_host/should_swap_browsing_instance.h @@ -24,9 +24,15 @@ enum class ShouldSwapBrowsingInstance { kNo_AlreadyHasMatchingBrowsingInstance = 9, kNo_RendererDebugURL = 10, kNo_NotNeededForBackForwardCache = 11, - kYes_ProactiveSwap = 12, + kYes_CrossSiteProactiveSwap = 12, + kYes_SameSiteProactiveSwap = 13, + kNo_SameDocumentNavigation = 14, + kNo_SamePageNavigation = 15, + kNo_WillReplaceEntry = 16, + kNo_Reload = 17, + kNo_Guest = 18, - kMaxValue = kYes_ProactiveSwap + kMaxValue = kNo_Guest }; } // namespace content |