blob: 7bd97c2f2af5c74f0fe8b22f7ae6e0b6cb0d2e1e [file] [log] [blame]
// Copyright 2017 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/cast_web_view.h"
#include <utility>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/browser/cast_web_contents_manager.h"
#include "chromecast/public/cast_media_shlib.h"
#include "content/public/browser/media_capture_devices.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ipc/ipc_message.h"
#include "net/base/net_errors.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "url/gurl.h"
#if defined(OS_ANDROID)
#include "chromecast/browser/android/cast_web_contents_activity.h"
#endif // defined(OS_ANDROID)
#if defined(USE_AURA)
#include "ui/aura/window.h"
#endif
namespace chromecast {
namespace {
std::unique_ptr<content::WebContents> CreateWebContents(
content::BrowserContext* browser_context,
scoped_refptr<content::SiteInstance> site_instance) {
CHECK(display::Screen::GetScreen());
gfx::Size display_size =
display::Screen::GetScreen()->GetPrimaryDisplay().size();
content::WebContents::CreateParams create_params(browser_context, NULL);
create_params.routing_id = MSG_ROUTING_NONE;
create_params.initial_size = display_size;
create_params.site_instance = site_instance;
content::WebContents* web_contents =
content::WebContents::Create(create_params);
return base::WrapUnique(web_contents);
}
} // namespace
CastWebView::CastWebView(Delegate* delegate,
CastWebContentsManager* web_contents_manager,
content::BrowserContext* browser_context,
scoped_refptr<content::SiteInstance> site_instance,
bool transparent,
bool allow_media_access,
bool is_headless,
bool enable_touch_input)
: delegate_(delegate),
web_contents_manager_(web_contents_manager),
browser_context_(browser_context),
site_instance_(std::move(site_instance)),
transparent_(transparent),
web_contents_(CreateWebContents(browser_context_, site_instance_)),
window_(shell::CastContentWindow::Create(delegate,
is_headless,
enable_touch_input)),
did_start_navigation_(false),
allow_media_access_(allow_media_access),
weak_factory_(this) {
DCHECK(delegate_);
DCHECK(web_contents_manager_);
DCHECK(browser_context_);
DCHECK(window_);
content::WebContentsObserver::Observe(web_contents_.get());
web_contents_->SetDelegate(this);
}
CastWebView::~CastWebView() {}
void CastWebView::LoadUrl(GURL url) {
web_contents_->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_TYPED, "");
}
void CastWebView::ClosePage(const base::TimeDelta& shutdown_delay) {
shutdown_delay_ = shutdown_delay;
content::WebContentsObserver::Observe(nullptr);
web_contents_->DispatchBeforeUnload();
web_contents_->ClosePage();
}
void CastWebView::CloseContents(content::WebContents* source) {
DCHECK_EQ(source, web_contents_.get());
window_.reset(); // Window destructor requires live web_contents on Android.
// We need to delay the deletion of web_contents_ to give (and guarantee) the
// renderer enough time to finish 'onunload' handler (but we don't want to
// wait any longer than that to delay the starting of next app).
web_contents_manager_->DelayWebContentsDeletion(std::move(web_contents_),
shutdown_delay_);
delegate_->OnPageStopped(net::OK);
}
void CastWebView::Show(CastWindowManager* window_manager) {
if (media::CastMediaShlib::ClearVideoPlaneImage) {
media::CastMediaShlib::ClearVideoPlaneImage();
}
DCHECK(window_manager);
window_->ShowWebContents(web_contents_.get(), window_manager);
web_contents_->Focus();
}
content::WebContents* CastWebView::OpenURLFromTab(
content::WebContents* source,
const content::OpenURLParams& params) {
LOG(INFO) << "Change url: " << params.url;
// If source is NULL which means current tab, use web_contents_ of this class.
if (!source)
source = web_contents_.get();
DCHECK_EQ(source, web_contents_.get());
// We don't want to create another web_contents. Load url only when source is
// specified.
source->GetController().LoadURL(params.url, params.referrer,
params.transition, params.extra_headers);
return source;
}
void CastWebView::LoadingStateChanged(content::WebContents* source,
bool to_different_document) {
delegate_->OnLoadingStateChanged(source->IsLoading());
}
void CastWebView::ActivateContents(content::WebContents* contents) {
DCHECK_EQ(contents, web_contents_.get());
contents->GetRenderViewHost()->GetWidget()->Focus();
}
bool CastWebView::CheckMediaAccessPermission(content::WebContents* web_contents,
const GURL& security_origin,
content::MediaStreamType type) {
if (!base::FeatureList::IsEnabled(kAllowUserMediaAccess) &&
!allow_media_access_) {
LOG(WARNING) << __func__ << ": media access is disabled.";
return false;
}
return true;
}
const content::MediaStreamDevice* GetRequestedDeviceOrDefault(
const content::MediaStreamDevices& devices,
const std::string& requested_device_id) {
if (!requested_device_id.empty()) {
auto it = std::find_if(
devices.begin(), devices.end(),
[requested_device_id](const content::MediaStreamDevice& device) {
return device.id == requested_device_id;
});
return it != devices.end() ? &(*it) : nullptr;
}
if (!devices.empty())
return &devices[0];
return nullptr;
}
void CastWebView::RequestMediaAccessPermission(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
const content::MediaResponseCallback& callback) {
if (!base::FeatureList::IsEnabled(kAllowUserMediaAccess) &&
!allow_media_access_) {
LOG(WARNING) << __func__ << ": media access is disabled.";
callback.Run(content::MediaStreamDevices(),
content::MEDIA_DEVICE_NOT_SUPPORTED,
std::unique_ptr<content::MediaStreamUI>());
return;
}
auto audio_devices =
content::MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
auto video_devices =
content::MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
VLOG(2) << __func__ << " audio_devices=" << audio_devices.size()
<< " video_devices=" << video_devices.size();
content::MediaStreamDevices devices;
if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
const content::MediaStreamDevice* device = GetRequestedDeviceOrDefault(
audio_devices, request.requested_audio_device_id);
if (device) {
VLOG(1) << __func__ << "Using audio device: id=" << device->id
<< " name=" << device->name;
devices.push_back(*device);
}
}
if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
const content::MediaStreamDevice* device = GetRequestedDeviceOrDefault(
video_devices, request.requested_video_device_id);
if (device) {
VLOG(1) << __func__ << "Using video device: id=" << device->id
<< " name=" << device->name;
devices.push_back(*device);
}
}
callback.Run(devices, content::MEDIA_DEVICE_OK,
std::unique_ptr<content::MediaStreamUI>());
}
#if defined(OS_ANDROID)
base::android::ScopedJavaLocalRef<jobject>
CastWebView::GetContentVideoViewEmbedder() {
DCHECK(web_contents_);
auto* activity = shell::CastWebContentsActivity::Get(web_contents_.get());
return activity->GetContentVideoViewEmbedder();
}
#endif // defined(OS_ANDROID)
void CastWebView::RenderProcessGone(base::TerminationStatus status) {
LOG(INFO) << "APP_ERROR_CHILD_PROCESS_CRASHED";
delegate_->OnPageStopped(net::ERR_UNEXPECTED);
}
void CastWebView::RenderViewCreated(content::RenderViewHost* render_view_host) {
content::RenderWidgetHostView* view =
render_view_host->GetWidget()->GetView();
if (view) {
view->SetBackgroundColor(transparent_ ? SK_ColorTRANSPARENT
: SK_ColorBLACK);
}
}
void CastWebView::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
// 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();
return;
}
net::Error error_code = navigation_handle->GetNetErrorCode();
if (!navigation_handle->IsErrorPage())
return;
// If we abort errors in an iframe, it can create a really confusing
// and fragile user experience. Rather than create a list of errors
// that are most likely to occur, we ignore all of them for now.
if (!navigation_handle->IsInMainFrame()) {
LOG(ERROR) << "Got error on sub-iframe: url=" << navigation_handle->GetURL()
<< ", error=" << error_code
<< ", description=" << net::ErrorToShortString(error_code);
return;
}
LOG(ERROR) << "Got error on navigation: url=" << navigation_handle->GetURL()
<< ", error_code=" << error_code
<< ", description= " << net::ErrorToShortString(error_code);
delegate_->OnPageStopped(error_code);
}
void CastWebView::DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) {
// Only report an error if we are the main frame. See b/8433611.
if (render_frame_host->GetParent()) {
LOG(ERROR) << "Got error on sub-iframe: url=" << validated_url.spec()
<< ", error=" << error_code;
} else 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.
LOG(INFO) << "Load canceled: url=" << validated_url.spec();
} else {
LOG(ERROR) << "Got error on load: url=" << validated_url.spec()
<< ", error_code=" << error_code;
delegate_->OnPageStopped(error_code);
}
}
void CastWebView::DidFirstVisuallyNonEmptyPaint() {
metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstPaint();
}
void CastWebView::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
if (did_start_navigation_) {
return;
}
did_start_navigation_ = true;
#if defined(USE_AURA)
// Resize window
gfx::Size display_size =
display::Screen::GetScreen()->GetPrimaryDisplay().size();
aura::Window* content_window = web_contents()->GetNativeView();
content_window->SetBounds(
gfx::Rect(display_size.width(), display_size.height()));
#endif
}
void CastWebView::MediaStartedPlaying(const MediaPlayerInfo& media_info,
const MediaPlayerId& id) {
metrics::CastMetricsHelper::GetInstance()->LogMediaPlay();
}
void CastWebView::MediaStoppedPlaying(
const MediaPlayerInfo& media_info,
const MediaPlayerId& id,
WebContentsObserver::MediaStoppedReason reason) {
metrics::CastMetricsHelper::GetInstance()->LogMediaPause();
}
} // namespace chromecast