blob: db1bee3bfe042ae423593286691db80bdc500a2b [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/android/delegated_frame_host_android.h"
#include "base/android/build_info.h"
#include "base/bind.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "cc/layers/solid_color_layer.h"
#include "cc/layers/surface_layer.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/swap_promise.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "ui/android/window_android_compositor.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/dip_util.h"
namespace ui {
namespace {
class TopControlsSwapPromise : public cc::SwapPromise {
public:
explicit TopControlsSwapPromise(float height) : height_(height) {}
~TopControlsSwapPromise() override = default;
// cc::SwapPromise:
void DidActivate() override {}
void WillSwap(viz::CompositorFrameMetadata* metadata) override {
DCHECK_GT(metadata->frame_token, 0u);
metadata->top_controls_visible_height.emplace(height_);
}
void DidSwap() override {}
cc::SwapPromise::DidNotSwapAction DidNotSwap(
DidNotSwapReason reason) override {
return DidNotSwapAction::KEEP_ACTIVE;
}
int64_t GetTraceId() const override { return 0; }
private:
const float height_;
};
scoped_refptr<cc::SurfaceLayer> CreateSurfaceLayer(
const viz::SurfaceId& primary_surface_id,
const viz::SurfaceId& fallback_surface_id,
const gfx::Size& size_in_pixels,
const cc::DeadlinePolicy& deadline_policy,
bool surface_opaque) {
// manager must outlive compositors using it.
auto layer = cc::SurfaceLayer::Create();
layer->SetSurfaceId(primary_surface_id, deadline_policy);
layer->SetOldestAcceptableFallback(fallback_surface_id);
layer->SetBounds(size_in_pixels);
layer->SetIsDrawable(true);
layer->SetContentsOpaque(surface_opaque);
layer->SetSurfaceHitTestable(true);
return layer;
}
// From content::VisibleTimeRequestTrigger::ConsumeAndMergeRequests
// TODO(crbug.com/1263687): Use separate start time for each event.
blink::mojom::RecordContentToVisibleTimeRequestPtr ConsumeAndMergeRequests(
blink::mojom::RecordContentToVisibleTimeRequestPtr request1,
blink::mojom::RecordContentToVisibleTimeRequestPtr request2) {
if (!request1 && !request2)
return nullptr;
// Pick any non-null request to merge into.
blink::mojom::RecordContentToVisibleTimeRequestPtr to;
blink::mojom::RecordContentToVisibleTimeRequestPtr from;
if (request1) {
to = std::move(request1);
from = std::move(request2);
} else {
to = std::move(request2);
from = std::move(request1);
}
if (from) {
to->event_start_time =
std::min(to->event_start_time, from->event_start_time);
to->destination_is_loaded |= from->destination_is_loaded;
to->show_reason_tab_switching |= from->show_reason_tab_switching;
to->show_reason_bfcache_restore |= from->show_reason_bfcache_restore;
}
return to;
}
} // namespace
DelegatedFrameHostAndroid::DelegatedFrameHostAndroid(
ui::ViewAndroid* view,
viz::HostFrameSinkManager* host_frame_sink_manager,
Client* client,
const viz::FrameSinkId& frame_sink_id)
: frame_sink_id_(frame_sink_id),
view_(view),
host_frame_sink_manager_(host_frame_sink_manager),
client_(client),
frame_evictor_(std::make_unique<viz::FrameEvictor>(this)) {
DCHECK(view_);
DCHECK(client_);
constexpr bool is_transparent = false;
content_layer_ = CreateSurfaceLayer(
viz::SurfaceId(), viz::SurfaceId(), gfx::Size(),
cc::DeadlinePolicy::UseDefaultDeadline(), is_transparent);
view_->GetLayer()->AddChild(content_layer_);
host_frame_sink_manager_->RegisterFrameSinkId(
frame_sink_id_, this, viz::ReportFirstSurfaceActivation::kNo);
host_frame_sink_manager_->SetFrameSinkDebugLabel(frame_sink_id_,
"DelegatedFrameHostAndroid");
}
DelegatedFrameHostAndroid::~DelegatedFrameHostAndroid() {
EvictDelegatedFrame();
DetachFromCompositor();
host_frame_sink_manager_->InvalidateFrameSinkId(frame_sink_id_);
}
const viz::FrameSinkId& DelegatedFrameHostAndroid::GetFrameSinkId() const {
return frame_sink_id_;
}
void DelegatedFrameHostAndroid::CopyFromCompositingSurface(
const gfx::Rect& src_subrect,
const gfx::Size& output_size,
base::OnceCallback<void(const SkBitmap&)> callback) {
DCHECK(CanCopyFromCompositingSurface());
std::unique_ptr<ui::WindowAndroidCompositor::ReadbackRef> readback_ref;
if (view_->GetWindowAndroid() && view_->GetWindowAndroid()->GetCompositor()) {
readback_ref =
view_->GetWindowAndroid()->GetCompositor()->TakeReadbackRef();
}
std::unique_ptr<viz::CopyOutputRequest> request =
std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::BindOnce(
[](base::OnceCallback<void(const SkBitmap&)> callback,
std::unique_ptr<ui::WindowAndroidCompositor::ReadbackRef>
readback_ref,
std::unique_ptr<viz::CopyOutputResult> result) {
auto scoped_bitmap = result->ScopedAccessSkBitmap();
std::move(callback).Run(scoped_bitmap.GetOutScopedBitmap());
},
std::move(callback), std::move(readback_ref)));
if (!src_subrect.IsEmpty())
request->set_area(src_subrect);
if (!output_size.IsEmpty()) {
// The CopyOutputRequest API does not allow fixing the output size. Instead
// we have the set area and scale in such a way that it would result in the
// desired output size.
if (!request->has_area())
request->set_area(gfx::Rect(surface_size_in_pixels_));
request->set_result_selection(gfx::Rect(output_size));
const gfx::Rect& area = request->area();
// Viz would normally return an empty result for an empty area.
// However, this guard here is still necessary to protect against setting
// an illegal scaling ratio.
if (area.IsEmpty())
return;
request->SetScaleRatio(
gfx::Vector2d(area.width(), area.height()),
gfx::Vector2d(output_size.width(), output_size.height()));
}
host_frame_sink_manager_->RequestCopyOfOutput(
viz::SurfaceId(frame_sink_id_, local_surface_id_), std::move(request));
}
bool DelegatedFrameHostAndroid::CanCopyFromCompositingSurface() const {
return local_surface_id_.is_valid();
}
void DelegatedFrameHostAndroid::EvictDelegatedFrame() {
content_layer_->SetSurfaceId(viz::SurfaceId(),
cc::DeadlinePolicy::UseDefaultDeadline());
std::vector<viz::SurfaceId> surface_ids;
// If we have a surface from before a navigation, evict it, regardless of
// visibility state.
if (pre_navigation_local_surface_id_.is_valid()) {
viz::SurfaceId pre_nav =
viz::SurfaceId(frame_sink_id_, pre_navigation_local_surface_id_);
surface_ids.push_back(pre_nav);
} else if (!HasSavedFrame() || frame_evictor_->visible()) {
return;
}
if (local_surface_id_.is_valid()) {
viz::SurfaceId current = viz::SurfaceId(frame_sink_id_, local_surface_id_);
surface_ids.push_back(current);
}
if (surface_ids.empty())
return;
host_frame_sink_manager_->EvictSurfaces(surface_ids);
frame_evictor_->OnSurfaceDiscarded();
// When surface sync is on, this call will force |client_| to allocate a new
// LocalSurfaceId which will be embedded the next time the tab is shown. When
// surface sync is off, the renderer will always allocate a new LocalSurfaceId
// when it becomes visible just in case the previous LocalSurfaceId is evicted
// by the browser.
client_->WasEvicted();
}
void DelegatedFrameHostAndroid::ClearFallbackSurfaceForCommitPending() {
const absl::optional<viz::SurfaceId> fallback_surface_id =
content_layer_->oldest_acceptable_fallback();
// CommitPending without a target for TakeFallbackContentFrom. Since we cannot
// guarantee that Navigation will complete, evict our surfaces which are from
// a previous Navigation.
if (fallback_surface_id && fallback_surface_id->is_valid()) {
EvictDelegatedFrame();
content_layer_->SetOldestAcceptableFallback(viz::SurfaceId());
}
}
void DelegatedFrameHostAndroid::ResetFallbackToFirstNavigationSurface() {
// Don't update the fallback if it's already newer than the first id after
// navigation.
if (content_layer_->oldest_acceptable_fallback() &&
content_layer_->oldest_acceptable_fallback()->frame_sink_id() ==
frame_sink_id_ &&
content_layer_->oldest_acceptable_fallback()
->local_surface_id()
.IsSameOrNewerThan(first_local_surface_id_after_navigation_)) {
return;
}
// If we have a surface from before a navigation, evict it as well.
if (pre_navigation_local_surface_id_.is_valid() &&
!first_local_surface_id_after_navigation_.is_valid()) {
EvictDelegatedFrame();
content_layer_->SetBackgroundColor(SkColors::kTransparent);
}
content_layer_->SetOldestAcceptableFallback(
viz::SurfaceId(frame_sink_id_, first_local_surface_id_after_navigation_));
}
bool DelegatedFrameHostAndroid::HasDelegatedContent() const {
return content_layer_->surface_id().is_valid();
}
void DelegatedFrameHostAndroid::CompositorFrameSinkChanged() {
EvictDelegatedFrame();
if (registered_parent_compositor_)
AttachToCompositor(registered_parent_compositor_);
}
void DelegatedFrameHostAndroid::AttachToCompositor(
WindowAndroidCompositor* compositor) {
if (registered_parent_compositor_)
DetachFromCompositor();
compositor->AddChildFrameSink(frame_sink_id_);
registered_parent_compositor_ = compositor;
if (content_to_visible_time_request_) {
registered_parent_compositor_->PostRequestPresentationTimeForNextFrame(
content_to_visible_time_recorder_.TabWasShown(
true /* has_saved_frames */,
std::move(content_to_visible_time_request_)));
}
}
void DelegatedFrameHostAndroid::DetachFromCompositor() {
if (!registered_parent_compositor_)
return;
registered_parent_compositor_->RemoveChildFrameSink(frame_sink_id_);
registered_parent_compositor_ = nullptr;
content_to_visible_time_request_ = nullptr;
}
bool DelegatedFrameHostAndroid::IsPrimarySurfaceEvicted() const {
return !content_layer_->surface_id().is_valid();
}
bool DelegatedFrameHostAndroid::HasSavedFrame() const {
return frame_evictor_->has_surface();
}
void DelegatedFrameHostAndroid::WasHidden() {
CancelPresentationTimeRequest();
frame_evictor_->SetVisible(false);
}
void DelegatedFrameHostAndroid::WasShown(
const viz::LocalSurfaceId& new_local_surface_id,
const gfx::Size& new_size_in_pixels,
bool is_fullscreen,
blink::mojom::RecordContentToVisibleTimeRequestPtr
content_to_visible_time_request) {
if (content_to_visible_time_request) {
PostRequestPresentationTimeForNextFrame(
std::move(content_to_visible_time_request));
}
frame_evictor_->SetVisible(true);
EmbedSurface(
new_local_surface_id, new_size_in_pixels,
cc::DeadlinePolicy::UseSpecifiedDeadline(FirstFrameTimeoutFrames()),
is_fullscreen);
}
void DelegatedFrameHostAndroid::EmbedSurface(
const viz::LocalSurfaceId& new_local_surface_id,
const gfx::Size& new_size_in_pixels,
cc::DeadlinePolicy deadline_policy,
bool is_fullscreen) {
TRACE_EVENT2("viz", "DelegatedFrameHostAndroid::EmbedSurface", "surface_id",
new_local_surface_id.ToString(), "deadline_policy",
deadline_policy.ToString());
// We should never attempt to embed an invalid surface. Catch this here to
// track down the root cause. Otherwise we will have vague crashes later on
// at serialization time.
CHECK(new_local_surface_id.is_valid());
// Confirm that there is a valid fallback surface on, otherwise we need to
// adjust deadline times. To avoid displaying invalid content.
bool has_fallback_surface =
(content_layer_->oldest_acceptable_fallback() &&
content_layer_->oldest_acceptable_fallback()->is_valid());
SetLocalSurfaceId(new_local_surface_id);
// The embedding of a new surface completes the navigation process.
pre_navigation_local_surface_id_ = viz::LocalSurfaceId();
// Navigations performed while hidden delay embedding until transitioning to
// becoming visible. So we may not have a valid surace when DidNavigate is
// called. Cache the first surface here so we have the correct oldest surface
// to fallback to.
if (!first_local_surface_id_after_navigation_.is_valid())
first_local_surface_id_after_navigation_ = local_surface_id_;
surface_size_in_pixels_ = new_size_in_pixels;
viz::SurfaceId current_primary_surface_id = content_layer_->surface_id();
viz::SurfaceId new_primary_surface_id(frame_sink_id_, local_surface_id_);
if (!frame_evictor_->visible() || is_fullscreen) {
// For fullscreen or when tab is hidden we don't want to display old sized
// content. So we advance the fallback forcing viz to fallback to blank
// screen if renderer won't submit frame in time. See
// https://crbug.com/1088369 and https://crbug.com/813157
//
// An empty content layer bounds indicates this renderer has never been made
// visible. This is the case for pre-rendered contents. Don't use the
// primary id as fallback since it's guaranteed to have no content. See
// crbug.com/1218238.
if (!content_layer_->bounds().IsEmpty() &&
surface_size_in_pixels_ != content_layer_->bounds() &&
has_fallback_surface) {
content_layer_->SetOldestAcceptableFallback(new_primary_surface_id);
// We default to black background for fullscreen case.
content_layer_->SetBackgroundColor(
is_fullscreen ? SkColors::kBlack : SkColors::kTransparent);
}
}
if (!frame_evictor_->visible()) {
// Don't update the SurfaceLayer when invisible to avoid blocking on
// renderers that do not submit CompositorFrames. Next time the renderer
// is visible, EmbedSurface will be called again. See WasShown.
return;
}
frame_evictor_->OnNewSurfaceEmbedded();
if (!current_primary_surface_id.is_valid() ||
current_primary_surface_id.local_surface_id() != local_surface_id_) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_OREO) {
// On version of Android earlier than Oreo, we would like to produce new
// content as soon as possible or the OS will create an additional black
// gutter. We only reset the deadline on the first frame (no bounds yet
// specified) or on resize, and only if the deadline policy is not
// infinite.
if (deadline_policy.policy_type() !=
cc::DeadlinePolicy::kUseInfiniteDeadline &&
(content_layer_->bounds().IsEmpty() ||
content_layer_->bounds() != surface_size_in_pixels_)) {
deadline_policy = cc::DeadlinePolicy::UseSpecifiedDeadline(0u);
}
}
// If there is not a valid current surface, nor a valid fallback, we want to
// produce new content as soon as possible. To avoid displaying invalide
// content, such as surfaces from before a navigation.
if (!has_fallback_surface)
deadline_policy = cc::DeadlinePolicy::UseSpecifiedDeadline(0u);
content_layer_->SetSurfaceId(new_primary_surface_id, deadline_policy);
content_layer_->SetBounds(new_size_in_pixels);
}
}
void DelegatedFrameHostAndroid::RequestPresentationTimeForNextFrame(
blink::mojom::RecordContentToVisibleTimeRequestPtr
content_to_content_to_visible_time_request) {
PostRequestPresentationTimeForNextFrame(
std::move(content_to_content_to_visible_time_request));
}
void DelegatedFrameHostAndroid::CancelPresentationTimeRequest() {
content_to_visible_time_request_.reset();
content_to_visible_time_recorder_.TabWasHidden();
}
void DelegatedFrameHostAndroid::OnFirstSurfaceActivation(
const viz::SurfaceInfo& surface_info) {
NOTREACHED();
}
void DelegatedFrameHostAndroid::OnFrameTokenChanged(
uint32_t frame_token,
base::TimeTicks activation_time) {
client_->OnFrameTokenChanged(frame_token, activation_time);
}
viz::SurfaceId DelegatedFrameHostAndroid::SurfaceId() const {
return viz::SurfaceId(frame_sink_id_, local_surface_id_);
}
void DelegatedFrameHostAndroid::SetLocalSurfaceId(
const viz::LocalSurfaceId& local_surface_id) {
local_surface_id_ = local_surface_id;
client_->OnSurfaceIdChanged();
}
bool DelegatedFrameHostAndroid::HasPrimarySurface() const {
return content_layer_->surface_id().is_valid();
}
bool DelegatedFrameHostAndroid::HasFallbackSurface() const {
return content_layer_->oldest_acceptable_fallback() &&
content_layer_->oldest_acceptable_fallback()->is_valid();
}
void DelegatedFrameHostAndroid::TakeFallbackContentFrom(
DelegatedFrameHostAndroid* other) {
if (HasFallbackSurface() || !other->HasPrimarySurface())
return;
const viz::SurfaceId& other_primary = other->content_layer_->surface_id();
const absl::optional<viz::SurfaceId>& other_fallback =
other->content_layer_->oldest_acceptable_fallback();
viz::SurfaceId desired_fallback;
if (!other->HasFallbackSurface() ||
!other_primary.IsSameOrNewerThan(*other_fallback)) {
desired_fallback = other_primary.ToSmallestId();
} else {
desired_fallback = *other_fallback;
}
content_layer_->SetOldestAcceptableFallback(
other->content_layer_->surface_id().ToSmallestId());
}
void DelegatedFrameHostAndroid::DidNavigate() {
first_local_surface_id_after_navigation_ = local_surface_id_;
}
void DelegatedFrameHostAndroid::OnNavigateToNewPage() {
// We are navigating to a different page, so the current |local_surface_id_|
// and the fallback option of |first_local_surface_id_after_navigation_| are
// no longer valid, as they represent older content from a different source.
//
// Cache the current |local_surface_id_| so that if navigation fails we can
// evict it when transitioning to becoming visible.
pre_navigation_local_surface_id_ = local_surface_id_;
first_local_surface_id_after_navigation_ = viz::LocalSurfaceId();
SetLocalSurfaceId(viz::LocalSurfaceId());
}
void DelegatedFrameHostAndroid::SetTopControlsVisibleHeight(float height) {
if (top_controls_visible_height_ == height)
return;
if (!content_layer_ || !content_layer_->layer_tree_host())
return;
top_controls_visible_height_ = height;
auto swap_promise = std::make_unique<TopControlsSwapPromise>(height);
content_layer_->layer_tree_host()->QueueSwapPromise(std::move(swap_promise));
}
void DelegatedFrameHostAndroid::PostRequestPresentationTimeForNextFrame(
blink::mojom::RecordContentToVisibleTimeRequestPtr
content_to_visible_time_request) {
// Since we could receive multiple requests while awaiting
// `registered_parent_compositor_` we merge them.
auto request =
ConsumeAndMergeRequests(std::move(content_to_visible_time_request_),
std::move(content_to_visible_time_request));
if (!registered_parent_compositor_) {
content_to_visible_time_request_ = std::move(request);
return;
}
registered_parent_compositor_->PostRequestPresentationTimeForNextFrame(
content_to_visible_time_recorder_.TabWasShown(true /* has_saved_frames */,
std::move(request)));
}
} // namespace ui