[Cast Receiver] Move WebContentsObserver Functionality To New Class
This CL updates the WebRuntimeApplication class by splitting half of its
WebContentsObserver functionality out to a new class, to be re-used by
other cast_receiver classes.
Bug: 1357135, 1359559, 1359578
Change-Id: If1f6c371f53b822dfb60d0cf314bdfbde020fdb2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3875999
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Vigen Issahhanjan <vigeni@google.com>
Reviewed-by: Matt Mueller <mattm@chromium.org>
Commit-Queue: Ryan Keane <rwkeane@google.com>
Cr-Commit-Position: refs/heads/main@{#1044173}
diff --git a/chromecast/cast_core/runtime/browser/BUILD.gn b/chromecast/cast_core/runtime/browser/BUILD.gn
index d70919a..eba6523 100644
--- a/chromecast/cast_core/runtime/browser/BUILD.gn
+++ b/chromecast/cast_core/runtime/browser/BUILD.gn
@@ -180,6 +180,7 @@
"//chromecast/browser:browser_base",
"//chromecast/common:feature_constants",
"//components/cast_receiver/browser",
+ "//components/cast_receiver/browser:page_state_observer",
"//components/cast_streaming/public/mojom",
"//components/url_rewrite/browser",
"//third_party/abseil-cpp:absl",
diff --git a/chromecast/cast_core/runtime/browser/DEPS b/chromecast/cast_core/runtime/browser/DEPS
index 59e20a4..00458a5b 100644
--- a/chromecast/cast_core/runtime/browser/DEPS
+++ b/chromecast/cast_core/runtime/browser/DEPS
@@ -10,7 +10,7 @@
"+chromecast/service",
"+chromecast/shared",
"+components/cast",
- "+components/cast_receiver/browser/public",
+ "+components/cast_receiver/browser",
"+components/cast_streaming/browser",
"+components/cast_streaming/public",
"+components/guest_view/browser",
diff --git a/chromecast/cast_core/runtime/browser/web_runtime_application.cc b/chromecast/cast_core/runtime/browser/web_runtime_application.cc
index 98fccb6e..8680da8 100644
--- a/chromecast/cast_core/runtime/browser/web_runtime_application.cc
+++ b/chromecast/cast_core/runtime/browser/web_runtime_application.cc
@@ -112,69 +112,6 @@
NotifyMediaPlaybackChanged(false);
}
-void WebRuntimeApplication::DidFinishLoad(
- content::RenderFrameHost* render_frame_host,
- const GURL& validated_url) {
- // This logic is a subset of that for DidFinishLoad() in CastWebContentsImpl.
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- 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(INFO) << "Stopping after receiving http failure status code: "
- << http_status_code;
- StopApplication(cast::common::StopReason::HTTP_ERROR,
- net::ERR_HTTP_RESPONSE_CODE_FAILURE);
- return;
- }
-
- OnPageLoaded();
-}
-
-void WebRuntimeApplication::DidFailLoad(
- content::RenderFrameHost* render_frame_host,
- const GURL& validated_url,
- int error_code) {
- // This logic is a subset of that for DidFailLoad() in CastWebContentsImpl.
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- 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(INFO) << "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.
- OnPageLoaded();
- return;
- }
-
- StopApplication(cast::common::StopReason::HTTP_ERROR, error_code);
-}
-
-void WebRuntimeApplication::WebContentsDestroyed() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- content::WebContentsObserver::Observe(nullptr);
- StopApplication(cast::common::StopReason::APPLICATION_REQUEST, net::OK);
-}
-
-void WebRuntimeApplication::PrimaryMainFrameRenderProcessGone(
- base::TerminationStatus status) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- StopApplication(cast::common::StopReason::HTTP_ERROR, net::ERR_UNEXPECTED);
-}
-
void WebRuntimeApplication::OnAllBindingsReceived(
cast::utils::GrpcStatusOr<cast::bindings::GetAllResponse> response_or) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -185,6 +122,8 @@
}
content::WebContentsObserver::Observe(GetCastWebContents()->web_contents());
+ cast_receiver::PageStateObserver::Observe(
+ GetCastWebContents()->web_contents());
bindings_manager_ =
std::make_unique<BindingsManagerWebRuntime>(core_message_port_app_stub());
for (int i = 0; i < response_or->bindings_size(); ++i) {
@@ -198,4 +137,26 @@
LoadPage(app_url_);
}
+void WebRuntimeApplication::OnPageLoadComplete() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ OnPageLoaded();
+}
+
+void WebRuntimeApplication::OnPageStopped(StopReason reason,
+ int32_t error_code) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ switch (reason) {
+ case cast_receiver::PageStateObserver::StopReason::kUnknown:
+ StopApplication(cast::common::StopReason::RUNTIME_ERROR, error_code);
+ break;
+ case cast_receiver::PageStateObserver::StopReason::kApplicationRequest:
+ StopApplication(cast::common::StopReason::APPLICATION_REQUEST,
+ error_code);
+ break;
+ case cast_receiver::PageStateObserver::StopReason::kHttpError:
+ StopApplication(cast::common::StopReason::HTTP_ERROR, error_code);
+ break;
+ }
+}
+
} // namespace chromecast
diff --git a/chromecast/cast_core/runtime/browser/web_runtime_application.h b/chromecast/cast_core/runtime/browser/web_runtime_application.h
index 959d8e19..6f5ab71 100644
--- a/chromecast/cast_core/runtime/browser/web_runtime_application.h
+++ b/chromecast/cast_core/runtime/browser/web_runtime_application.h
@@ -5,7 +5,9 @@
#ifndef CHROMECAST_CAST_CORE_RUNTIME_BROWSER_WEB_RUNTIME_APPLICATION_H_
#define CHROMECAST_CAST_CORE_RUNTIME_BROWSER_WEB_RUNTIME_APPLICATION_H_
+#include "chromecast/cast_core/runtime/browser/bindings_manager_web_runtime.h"
#include "chromecast/cast_core/runtime/browser/runtime_application_base.h"
+#include "components/cast_receiver/browser/page_state_observer.h"
#include "content/public/browser/web_contents_observer.h"
namespace chromecast {
@@ -14,7 +16,8 @@
class CastWebService;
class WebRuntimeApplication final : public RuntimeApplicationBase,
- public content::WebContentsObserver {
+ public content::WebContentsObserver,
+ public cast_receiver::PageStateObserver {
public:
// |web_service| is expected to exist for the lifetime of this instance.
WebRuntimeApplication(std::string cast_session_id,
@@ -24,12 +27,19 @@
~WebRuntimeApplication() override;
private:
+ void OnAllBindingsReceived(
+ cast::utils::GrpcStatusOr<cast::bindings::GetAllResponse> response_or);
+
// RuntimeApplicationBase implementation:
cast::utils::GrpcStatusOr<cast::web::MessagePortStatus> HandlePortMessage(
cast::web::Message message) override;
void LaunchApplication() override;
bool IsStreamingApplication() const override;
+ // cast_receiver::PageStateObserver implementation:
+ void OnPageLoadComplete() override;
+ void OnPageStopped(StopReason reason, int32_t error_code) override;
+
// content::WebContentsObserver implementation:
void InnerWebContentsCreated(
content::WebContents* inner_web_contents) override;
@@ -39,17 +49,6 @@
const MediaPlayerInfo& video_type,
const content::MediaPlayerId& id,
content::WebContentsObserver::MediaStoppedReason reason) override;
- void DidFinishLoad(content::RenderFrameHost* render_frame_host,
- const GURL& validated_url) override;
- void DidFailLoad(content::RenderFrameHost* render_frame_host,
- const GURL& validated_url,
- int error_code) override;
- void WebContentsDestroyed() override;
- void PrimaryMainFrameRenderProcessGone(
- base::TerminationStatus status) override;
-
- void OnAllBindingsReceived(
- cast::utils::GrpcStatusOr<cast::bindings::GetAllResponse> response_or);
const GURL app_url_;
std::unique_ptr<BindingsManagerWebRuntime> bindings_manager_;
diff --git a/components/cast_receiver/browser/BUILD.gn b/components/cast_receiver/browser/BUILD.gn
index 15e2ddb..8affd5c 100644
--- a/components/cast_receiver/browser/BUILD.gn
+++ b/components/cast_receiver/browser/BUILD.gn
@@ -13,10 +13,24 @@
friend = [ ":unit_tests" ]
}
+# TODO(crbug.com/1359579): Limit visibility to just this component.
+source_set("page_state_observer") {
+ deps = [
+ "//base",
+ "//content",
+ "//net",
+ ]
+ sources = [
+ "page_state_observer.cc",
+ "page_state_observer.h",
+ ]
+}
+
source_set("unit_tests") {
testonly = true
deps = [
":browser",
+ ":page_state_observer",
"//media",
"//testing/gmock",
"//testing/gtest",
diff --git a/components/cast_receiver/browser/DEPS b/components/cast_receiver/browser/DEPS
index 1931639e9..656bcd8 100644
--- a/components/cast_receiver/browser/DEPS
+++ b/components/cast_receiver/browser/DEPS
@@ -1,4 +1,6 @@
include_rules = [
+ "+content/public/browser",
"+media",
+ "+net/base",
"+ui/gfx/geometry/rect.h",
]
diff --git a/components/cast_receiver/browser/page_state_observer.cc b/components/cast_receiver/browser/page_state_observer.cc
new file mode 100644
index 0000000..dad1ed6
--- /dev/null
+++ b/components/cast_receiver/browser/page_state_observer.cc
@@ -0,0 +1,107 @@
+// Copyright 2022 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 "components/cast_receiver/browser/page_state_observer.h"
+
+#include "base/memory/raw_ref.h"
+#include "base/process/kill.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/net_errors.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:
+ // content::WebContentsObserver implementation.
+ void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url) override {
+ // 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;
+ wrapped_->OnPageStopped(StopReason::kHttpError,
+ net::ERR_HTTP_RESPONSE_CODE_FAILURE);
+ return;
+ }
+
+ wrapped_->OnPageLoadComplete();
+ }
+
+ 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.
+ wrapped_->OnPageLoadComplete();
+ return;
+ }
+
+ wrapped_->OnPageStopped(StopReason::kHttpError, error_code);
+ }
+
+ void WebContentsDestroyed() override {
+ content::WebContentsObserver::Observe(nullptr);
+ wrapped_->OnPageStopped(StopReason::kApplicationRequest, net::OK);
+ }
+
+ void PrimaryMainFrameRenderProcessGone(
+ base::TerminationStatus status) override {
+ content::WebContentsObserver::Observe(nullptr);
+ wrapped_->OnPageStopped(StopReason::kHttpError, net::ERR_UNEXPECTED);
+ }
+
+ base::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
diff --git a/components/cast_receiver/browser/page_state_observer.h b/components/cast_receiver/browser/page_state_observer.h
new file mode 100644
index 0000000..43adfd3
--- /dev/null
+++ b/components/cast_receiver/browser/page_state_observer.h
@@ -0,0 +1,56 @@
+// Copyright 2022 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 COMPONENTS_CAST_RECEIVER_BROWSER_PAGE_STATE_OBSERVER_H_
+#define COMPONENTS_CAST_RECEIVER_BROWSER_PAGE_STATE_OBSERVER_H_
+
+#include "content/public/browser/web_contents_observer.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace cast_receiver {
+
+// Wrapper around a WebContentsObserver to expose simple lifetime events for
+// a WebContents.
+class PageStateObserver {
+ public:
+ // Reason which a page load may have stopped. Used when calling
+ // OnPageStopped() below.
+ enum class StopReason { kUnknown = 0, kApplicationRequest, kHttpError };
+
+ virtual ~PageStateObserver();
+
+ // Called when the observed |web_contents| has completed loading of a page,
+ // either by completing loading of the page's contents or by user action to
+ // cancel the navigation.
+ virtual void OnPageLoadComplete() {}
+
+ // Called when the observed |web_contents| stops trying to load the page for
+ // reasons outside of the user's control - such as the page closing or an
+ // HTTP error.
+ virtual void OnPageStopped(StopReason reason, int32_t error_code) {}
+
+ protected:
+ PageStateObserver();
+ explicit PageStateObserver(content::WebContents* web_contents);
+
+ void Observe(content::WebContents* web_contents);
+
+ private:
+ // Implementation of the WebContentsObserver interface to call into the
+ // functions defined above. This extra later of indirection is used rather
+ // than directly observing the |web_contents| to avoid complexity associated
+ // with implementers of this class which wish to also implement
+ // content::WebContentsObserver.
+ class WebContentsObserverWrapper;
+
+ std::unique_ptr<WebContentsObserverWrapper> observer_wrapper_;
+};
+
+} // namespace cast_receiver
+
+#endif // COMPONENTS_CAST_RECEIVER_BROWSER_PAGE_STATE_OBSERVER_H_