| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/cast_receiver/browser/page_state_observer.h" |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/process/kill.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace cast_receiver { |
| |
| class PageStateObserver::WebContentsObserverWrapper |
| : public content::WebContentsObserver { |
| public: |
| using content::WebContentsObserver::Observe; |
| |
| WebContentsObserverWrapper(PageStateObserver& wrapped, |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), wrapped_(wrapped) {} |
| |
| explicit WebContentsObserverWrapper(PageStateObserver& wrapped) |
| : wrapped_(wrapped) {} |
| |
| ~WebContentsObserverWrapper() override { Observe(nullptr); } |
| |
| private: |
| void TryCallOnPageStopped(StopReason reason, net::Error error_code) { |
| if (!navigation_handle_) { |
| return; |
| } |
| |
| navigation_handle_ = nullptr; |
| wrapped_->OnPageStopped(reason, error_code); |
| } |
| |
| void TryCallOnPageLoadComplete() { |
| if (!navigation_handle_) { |
| return; |
| } |
| |
| navigation_handle_ = nullptr; |
| wrapped_->OnPageLoadComplete(); |
| } |
| |
| // content::WebContentsObserver implementation. |
| void DidStartNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| navigation_handle_ = navigation_handle; |
| } |
| |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| // Ignore sub-frame and non-current main frame navigation. |
| if (navigation_handle != navigation_handle_) { |
| return; |
| } |
| |
| // If the navigation was not committed, it means either the page was a |
| // download or error 204/205, or the navigation never left the previous |
| // URL. Ignore these navigations. |
| if (!navigation_handle->HasCommitted()) { |
| LOG(WARNING) << "Navigation did not commit: url=" |
| << navigation_handle->GetURL(); |
| navigation_handle_ = nullptr; |
| return; |
| } |
| |
| if (navigation_handle->IsErrorPage()) { |
| const net::Error error_code = navigation_handle->GetNetErrorCode(); |
| LOG(ERROR) << "Got error on navigation: url=" |
| << navigation_handle->GetURL() << ", error_code=" << error_code |
| << ", description=" << net::ErrorToShortString(error_code); |
| TryCallOnPageStopped(StopReason::kHttpError, error_code); |
| return; |
| } |
| |
| // Notifies observers that the navigation of the main frame has finished |
| // with no errors. |
| TryCallOnPageLoadComplete(); |
| } |
| |
| void DidFinishLoad(content::RenderFrameHost* render_frame_host, |
| const GURL& validated_url) override { |
| if (render_frame_host != web_contents()->GetPrimaryMainFrame()) { |
| return; |
| } |
| |
| // This logic is a subset of that for DidFinishLoad() in |
| // CastWebContentsImpl. |
| int http_status_code = 0; |
| content::NavigationEntry* nav_entry = |
| web_contents()->GetController().GetVisibleEntry(); |
| if (nav_entry) { |
| http_status_code = nav_entry->GetHttpStatusCode(); |
| } |
| |
| if (http_status_code != 0 && http_status_code / 100 != 2) { |
| DLOG(WARNING) << "Stopping after receiving http failure status code: " |
| << http_status_code; |
| TryCallOnPageStopped(StopReason::kHttpError, |
| net::ERR_HTTP_RESPONSE_CODE_FAILURE); |
| return; |
| } |
| |
| TryCallOnPageLoadComplete(); |
| } |
| |
| void DidFailLoad(content::RenderFrameHost* render_frame_host, |
| const GURL& validated_url, |
| int error_code) override { |
| // This logic is a subset of that for DidFailLoad() in CastWebContentsImpl. |
| if (render_frame_host->GetParent()) { |
| DLOG(ERROR) << "Got error on sub-iframe: url=" << validated_url.spec() |
| << ", error=" << error_code; |
| return; |
| } |
| if (error_code == net::ERR_ABORTED) { |
| // ERR_ABORTED means download was aborted by the app, typically this |
| // happens when flinging URL for direct playback, the initial URLRequest |
| // gets cancelled/aborted and then the same URL is requested via the |
| // buffered data source for media::Pipeline playback. |
| DLOG(WARNING) << "Load canceled: url=" << validated_url.spec(); |
| |
| // We consider the page to be fully loaded in this case, since the app has |
| // intentionally entered this state. If the app wanted to stop, it would |
| // have called window.close() instead. |
| TryCallOnPageLoadComplete(); |
| return; |
| } |
| |
| TryCallOnPageStopped(StopReason::kHttpError, |
| static_cast<net::Error>(error_code)); |
| } |
| |
| void WebContentsDestroyed() override { |
| content::WebContentsObserver::Observe(nullptr); |
| TryCallOnPageStopped(StopReason::kApplicationRequest, net::OK); |
| } |
| |
| void PrimaryMainFrameRenderProcessGone( |
| base::TerminationStatus status) override { |
| content::WebContentsObserver::Observe(nullptr); |
| TryCallOnPageStopped(StopReason::kHttpError, net::ERR_UNEXPECTED); |
| } |
| |
| raw_ptr<content::NavigationHandle> navigation_handle_ = nullptr; |
| raw_ref<PageStateObserver> wrapped_; |
| }; |
| |
| PageStateObserver::PageStateObserver() |
| : observer_wrapper_(std::make_unique<WebContentsObserverWrapper>(*this)) {} |
| |
| PageStateObserver::PageStateObserver(content::WebContents* web_contents) |
| : observer_wrapper_( |
| std::make_unique<WebContentsObserverWrapper>(*this, web_contents)) {} |
| |
| PageStateObserver::~PageStateObserver() = default; |
| |
| void PageStateObserver::Observe(content::WebContents* web_contents) { |
| observer_wrapper_->Observe(web_contents); |
| } |
| |
| } // namespace cast_receiver |