Commit 9dd7bb4a authored by Jiawei Li's avatar Jiawei Li Committed by Chromium LUCI CQ

[chromecast] Add CastWindowEmbedder and CastContentWindowEmbedded

Those interfaces are used for organizing Cast native windows to work
with an external window manager via the interface CastWindowEmbedder
primarily through pre-defined events and requests.

The unittest is not exhaustive and more test cases will be added in
follow-up CLs.

Bug: Internal b/173251340
Test: cast_content_window_embedded_unittest
Change-Id: I9385cfb0bd2af64cc364f74f5c1b86b88d4432ec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2539668Reviewed-by: default avatarShawn Gallea <sagallea@google.com>
Reviewed-by: default avatarSean Topping <seantopping@chromium.org>
Commit-Queue: Jiawei Li <lijiawei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843204}
parent 2d9577e0
......@@ -465,6 +465,10 @@ cast_source_set("browser_base") {
"webview/cast_app_controller.h",
"webview/cast_app_rpc_instance.cc",
"webview/cast_app_rpc_instance.h",
"webview/cast_content_window_embedded.cc",
"webview/cast_content_window_embedded.h",
"webview/cast_window_embedder.cc",
"webview/cast_window_embedder.h",
"webview/js_channel_service.cc",
"webview/js_channel_service.h",
"webview/platform_views_grpc_service.cc",
......@@ -489,6 +493,7 @@ cast_source_set("browser_base") {
deps += [
":web_contents_provider",
"//chromecast/browser/webview/proto",
"//chromecast/ui:back_gesture_router",
"//components/exo",
"//components/exo/wayland",
"//content/public/browser",
......@@ -664,6 +669,10 @@ cast_source_set("unittests") {
"//ui/events:test_support",
]
}
if ((is_linux || is_chromeos) && use_ozone) {
deps += [ "//chromecast/browser/webview:unittests" ]
}
}
if (is_android) {
......
# 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.
import("//chromecast/chromecast.gni")
import("//testing/test.gni")
cast_source_set("unittests") {
testonly = true
sources = [ "cast_content_window_embedded_unittest.cc" ]
deps = [
"//base",
"//base/test:test_support",
"//chromecast/browser",
"//chromecast/browser:public",
"//chromecast/browser:test_support",
"//content/public/common",
]
}
// 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 "chromecast/browser/webview/cast_content_window_embedded.h"
#include <string>
#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/values.h"
#include "chromecast/browser/cast_web_contents.h"
#include "chromecast/graphics/cast_window_manager.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/window.h"
namespace chromecast {
namespace {
constexpr char kKeyAppId[] = "appId";
constexpr char kKeyAppSessionId[] = "appSessionId";
constexpr char kKeyRemoteControlModeEnabled[] = "remoteControlModeEnabled";
} // namespace
CastContentWindowEmbedded::CastContentWindowEmbedded(
const CastContentWindow::CreateParams& params,
CastWindowEmbedder* cast_window_embedder,
bool force_720p_resolution)
: CastContentWindow(params),
is_touch_enabled_(params.enable_touch_input),
cast_window_embedder_(cast_window_embedder),
force_720p_resolution_(force_720p_resolution) {
DCHECK(delegate_);
DCHECK(cast_window_embedder_);
cast_window_embedder_->AddEmbeddedWindow(this);
window_id_ = cast_window_embedder_->GenerateWindowId();
}
void CastContentWindowEmbedded::SendWindowRequest(
CastWindowEmbedder::WindowRequestType request_type) {
cast_window_embedder_->OnWindowRequest(request_type,
PopulateCastWindowProperties());
}
CastContentWindowEmbedded::~CastContentWindowEmbedded() {
if (window_) {
window_->RemoveObserver(this);
window_ = nullptr;
}
if (cast_window_embedder_) {
cast_window_embedder_->RemoveEmbeddedWindow(this);
}
SendWindowRequest(CastWindowEmbedder::WindowRequestType::CLOSE_WINDOW);
}
void CastContentWindowEmbedded::CreateWindowForWebContents(
CastWebContents* cast_web_contents,
::chromecast::mojom::ZOrder z_order,
VisibilityPriority visibility_priority) {
if (!cast_web_contents) {
LOG(ERROR) << "cast_web_contents is null";
return;
}
cast_web_contents_ = cast_web_contents;
Observe(cast_web_contents_->web_contents());
window_ = cast_web_contents_->web_contents()->GetNativeView();
visibility_priority_ = visibility_priority;
if (!window_->HasObserver(this)) {
window_->AddObserver(this);
}
if (!cast_web_contents_->web_contents()->IsLoading()) {
MaybeSendOpenWindowRequest();
}
}
void CastContentWindowEmbedded::GrantScreenAccess() {
has_screen_access_ = true;
if (!open_window_sent_ && window_)
MaybeSendOpenWindowRequest();
else if (open_window_sent_)
RequestFocus();
}
void CastContentWindowEmbedded::RevokeScreenAccess() {
has_screen_access_ = false;
ReleaseFocus();
}
void CastContentWindowEmbedded::EnableTouchInput(bool enabled) {}
void CastContentWindowEmbedded::RequestVisibility(
VisibilityPriority visibility_priority) {
visibility_priority_ = visibility_priority;
// Since STICKY is sent to the embedder window manager when the app requests
// HIDDEN, the app must be removed from focus before new visibility properties
// are sent.
if (visibility_priority == VisibilityPriority::HIDDEN ||
visibility_priority == VisibilityPriority::HIDDEN_STICKY) {
ReleaseFocus();
SendCastWindowProperties();
} else {
SendCastWindowProperties();
RequestFocus();
}
}
void CastContentWindowEmbedded::SetActivityContext(
base::Value activity_context) {
activity_context_ = activity_context.Clone();
auto* found_app_id = activity_context.FindKey(kKeyAppId);
if (found_app_id) {
app_id_ = found_app_id->GetString();
} else {
LOG(ERROR) << "App ID not found";
}
auto* found_remote_control =
activity_context.FindKey(kKeyRemoteControlModeEnabled);
if (found_remote_control) {
is_remote_control_ = found_remote_control->GetBool();
} else {
LOG(ERROR) << "Is remote control not found";
}
auto* found_session_id = activity_context.FindKey(kKeyAppSessionId);
if (found_session_id) {
session_id_ = found_session_id->GetString();
} else {
LOG(ERROR) << "Session ID not found";
}
}
void CastContentWindowEmbedded::SetHostContext(base::Value host_context) {
host_context_ = host_context.Clone();
}
void CastContentWindowEmbedded::NotifyVisibilityChange(
VisibilityType visibility_type) {
if (delegate_) {
delegate_->OnVisibilityChange(visibility_type);
}
for (auto& observer : observer_list_) {
observer.OnVisibilityChange(visibility_type);
}
}
void CastContentWindowEmbedded::RequestMoveOut() {}
void CastContentWindowEmbedded::OnWindowDestroyed(aura::Window* window) {
window_ = nullptr;
SendWindowRequest(CastWindowEmbedder::WindowRequestType::CLOSE_WINDOW);
}
void CastContentWindowEmbedded::OnEmbedderWindowEvent(
const CastWindowEmbedder::EmbedderWindowEvent& request) {
if (window_id_ != request.window_id)
return;
if (request.navigation && request.navigation.value() ==
CastWindowEmbedder::NavigationType::GO_BACK) {
if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK)) {
delegate_->ConsumeGesture(
GestureType::GO_BACK,
base::BindOnce(&CastContentWindowEmbedded::ConsumeGestureCompleted,
base::Unretained(this)));
} else {
cast_window_embedder_->GenerateAndSendNavigationHandleResult(
window_id_, session_id_, false /* handled */,
CastWindowEmbedder::NavigationType::GO_BACK);
}
return;
}
if (request.visibility_changed) {
switch (request.visibility_changed.value()) {
case CastWindowEmbedder::VisibilityChange::UNKNOWN:
NotifyVisibilityChange(VisibilityType::UNKNOWN);
break;
case CastWindowEmbedder::VisibilityChange::NOT_VISIBLE:
NotifyVisibilityChange(VisibilityType::HIDDEN);
break;
case CastWindowEmbedder::VisibilityChange::FULL_SCREEN:
NotifyVisibilityChange(VisibilityType::FULL_SCREEN);
break;
case CastWindowEmbedder::VisibilityChange::OBSCURED:
NotifyVisibilityChange(VisibilityType::TRANSIENTLY_HIDDEN);
break;
case CastWindowEmbedder::VisibilityChange::INTERRUPTION:
NotifyVisibilityChange(VisibilityType::PARTIAL_OUT);
break;
case CastWindowEmbedder::VisibilityChange::INTERRUPTED:
NotifyVisibilityChange(VisibilityType::FULL_SCREEN);
break;
}
return;
}
if (request.back_gesture_progress_event) {
if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK))
delegate_->GestureProgress(
GestureType::GO_BACK,
gfx::Point(request.back_gesture_progress_event.value().x,
request.back_gesture_progress_event.value().y));
return;
}
if (request.back_gesture_cancel_event) {
if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK))
delegate_->CancelGesture(GestureType::GO_BACK);
return;
}
}
void CastContentWindowEmbedded::ConsumeGestureCompleted(bool handled) {
cast_window_embedder_->GenerateAndSendNavigationHandleResult(
window_id_, session_id_, handled,
CastWindowEmbedder::NavigationType::GO_BACK);
}
int CastContentWindowEmbedded::GetWindowId() {
return window_id_;
}
std::string CastContentWindowEmbedded::GetAppId() {
return app_id_;
}
content::WebContents* CastContentWindowEmbedded::GetWebContents() {
DCHECK(cast_web_contents_);
return cast_web_contents_->web_contents();
}
CastWebContents* CastContentWindowEmbedded::GetCastWebContents() {
return cast_web_contents_;
}
void CastContentWindowEmbedded::DispatchState() {
SendOpenWindowRequest();
RequestFocus();
}
void CastContentWindowEmbedded::SendAppContext(const std::string& context) {
auto cast_window_properties = PopulateCastWindowProperties();
cast_window_properties.app_context = context;
cast_window_embedder_->OnWindowRequest(
CastWindowEmbedder::WindowRequestType::SET_PROPERTIES,
cast_window_properties);
}
void CastContentWindowEmbedded::Stop() {
if (cast_web_contents_)
cast_web_contents_->Stop(net::ERR_FAILED);
}
void CastContentWindowEmbedded::SetCanGoBack(bool can_go_back) {
can_go_back_ = can_go_back;
if (open_window_sent_)
SendCastWindowProperties();
}
void CastContentWindowEmbedded::RegisterBackGestureRouter(
::chromecast::BackGestureRouter* back_gesture_router) {
back_gesture_router->SetBackGestureDelegate(this);
}
CastWindowEmbedder::CastWindowProperties
CastContentWindowEmbedded::PopulateCastWindowProperties() {
CastWindowEmbedder::CastWindowProperties window_properties;
window_properties.window_id = window_id_;
window_properties.session_id = session_id_;
window_properties.app_id = app_id_;
window_properties.is_system_setup_window = false;
window_properties.is_touch_enabled = is_touch_enabled_;
window_properties.is_remote_control = is_remote_control_;
window_properties.visibility_priority = visibility_priority_;
window_properties.force_720p_resolution = force_720p_resolution_;
window_properties.supports_go_back_inside = can_go_back_;
window_properties.host_context = host_context_.Clone();
return window_properties;
}
void CastContentWindowEmbedded::ReleaseFocus() {
if (!window_) {
LOG(WARNING) << "window_ is null";
return;
}
SendWindowRequest(CastWindowEmbedder::WindowRequestType::RELEASE_FOCUS);
// Because rendering a larger window may require more system resources,
// resize the window to one pixel while hidden.
LOG(INFO) << "Resizing window to 1x1 pixel while hidden";
window_->SetBounds(gfx::Rect(1, 1));
}
void CastContentWindowEmbedded::RequestFocus() {
if (!has_screen_access_)
return;
if (!window_) {
LOG(WARNING) << "window_ is null";
return;
}
SendWindowRequest(CastWindowEmbedder::WindowRequestType::REQUEST_FOCUS);
}
void CastContentWindowEmbedded::MaybeSendOpenWindowRequest() {
if (open_window_sent_ || !has_screen_access_)
return;
SendOpenWindowRequest();
RequestFocus();
open_window_sent_ = true;
}
void CastContentWindowEmbedded::SendCastWindowProperties() {
SendWindowRequest(CastWindowEmbedder::WindowRequestType::SET_PROPERTIES);
}
void CastContentWindowEmbedded::SendOpenWindowRequest() {
SendWindowRequest(CastWindowEmbedder::WindowRequestType::OPEN_WINDOW);
}
void CastContentWindowEmbedded::DidFinishLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
MaybeSendOpenWindowRequest();
}
void CastContentWindowEmbedded::DidFirstVisuallyNonEmptyPaint() {
MaybeSendOpenWindowRequest();
}
void CastContentWindowEmbedded::WebContentsDestroyed() {
cast_web_contents_ = nullptr;
}
} // namespace chromecast
// 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 CHROMECAST_BROWSER_WEBVIEW_CAST_CONTENT_WINDOW_EMBEDDED_H_
#define CHROMECAST_BROWSER_WEBVIEW_CAST_CONTENT_WINDOW_EMBEDDED_H_
#include <string>
#include "base/macros.h"
#include "chromecast/browser/cast_content_window.h"
#include "chromecast/browser/webview/cast_window_embedder.h"
#include "chromecast/ui/back_gesture_router.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/aura/window_observer.h"
namespace aura {
class Window;
} // namespace aura
namespace content {
class WebContents;
} // namespace content
namespace chromecast {
class CastWebContents;
// Embedded Cast window implementation to work with webview window
// management system. This class will receive embedder's window events
// and reports its Cast window specific request to embedder.
// An instance of this object is created for each CastWebContents instance. Its
// web contents may be destroyed before |this| is destroyed, so
// WebContentsObserver is used to observe the web contents to prevent garbage
// from being returned in GetWebContents.
class CastContentWindowEmbedded
: public CastContentWindow,
public aura::WindowObserver,
public content::WebContentsObserver,
public chromecast::BackGestureRouter::Delegate,
public CastWindowEmbedder::EmbeddedWindow {
public:
// |cast_window_embedder|: The corresponding embedder that this window
// listens on incoming window events. Must outlive |this|.
// |force_720p_resolution|: Whether 720p resolution is enabled/forced for
// this window's hosted web page (i.e. a CastWebView).
explicit CastContentWindowEmbedded(
const CastContentWindow::CreateParams& params,
CastWindowEmbedder* cast_window_embedder,
bool force_720p_resolution);
~CastContentWindowEmbedded() override;
CastContentWindowEmbedded(const CastContentWindowEmbedded&) = delete;
CastContentWindowEmbedded& operator=(const CastContentWindowEmbedded&) =
delete;
// CastContentWindow implementation:
void CreateWindowForWebContents(
CastWebContents* cast_web_contents,
::chromecast::mojom::ZOrder z_order,
VisibilityPriority visibility_priority) override;
void GrantScreenAccess() override;
void RevokeScreenAccess() override;
void RequestVisibility(VisibilityPriority visibility_priority) override;
void SetActivityContext(base::Value activity_context) override;
void SetHostContext(base::Value host_context) override;
void NotifyVisibilityChange(VisibilityType visibility_type) override;
void RequestMoveOut() override;
void EnableTouchInput(bool enabled) override;
// aura::WindowObserver implementation:
void OnWindowDestroyed(aura::Window* window) override;
// CastWindowEmbedder::EmbeddedWindow implementation:
int GetWindowId() override;
std::string GetAppId() override;
void OnEmbedderWindowEvent(
const CastWindowEmbedder::EmbedderWindowEvent& request) override;
content::WebContents* GetWebContents() override;
CastWebContents* GetCastWebContents() override;
void DispatchState() override;
void SendAppContext(const std::string& context) override;
void Stop() override;
// chromecast::GestureRouter::Delegate
void SetCanGoBack(bool can_go_back) override;
void RegisterBackGestureRouter(
::chromecast::BackGestureRouter* back_gesture_router) override;
private:
// Sending a window request to the embedder to handle.
// |request_type| specifies the type of the request, e.g. creation of new
// window, request of focus, and so forth.
void SendWindowRequest(CastWindowEmbedder::WindowRequestType request_type);
// Collects and generates a |CastWindowProperties| to represent the current
// state of this window, in terms of the embedder needs to know about.
CastWindowEmbedder::CastWindowProperties PopulateCastWindowProperties();
void ReleaseFocus();
void RequestFocus();
// Sends open window request only when this window has gained screen access
// and has not reported the creation/open of this window to the embedder yet.
void MaybeSendOpenWindowRequest();
// Sends the current CastWindowProperties to |cast_window_embedder_|.
void SendCastWindowProperties();
void SendOpenWindowRequest();
void ConsumeGestureCompleted(bool handled);
// content::WebContentsObserver
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override;
void DidFirstVisuallyNonEmptyPaint() override;
void WebContentsDestroyed() override;
// Once set, those window properties are fixed during lifetime of |this|.
std::string app_id_;
std::string session_id_;
int window_id_ = -1;
bool is_remote_control_ = false;
const bool is_touch_enabled_ = false;
CastWindowEmbedder* cast_window_embedder_ = nullptr;
const bool force_720p_resolution_ = false;
// States might change during lifetime of |this|.
bool open_window_sent_ = false;
bool can_go_back_ = false;
bool has_screen_access_ = false;
VisibilityPriority visibility_priority_ = VisibilityPriority::DEFAULT;
aura::Window* window_ = nullptr;
CastWebContents* cast_web_contents_ = nullptr;
base::Value activity_context_;
// A free-form custom data field for communicating with the window embedder.
base::Value host_context_;
};
} // namespace chromecast
#endif // CHROMECAST_BROWSER_WEBVIEW_CAST_CONTENT_WINDOW_EMBEDDED_H_
// 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 "chromecast/browser/webview/cast_content_window_embedded.h"
#include <memory>
#include <utility>
#include "chromecast/browser/test/mock_cast_content_window_delegate.h"
#include "chromecast/browser/test/mock_cast_web_view.h"
#include "chromecast/browser/webview/cast_window_embedder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace {
using testing::_;
using ::testing::Return;
class MockCastWindowEmbedder : public CastWindowEmbedder {
public:
MockCastWindowEmbedder() = default;
~MockCastWindowEmbedder() override = default;
// CastWindowEmbedder implementation:
MOCK_METHOD(int, GenerateWindowId, (), (override));
MOCK_METHOD(void,
AddEmbeddedWindow,
(CastWindowEmbedder::EmbeddedWindow*),
(override));
MOCK_METHOD(void,
RemoveEmbeddedWindow,
(CastWindowEmbedder::EmbeddedWindow*),
(override));
MOCK_METHOD(void,
OnWindowRequest,
(const CastWindowEmbedder::WindowRequestType&,
const CastWindowEmbedder::CastWindowProperties&),
(override));
MOCK_METHOD(void,
GenerateAndSendNavigationHandleResult,
(const int,
const std::string,
const bool,
CastWindowEmbedder::NavigationType),
(override));
};
CastContentWindow::CreateParams GenerateWindowCreateParams() {
CastContentWindow::CreateParams window_params;
window_params.enable_touch_input = true;
window_params.is_remote_control_mode = false;
window_params.turn_on_screen = true;
window_params.gesture_priority = CastGestureHandler::Priority::MAIN_ACTIVITY;
window_params.session_id = "test_session_id";
return window_params;
}
} // namespace
class CastContentWindowEmbeddedTest : public testing::Test {
public:
CastContentWindowEmbeddedTest() {}
~CastContentWindowEmbeddedTest() override = default;
void SetUp() override {
cast_window_embedder_ = std::make_unique<MockCastWindowEmbedder>();
mock_cast_content_window_delegate_ =
std::make_unique<MockCastContentWindowDelegate>();
}
std::unique_ptr<MockCastWindowEmbedder> cast_window_embedder_;
std::unique_ptr<MockCastContentWindowDelegate>
mock_cast_content_window_delegate_;
std::unique_ptr<CastContentWindowEmbedded> cast_content_window_embedded_;
};
// Test 1: Embedded window shall add itself into the CastWindowEmbedder's
// list of managed windows.
TEST_F(CastContentWindowEmbeddedTest, AddObserverOnWindowCreation) {
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
}
// Test 2: Embedded window shall remove itself from the CastWindowEmbedder's
// list of managed windows.
TEST_F(CastContentWindowEmbeddedTest, RemoveObserverOnWindowClose) {
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, RemoveEmbeddedWindow(_)).Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
cast_content_window_embedded_.reset();
}
// Test 3: Embedded window shall request the embedder to assign a unique window
// ID.
TEST_F(CastContentWindowEmbeddedTest,
RequestToGenerateWindowIdOnWindowCreation) {
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
EXPECT_CALL(*cast_window_embedder_, GenerateWindowId()).Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
}
// Test 4: Delegate can handle navigation GO_BACK event. In this case,
// the embedded shall inform the embedder that the navigation request
// has been handled successfully.
TEST_F(CastContentWindowEmbeddedTest, HandleNavigationEventByDelegate) {
// Use a fake window_id here for testing.
constexpr int fake_window_id = 1;
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
EXPECT_CALL(*cast_window_embedder_, GenerateWindowId())
.Times(1)
.WillOnce(Return(fake_window_id /* window_id */));
EXPECT_CALL(*mock_cast_content_window_delegate_,
CanHandleGesture(GestureType::GO_BACK))
.Times(1)
.WillOnce(Return(true));
// Delegate of the CastContentWindow indicts that it has handled the event.
EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
.Times(1)
.WillOnce([](GestureType gesture_type,
base::OnceCallback<void(bool)> consume_gesture_completed) {
std::move(consume_gesture_completed).Run(true);
});
// CastWindowEmbedder shall receive the ack of event handling
EXPECT_CALL(
*cast_window_embedder_,
GenerateAndSendNavigationHandleResult(
fake_window_id, _, true, CastWindowEmbedder::NavigationType::GO_BACK))
.Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
CastWindowEmbedder::EmbedderWindowEvent window_event;
window_event.window_id = 1;
window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
cast_content_window_embedded_->OnEmbedderWindowEvent(window_event);
}
// Test 5: Delegate cannot consume navigation GO_BACK event. In this case,
// the embedded window shall inform the embedder that the navigation request
// has been rejected / not handled.
TEST_F(CastContentWindowEmbeddedTest, NavigationEventUnhandled) {
// Use a fake window_id here for testing.
constexpr int fake_window_id = 1;
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
EXPECT_CALL(*cast_window_embedder_, GenerateWindowId())
.Times(1)
.WillOnce(Return(fake_window_id /* window_id */));
EXPECT_CALL(*mock_cast_content_window_delegate_,
CanHandleGesture(GestureType::GO_BACK))
.Times(1)
.WillOnce(Return(true));
// Delegate of the CastContentWindow indicts that it dit not handle the
// event.
EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
.Times(1)
.WillOnce([](GestureType gesture_type,
base::OnceCallback<void(bool)> consume_gesture_completed) {
std::move(consume_gesture_completed).Run(false /* handled */);
});
// CastWindowEmbedder shall receive the ack of event handling
EXPECT_CALL(*cast_window_embedder_,
GenerateAndSendNavigationHandleResult(
fake_window_id, _, false /* handled */,
CastWindowEmbedder::NavigationType::GO_BACK))
.Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
CastWindowEmbedder::EmbedderWindowEvent window_event;
window_event.window_id = 1;
window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
cast_content_window_embedded_->OnEmbedderWindowEvent(window_event);
}
// Test 6: Delegate cannot handle GO_BACK gesture. In this case,
// the embedded window shall inform the embedder that the navigation request
// has been rejected / not handled.
TEST_F(CastContentWindowEmbeddedTest, DelegateCannotHandleGoBackGesture) {
// Use a fake window_id here for testing.
constexpr int fake_window_id = 1;
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
EXPECT_CALL(*cast_window_embedder_, GenerateWindowId())
.Times(1)
.WillOnce(Return(fake_window_id /* window_id */));
EXPECT_CALL(*mock_cast_content_window_delegate_,
CanHandleGesture(GestureType::GO_BACK))
.Times(1)
.WillOnce(Return(false));
// Delegate of the CastContentWindow indicts that it dit not handled the
// event.
EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
.Times(0);
// CastWindowEmbedder shall receive the ack of event handling
EXPECT_CALL(*cast_window_embedder_,
GenerateAndSendNavigationHandleResult(
fake_window_id, _, false /* handled */,
CastWindowEmbedder::NavigationType::GO_BACK))
.Times(1);
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
CastWindowEmbedder::EmbedderWindowEvent window_event;
window_event.window_id = 1;
window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
cast_content_window_embedded_->OnEmbedderWindowEvent(window_event);
}
// Test 7: |GetWindowId()| must be same as the value which is assigned by
// |CastWindowEmbedder::GenerateId()|.
TEST_F(CastContentWindowEmbeddedTest, WindowIdMustMatchInitialValue) {
// Use a fake window_id here for testing.
constexpr int fake_window_id = 369;
auto window_params = GenerateWindowCreateParams();
window_params.delegate = mock_cast_content_window_delegate_->AsWeakPtr();
EXPECT_CALL(*cast_window_embedder_, AddEmbeddedWindow(_)).Times(1);
EXPECT_CALL(*cast_window_embedder_, GenerateWindowId())
.Times(1)
.WillOnce(Return(fake_window_id /* window_id */));
cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
window_params, cast_window_embedder_.get(),
false /* force_720p_resolution */);
EXPECT_EQ(fake_window_id, cast_content_window_embedded_->GetWindowId());
}
} // namespace chromecast
// 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 "chromecast/browser/webview/cast_window_embedder.h"
namespace chromecast {
CastWindowEmbedder::EmbedderWindowEvent::EmbedderWindowEvent() = default;
CastWindowEmbedder::EmbedderWindowEvent::~EmbedderWindowEvent() = default;
CastWindowEmbedder::CastWindowProperties::CastWindowProperties() = default;
CastWindowEmbedder::CastWindowProperties::~CastWindowProperties() = default;
CastWindowEmbedder::CastWindowProperties::CastWindowProperties(
CastWindowProperties&& other) = default;
} // namespace chromecast
// 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 CHROMECAST_BROWSER_WEBVIEW_CAST_WINDOW_EMBEDDER_H_
#define CHROMECAST_BROWSER_WEBVIEW_CAST_WINDOW_EMBEDDER_H_
#include <string>
#include "base/optional.h"
#include "chromecast/browser/cast_content_window.h"
namespace content {
class WebContents;
} // namespace content
namespace chromecast {
class CastWebContents;
// An interface to route messages and requests between a window manager
// other than CastShell and the embedded Cast window (i.e. subclass of
// CastContentWindow).
class CastWindowEmbedder {
public:
// Describes change of the visibility state of the embedded window.
enum class VisibilityChange {
// Unknown visibility state.
UNKNOWN = 0,
// The window is not visible to the user.
NOT_VISIBLE = 1,
// The window is active and shown fullscreen to the user.
FULL_SCREEN = 2,
// The window it is covered by other activities.
OBSCURED = 3,
// The window is interrupting another activity, and shown as a side
// interruption.
INTERRUPTION = 4,
// The cast window is interrupted by another activity, and only partially
// visible.
INTERRUPTED = 5,
};
enum class NavigationType {
// Unknown nagvigation type.
UNKNOWN = 0,
// Navigate back.
GO_BACK = 1,
};
struct BackGestureProgressEvent {
// The x-coordinate of the finger during the swipe.
double x = -1;
// The y-coordinate of the finger during the swipe.
double y = -1;
};
// Event sent from the embedder to instruct corresponding Cast window
// to respond.
// Four event types are supported:
// - Visibility change
// - Navigation
// - Back gesture progress update
// - Cancellation of back gesture
// Note that a |WindowEvent| must and must only convey one type
// of event at a time.
struct EmbedderWindowEvent {
EmbedderWindowEvent();
~EmbedderWindowEvent();
// Unique window ID assigned by the embedder.
int window_id = -1;
base::Optional<VisibilityChange> visibility_changed;
base::Optional<NavigationType> navigation;
base::Optional<BackGestureProgressEvent> back_gesture_progress_event;
base::Optional<bool> back_gesture_cancel_event;
};
// Interface for the embedded Cast window to implement for
// working with the embedder environment.
class EmbeddedWindow {
public:
virtual ~EmbeddedWindow() = default;
// Returns the unique window ID assigned by the embedder.
virtual int GetWindowId() = 0;
// Returns the ID of the hosted app.
virtual std::string GetAppId() = 0;
// Handles window change requested by the embedder.
virtual void OnEmbedderWindowEvent(const EmbedderWindowEvent& request) = 0;
// Returns the WebContents associsated with this Cast window.
virtual content::WebContents* GetWebContents() = 0;
// Returns the CastWebContents associsated with this Cast window.
virtual CastWebContents* GetCastWebContents() = 0;
// Populates and reports current window properties to the
// |CastWindowEmbedder|.
virtual void DispatchState() = 0;
// Sends |context| along with current |CastWindowProperties| to
// the embedder window manager.
virtual void SendAppContext(const std::string& context) = 0;
// Stops the Cast window and its assiciated |CastWebContents|.
virtual void Stop() = 0;
};
// Info that are needed by the embedder to orchectrate the embedded window
// with other activities' window.
struct CastWindowProperties {
CastWindowProperties();
~CastWindowProperties();
CastWindowProperties(CastWindowProperties&& other);
CastWindowProperties(const CastWindowProperties&) = delete;
CastWindowProperties& operator=(const CastWindowProperties&) = delete;
// Unique ID for the embedded Cast window. This must be set for each
// CastWindowProperties.
int window_id = -1;
// A Cast specific ID for identifying the type of content hosted by the
// Cast window.
std::string app_id;
// A unique Id to identify the hosted content.
std::string session_id;
// Whether the window is a system setup window for OOBE or error screens.
bool is_system_setup_window = false;
// Whether the hosted content supports handling touch event.
bool is_touch_enabled = false;
// Whether the hosted content is in remote control mode.
bool is_remote_control = false;
// Whether the content is enabled/forced to display in 720P resolution.
bool force_720p_resolution = false;
// Whether the window support navigate back inside.
bool supports_go_back_inside = false;
// Represents requested activity windowing mode.
VisibilityPriority visibility_priority = VisibilityPriority::DEFAULT;
// Application-related metadata associated with the Cast window.
base::Optional<std::string> app_context;
// Custom data for the Cast window. The embedder and whoever set the
// value need to have agreement on the schema of |host_context|.
base::Optional<base::Value> host_context;
};
// The embedded Cast window will use this to communicate with the embedder
// about its current status, including focus change, creation/close of the
// window, and so forth.
enum class WindowRequestType {
// The embedded window requests to report its current set of window
// properties to the embedder.
SET_PROPERTIES,
// Requests the embedder to handle the creation of a new Cast window.
// This could be called when the embedder is restarted and requests all
// alive managed Cast window to report its existence.
OPEN_WINDOW,
// Informs the embedder that the embedded window is being closed.
CLOSE_WINDOW,
// Requests that this window is brought into the focus of user's attention,
// instructed by its visibility priority setting which is included in
// |CastWindowProperties|. It generally means window wants to be shown.
REQUEST_FOCUS,
// Requests to release the focus of this window, e.g. when the window is
// hidden and screen access is revoked.
RELEASE_FOCUS,
// CastShell has handled GO_BACK navigation request.
NAVIGATION_HANDLE_RESULT,
};
virtual ~CastWindowEmbedder() = default;
// Generates a new window ID to be used for identifying a unique
// embedded CastContentWindow.
virtual int GenerateWindowId() = 0;
// Add managed |embedded_window| to listen on events that are sent
// by the embedder.
virtual void AddEmbeddedWindow(EmbeddedWindow* embedded_window) = 0;
// Remove |embedded_window| from the list of managed windows.
virtual void RemoveEmbeddedWindow(EmbeddedWindow* embedded_window) = 0;
// The embedded window report its updated window properties to the embedder
// via this function.
virtual void OnWindowRequest(
const WindowRequestType& type,
const CastWindowProperties& window_properties) = 0;
virtual void GenerateAndSendNavigationHandleResult(
const int window_id,
const std::string session_id,
const bool handled,
NavigationType navigation_type) = 0;
};
} // namespace chromecast
#endif // CHROMECAST_BROWSER_WEBVIEW_CAST_WINDOW_EMBEDDER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment