Store and set stale frame content screenshot when the frame is evicted
This patch captures the web content surface as a texture and displays
it on the RenderWidgetHostView until a new compositor frame is submitted
by the viz client. This allows the browser to display stale content
instead of a white solid color during animations.
The decision to show stale content or a solid color is done by the
WebContentsDelegate associated with the RenderWidgetHostView and its
DelegatedFrameHost. For this patch, the default is to show a solid
color on frame eviction. In the case of a tabbed browser, the Browser
(which implements the WebContentsDelegate) chooses to show stale content
if the frame being evicted is from an active tab.
Bug: 897826
Change-Id: I3346f3dec79780d107d2c96b4382886690a69570
Component: RenderWidgetHostViewAura, DelegatedFrameHost, ui layer
Reviewed-on: https://chromium-review.googlesource.com/c/1369714
Reviewed-by: Sadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Saman Sami <samans@chromium.org>
Reviewed-by: François Doray <fdoray@chromium.org>
Commit-Queue: Malay Keshav <malaykeshav@chromium.org>
Cr-Commit-Position: refs/heads/master@{#622255}
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 27290da..a2cc679 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1319,6 +1319,10 @@
return tab_strip_model_->ReplaceWebContentsAt(index, std::move(new_contents));
}
+bool Browser::ShouldShowStaleContentOnEviction(content::WebContents* source) {
+ return source == tab_strip_model_->GetActiveWebContents();
+}
+
bool Browser::IsMouseLocked() const {
return exclusive_access_manager_->mouse_lock_controller()->IsMouseLocked();
}
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 29e83e11..4677cab8 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -538,6 +538,7 @@
std::unique_ptr<content::WebContents> new_contents,
bool did_start_load,
bool did_finish_load) override;
+ bool ShouldShowStaleContentOnEviction(content::WebContents* source) override;
bool is_type_tabbed() const { return type_ == TYPE_TABBED; }
bool is_type_popup() const { return type_ == TYPE_POPUP; }
diff --git a/content/browser/renderer_host/DEPS b/content/browser/renderer_host/DEPS
index a38288a..0e360ca4 100644
--- a/content/browser/renderer_host/DEPS
+++ b/content/browser/renderer_host/DEPS
@@ -24,6 +24,7 @@
"+content/browser/frame_host",
"+content/browser/web_contents",
"+content/public/browser/web_contents.h",
+ "+content/public/browser/web_contents_delegate.h",
"+content/public/browser/web_contents_view.h",
],
"render_process_host_impl\.cc": [
diff --git a/content/browser/renderer_host/browser_compositor_view_mac.h b/content/browser/renderer_host/browser_compositor_view_mac.h
index da7e339..454c4cf 100644
--- a/content/browser/renderer_host/browser_compositor_view_mac.h
+++ b/content/browser/renderer_host/browser_compositor_view_mac.h
@@ -129,6 +129,7 @@
float GetDeviceScaleFactor() const override;
void InvalidateLocalSurfaceIdOnEviction() override;
std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction() override;
+ bool ShouldShowStaleContentOnEviction() override;
base::WeakPtr<BrowserCompositorMac> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
diff --git a/content/browser/renderer_host/browser_compositor_view_mac.mm b/content/browser/renderer_host/browser_compositor_view_mac.mm
index 21d3b34..0817b4e 100644
--- a/content/browser/renderer_host/browser_compositor_view_mac.mm
+++ b/content/browser/renderer_host/browser_compositor_view_mac.mm
@@ -358,6 +358,10 @@
return client_->CollectSurfaceIdsForEviction();
}
+bool BrowserCompositorMac::ShouldShowStaleContentOnEviction() {
+ return false;
+}
+
void BrowserCompositorMac::DidNavigate() {
if (render_widget_host_is_hidden_) {
// Navigating while hidden should not allocate a new LocalSurfaceID. Once
diff --git a/content/browser/renderer_host/delegated_frame_host.cc b/content/browser/renderer_host/delegated_frame_host.cc
index 0bfce4f..ecc0e45 100644
--- a/content/browser/renderer_host/delegated_frame_host.cc
+++ b/content/browser/renderer_host/delegated_frame_host.cc
@@ -28,10 +28,19 @@
#include "content/browser/gpu/compositor_util.h"
#include "content/common/tab_switching_time_callback.h"
#include "content/public/common/content_switches.h"
+#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/dip_util.h"
namespace content {
+namespace {
+
+// Normalized value [0..1] where 1 is full quality and 0 is empty. This sets
+// the quality of the captured texture by reducing its dimensions by this
+// factor.
+constexpr float kFrameContentCaptureQuality = 0.5f;
+
+} // namespace
////////////////////////////////////////////////////////////////////////////////
// DelegatedFrameHost
@@ -64,6 +73,10 @@
"DelegatedFrameHost");
CreateCompositorFrameSinkSupport();
frame_evictor_->SetVisible(client_->DelegatedFrameHostIsVisible());
+
+ stale_content_layer_ =
+ std::make_unique<ui::Layer>(ui::LayerType::LAYER_SOLID_COLOR);
+ stale_content_layer_->SetVisible(false);
}
DelegatedFrameHost::~DelegatedFrameHost() {
@@ -80,6 +93,9 @@
const viz::LocalSurfaceId& new_local_surface_id,
const gfx::Size& new_dip_size,
bool record_presentation_time) {
+ // Cancel any pending frame eviction and unpause it if paused.
+ frame_eviction_state_ = FrameEvictionState::kNotStarted;
+
frame_evictor_->SetVisible(true);
if (record_presentation_time && compositor_) {
compositor_->RequestPresentationTimeForNextFrame(
@@ -90,6 +106,12 @@
// TODO(fsamuel): Investigate if there is a better deadline to use here.
EmbedSurface(new_local_surface_id, new_dip_size,
cc::DeadlinePolicy::UseDefaultDeadline());
+
+ // Remove stale content that might be displayed.
+ if (stale_content_layer_->has_external_content()) {
+ stale_content_layer_->SetShowSolidColorContent();
+ stale_content_layer_->SetVisible(false);
+ }
}
bool DelegatedFrameHost::HasSavedFrame() const {
@@ -104,17 +126,35 @@
const gfx::Rect& src_subrect,
const gfx::Size& output_size,
base::OnceCallback<void(const SkBitmap&)> callback) {
+ CopyFromCompositingSurfaceInternal(
+ src_subrect, output_size,
+ viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP,
+ base::BindOnce(
+ [](base::OnceCallback<void(const SkBitmap&)> callback,
+ std::unique_ptr<viz::CopyOutputResult> result) {
+ std::move(callback).Run(result->AsSkBitmap());
+ },
+ std::move(callback)));
+}
+
+void DelegatedFrameHost::CopyFromCompositingSurfaceAsTexture(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
+ viz::CopyOutputRequest::CopyOutputRequestCallback callback) {
+ CopyFromCompositingSurfaceInternal(
+ src_subrect, output_size,
+ viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE, std::move(callback));
+}
+
+void DelegatedFrameHost::CopyFromCompositingSurfaceInternal(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
+ viz::CopyOutputRequest::ResultFormat format,
+ viz::CopyOutputRequest::CopyOutputRequestCallback callback) {
DCHECK(CanCopyFromCompositingSurface());
- std::unique_ptr<viz::CopyOutputRequest> request =
- std::make_unique<viz::CopyOutputRequest>(
- viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP,
- base::BindOnce(
- [](base::OnceCallback<void(const SkBitmap&)> callback,
- std::unique_ptr<viz::CopyOutputResult> result) {
- std::move(callback).Run(result->AsSkBitmap());
- },
- std::move(callback)));
+ auto request =
+ std::make_unique<viz::CopyOutputRequest>(format, std::move(callback));
if (!src_subrect.IsEmpty()) {
request->set_area(
@@ -344,6 +384,71 @@
}
void DelegatedFrameHost::EvictDelegatedFrame() {
+ // There is already an eviction request pending.
+ if (frame_eviction_state_ == FrameEvictionState::kPendingEvictionRequests) {
+ frame_evictor_->OnSurfaceDiscarded();
+ return;
+ }
+
+ if (!HasSavedFrame()) {
+ ContinueDelegatedFrameEviction();
+ return;
+ }
+
+ // Requests a copy of the compositing surface of the frame if one doesn't
+ // already exist. The copy (stale content) will be set on the surface until
+ // a new compositor frame is submitted. Setting a stale content prevents blank
+ // white screens from being displayed during various animations such as the
+ // CrOS overview mode.
+ if (client_->ShouldShowStaleContentOnEviction() &&
+ !stale_content_layer_->has_external_content()) {
+ frame_eviction_state_ = FrameEvictionState::kPendingEvictionRequests;
+ auto callback =
+ base::BindOnce(&DelegatedFrameHost::DidCopyStaleContent, GetWeakPtr());
+
+ // NOTE: This will not return any texture on non CrOS platforms as hiding
+ // the window on non CrOS platform disables drawing all together.
+ CopyFromCompositingSurfaceAsTexture(
+ gfx::Rect(),
+ gfx::ScaleToRoundedSize(surface_dip_size_, kFrameContentCaptureQuality),
+ std::move(callback));
+ } else {
+ ContinueDelegatedFrameEviction();
+ }
+ frame_evictor_->OnSurfaceDiscarded();
+}
+
+void DelegatedFrameHost::DidCopyStaleContent(
+ std::unique_ptr<viz::CopyOutputResult> result) {
+ // host may have become visible by the time the request to capture surface is
+ // completed.
+ if (frame_evictor_->visible() || result->IsEmpty())
+ return;
+
+ DCHECK_EQ(result->format(), viz::CopyOutputResult::Format::RGBA_TEXTURE);
+
+ if (frame_eviction_state_ == FrameEvictionState::kPendingEvictionRequests) {
+ frame_eviction_state_ = FrameEvictionState::kNotStarted;
+ ContinueDelegatedFrameEviction();
+ }
+
+ auto transfer_resource = viz::TransferableResource::MakeGL(
+ result->GetTextureResult()->mailbox, GL_LINEAR, GL_TEXTURE_2D,
+ result->GetTextureResult()->sync_token);
+ std::unique_ptr<viz::SingleReleaseCallback> release_callback =
+ result->TakeTextureOwnership();
+
+ if (stale_content_layer_->parent() != client_->DelegatedFrameHostGetLayer())
+ client_->DelegatedFrameHostGetLayer()->Add(stale_content_layer_.get());
+
+ DCHECK(!stale_content_layer_->has_external_content());
+ stale_content_layer_->SetVisible(true);
+ stale_content_layer_->SetBounds(gfx::Rect(surface_dip_size_));
+ stale_content_layer_->SetTransferableResource(
+ transfer_resource, std::move(release_callback), surface_dip_size_);
+}
+
+void DelegatedFrameHost::ContinueDelegatedFrameEviction() {
// Reset primary surface.
if (HasPrimarySurface()) {
client_->DelegatedFrameHostGetLayer()->SetShowSurface(
@@ -365,7 +470,6 @@
DCHECK(host_frame_sink_manager_);
host_frame_sink_manager_->EvictSurfaces(surface_ids);
}
- frame_evictor_->OnSurfaceDiscarded();
client_->InvalidateLocalSurfaceIdOnEviction();
}
diff --git a/content/browser/renderer_host/delegated_frame_host.h b/content/browser/renderer_host/delegated_frame_host.h
index 3a855f7..e22ddf5 100644
--- a/content/browser/renderer_host/delegated_frame_host.h
+++ b/content/browser/renderer_host/delegated_frame_host.h
@@ -52,6 +52,7 @@
virtual float GetDeviceScaleFactor() const = 0;
virtual void InvalidateLocalSurfaceIdOnEviction() = 0;
virtual std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction() = 0;
+ virtual bool ShouldShowStaleContentOnEviction() = 0;
};
// The DelegatedFrameHost is used to host all of the RenderWidgetHostView state
@@ -121,6 +122,10 @@
bool HasSavedFrame() const;
void AttachToCompositor(ui::Compositor* compositor);
void DetachFromCompositor();
+
+ // Copies |src_subrect| from the compositing surface into a bitmap (first
+ // overload) or texture (second overload). |output_size| specifies the size of
+ // the output bitmap or texture.
// Note: |src_subrect| is specified in DIP dimensions while |output_size|
// expects pixels. If |src_subrect| is empty, the entire surface area is
// copied.
@@ -128,6 +133,11 @@
const gfx::Rect& src_subrect,
const gfx::Size& output_size,
base::OnceCallback<void(const SkBitmap&)> callback);
+ void CopyFromCompositingSurfaceAsTexture(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
+ viz::CopyOutputRequest::CopyOutputRequestCallback callback);
+
bool CanCopyFromCompositingSurface() const;
const viz::FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
@@ -177,19 +187,31 @@
private:
friend class DelegatedFrameHostClient;
- FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest,
- SkippedDelegatedFrames);
- FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest,
- DiscardDelegatedFramesWithLocking);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionNormal);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionRejected);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionNone);
// FrameEvictorClient implementation.
void EvictDelegatedFrame() override;
+ void DidCopyStaleContent(std::unique_ptr<viz::CopyOutputResult> result);
+
+ void ContinueDelegatedFrameEviction();
+
SkColor GetGutterColor() const;
void CreateCompositorFrameSinkSupport();
void ResetCompositorFrameSinkSupport();
+ void CopyFromCompositingSurfaceInternal(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& output_size,
+ viz::CopyOutputRequest::ResultFormat format,
+ viz::CopyOutputRequest::CopyOutputRequestCallback callback);
+
const viz::FrameSinkId frame_sink_id_;
DelegatedFrameHostClient* const client_;
const bool enable_viz_;
@@ -225,6 +247,18 @@
bool seen_first_activation_ = false;
#endif
+ enum class FrameEvictionState {
+ kNotStarted = 0, // Frame eviction is ready.
+ kPendingEvictionRequests // Frame eviction is paused with pending requests.
+ };
+
+ FrameEvictionState frame_eviction_state_ = FrameEvictionState::kNotStarted;
+
+ // Layer responsible for displaying the stale content for the DFHC when the
+ // actual web content frame has been evicted. This will be reset when a new
+ // compositor frame is submitted.
+ std::unique_ptr<ui::Layer> stale_content_layer_;
+
base::WeakPtrFactory<DelegatedFrameHost> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DelegatedFrameHost);
diff --git a/content/browser/renderer_host/delegated_frame_host_client_aura.cc b/content/browser/renderer_host/delegated_frame_host_client_aura.cc
index 75a43f1..f002a3c9 100644
--- a/content/browser/renderer_host/delegated_frame_host_client_aura.cc
+++ b/content/browser/renderer_host/delegated_frame_host_client_aura.cc
@@ -64,4 +64,8 @@
return render_widget_host_view_->host()->CollectSurfaceIdsForEviction();
}
+bool DelegatedFrameHostClientAura::ShouldShowStaleContentOnEviction() {
+ return render_widget_host_view_->ShouldShowStaleContentOnEviction();
+}
+
} // namespace content
diff --git a/content/browser/renderer_host/delegated_frame_host_client_aura.h b/content/browser/renderer_host/delegated_frame_host_client_aura.h
index 40e1f52e..e7c62fb 100644
--- a/content/browser/renderer_host/delegated_frame_host_client_aura.h
+++ b/content/browser/renderer_host/delegated_frame_host_client_aura.h
@@ -35,6 +35,7 @@
float GetDeviceScaleFactor() const override;
void InvalidateLocalSurfaceIdOnEviction() override;
std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction() override;
+ bool ShouldShowStaleContentOnEviction() override;
private:
RenderWidgetHostViewAura* render_widget_host_view_;
diff --git a/content/browser/renderer_host/render_widget_host_delegate.cc b/content/browser/renderer_host/render_widget_host_delegate.cc
index efd2a7a..3b51afe5 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.cc
+++ b/content/browser/renderer_host/render_widget_host_delegate.cc
@@ -86,6 +86,10 @@
return false;
}
+bool RenderWidgetHostDelegate::ShouldShowStaleContentOnEviction() {
+ return false;
+}
+
blink::WebDisplayMode RenderWidgetHostDelegate::GetDisplayMode(
RenderWidgetHostImpl* render_widget_host) const {
return blink::kWebDisplayModeBrowser;
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index 83878e9..aef0cf02 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -201,6 +201,11 @@
// Returns whether the associated tab is in fullscreen mode.
virtual bool IsFullscreenForCurrentTab();
+ // Returns true if the widget's frame content needs to be stored before
+ // eviction and displayed until a new frame is generated. If false, a white
+ // solid color is displayed instead.
+ virtual bool ShouldShowStaleContentOnEviction();
+
// Returns the display mode for the view.
virtual blink::WebDisplayMode GetDisplayMode(
RenderWidgetHostImpl* render_widget_host) const;
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 72b1edb..17a49b9 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -674,6 +674,10 @@
view_->OnRenderWidgetInit();
}
+bool RenderWidgetHostImpl::ShouldShowStaleContentOnEviction() {
+ return delegate_->ShouldShowStaleContentOnEviction();
+}
+
void RenderWidgetHostImpl::ShutdownAndDestroyWidget(bool also_delete) {
CancelKeyboardLock();
RejectMouseLockOrUnlockIfNecessary();
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 768c9f0..3c8816a 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -277,6 +277,9 @@
// Initializes a RenderWidgetHost that is attached to a RenderFrameHost.
void InitForFrame();
+ // Returns true if the frame content needs be stored before being evicted.
+ bool ShouldShowStaleContentOnEviction();
+
// Signal whether this RenderWidgetHost is owned by a RenderFrameHost, in
// which case it does not do self-deletion.
void set_owned_by_render_frame_host(bool owned_by_rfh) {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 6003c62..5df169e 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -720,6 +720,10 @@
#endif
}
+bool RenderWidgetHostViewAura::ShouldShowStaleContentOnEviction() {
+ return host()->ShouldShowStaleContentOnEviction();
+}
+
gfx::Rect RenderWidgetHostViewAura::GetViewBounds() const {
return window_->GetBoundsInScreen();
}
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index 77208f6..44e7879 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -367,6 +367,7 @@
friend class DelegatedFrameHostClientAura;
friend class InputMethodAuraTestBase;
friend class RenderWidgetHostViewAuraTest;
+ friend class RenderWidgetHostViewAuraBrowserTest;
friend class RenderWidgetHostViewAuraCopyRequestTest;
friend class TestInputMethodObserver;
#if defined(OS_WIN)
@@ -463,6 +464,12 @@
void CreateAuraWindow(aura::client::WindowType type);
+ // Returns true if a stale frame content needs to be set for the current RWHV.
+ // This is primarily useful during various CrOS animations to prevent showing
+ // a white screen and instead showing a snapshot of the frame content that
+ // was most recently evicted.
+ bool ShouldShowStaleContentOnEviction();
+
void CreateDelegatedFrameHostClient();
void UpdateCursorIfOverSelf();
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
new file mode 100644
index 0000000..cba3ee9
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
@@ -0,0 +1,196 @@
+// Copyright 2018 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 "content/browser/renderer_host/render_widget_host_view_aura.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/browser/renderer_host/delegated_frame_host.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+
+namespace content {
+namespace {
+
+#if defined(OS_CHROMEOS)
+const char kMinimalPageDataURL[] =
+ "data:text/html,<html><head></head><body>Hello, world</body></html>";
+
+// Run the current message loop for a short time without unwinding the current
+// call stack.
+void GiveItSomeTime() {
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(),
+ base::TimeDelta::FromMilliseconds(250));
+ run_loop.Run();
+}
+#endif // defined(OS_CHROMEOS)
+
+class FakeWebContentsDelegate : public WebContentsDelegate {
+ public:
+ FakeWebContentsDelegate() = default;
+ ~FakeWebContentsDelegate() override = default;
+
+ void SetShowStaleContentOnEviction(bool value) {
+ show_stale_content_on_eviction_ = value;
+ }
+
+ bool ShouldShowStaleContentOnEviction(WebContents* source) override {
+ return show_stale_content_on_eviction_;
+ }
+
+ private:
+ bool show_stale_content_on_eviction_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeWebContentsDelegate);
+};
+
+} // namespace
+
+class RenderWidgetHostViewAuraBrowserTest : public ContentBrowserTest {
+ public:
+ RenderViewHost* GetRenderViewHost() const {
+ RenderViewHost* const rvh = shell()->web_contents()->GetRenderViewHost();
+ CHECK(rvh);
+ return rvh;
+ }
+
+ RenderWidgetHostViewAura* GetRenderWidgetHostView() const {
+ return static_cast<RenderWidgetHostViewAura*>(
+ GetRenderViewHost()->GetWidget()->GetView());
+ }
+
+ DelegatedFrameHost* GetDelegatedFrameHost() const {
+ return GetRenderWidgetHostView()->delegated_frame_host_.get();
+ }
+};
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionNormal) {
+ NavigateToURL(shell(), GURL(kMinimalPageDataURL));
+
+ // Wait for first frame activation when a surface is embedded.
+ while (!GetDelegatedFrameHost()->HasSavedFrame())
+ GiveItSomeTime();
+
+ FakeWebContentsDelegate delegate;
+ delegate.SetShowStaleContentOnEviction(true);
+ shell()->web_contents()->SetDelegate(&delegate);
+
+ // Initially there should be no stale content set.
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Hide the view and evict the frame. This should trigger a copy of the stale
+ // frame content.
+ GetRenderWidgetHostView()->Hide();
+ static_cast<viz::FrameEvictorClient*>(GetDelegatedFrameHost())
+ ->EvictDelegatedFrame();
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kPendingEvictionRequests);
+
+ // Wait until the stale frame content is copied and set onto the layer.
+ while (!GetDelegatedFrameHost()->stale_content_layer_->has_external_content())
+ GiveItSomeTime();
+
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Unhidding the view should reset the stale content layer to show the new
+ // frame content.
+ GetRenderWidgetHostView()->Show();
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionRejected) {
+ NavigateToURL(shell(), GURL(kMinimalPageDataURL));
+
+ // Wait for first frame activation when a surface is embedded.
+ while (!GetDelegatedFrameHost()->HasSavedFrame())
+ GiveItSomeTime();
+
+ FakeWebContentsDelegate delegate;
+ delegate.SetShowStaleContentOnEviction(true);
+ shell()->web_contents()->SetDelegate(&delegate);
+
+ // Initially there should be no stale content set.
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Hide the view and evict the frame. This should trigger a copy of the stale
+ // frame content.
+ GetRenderWidgetHostView()->Hide();
+ static_cast<viz::FrameEvictorClient*>(GetDelegatedFrameHost())
+ ->EvictDelegatedFrame();
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kPendingEvictionRequests);
+
+ GetRenderWidgetHostView()->Show();
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Wait until the stale frame content is copied and the result callback is
+ // complete.
+ GiveItSomeTime();
+
+ // This should however not set the stale content as the view is visible and
+ // new frames are being submitted.
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
+ StaleFrameContentOnEvictionNone) {
+ NavigateToURL(shell(), GURL(kMinimalPageDataURL));
+
+ // Wait for first frame activation when a surface is embedded.
+ while (!GetDelegatedFrameHost()->HasSavedFrame())
+ GiveItSomeTime();
+
+ FakeWebContentsDelegate delegate;
+ delegate.SetShowStaleContentOnEviction(false);
+ shell()->web_contents()->SetDelegate(&delegate);
+
+ // Initially there should be no stale content set.
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Hide the view and evict the frame. This should not trigger a copy of the
+ // stale frame content as the WebContentDelegate returns false.
+ GetRenderWidgetHostView()->Hide();
+ static_cast<viz::FrameEvictorClient*>(GetDelegatedFrameHost())
+ ->EvictDelegatedFrame();
+
+ EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
+ DelegatedFrameHost::FrameEvictionState::kNotStarted);
+
+ // Wait for a while to ensure any copy requests that were sent out are not
+ // completed. There shouldnt be any requests sent however.
+ GiveItSomeTime();
+ EXPECT_FALSE(
+ GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
+}
+#endif // #if defined(OS_CHROMEOS)
+
+} // namespace content
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 4bdfeba..88acf7d 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2508,6 +2508,10 @@
return delegate_ ? delegate_->IsFullscreenForTabOrPending(this) : false;
}
+bool WebContentsImpl::ShouldShowStaleContentOnEviction() {
+ return GetDelegate() && GetDelegate()->ShouldShowStaleContentOnEviction(this);
+}
+
bool WebContentsImpl::IsFullscreen() {
return IsFullscreenForCurrentTab();
}
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 3e652d8..d349a650 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -450,6 +450,7 @@
bool WasEverAudible() override;
void GetManifest(GetManifestCallback callback) override;
bool IsFullscreenForCurrentTab() override;
+ bool ShouldShowStaleContentOnEviction() override;
void ExitFullscreen(bool will_cause_resize) override;
void ResumeLoadingCreatedWebContents() override;
void SetIsOverlayContent(bool is_overlay_content) override;
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index 8981e93..f7ac4c2 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -300,4 +300,8 @@
return new_contents;
}
+bool WebContentsDelegate::ShouldShowStaleContentOnEviction(
+ WebContents* source) {
+ return false;
+}
} // namespace content
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index d7ce001..bdef12d 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -641,6 +641,11 @@
bool did_start_load,
bool did_finish_load);
+ // Returns true if the widget's frame content needs to be stored before
+ // eviction and displayed until a new frame is generated. If false, a white
+ // solid color is displayed instead.
+ virtual bool ShouldShowStaleContentOnEviction(WebContents* source);
+
protected:
virtual ~WebContentsDelegate();
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3df2eb0..925911a 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -861,6 +861,7 @@
"../browser/renderer_host/render_process_host_browsertest.cc",
"../browser/renderer_host/render_view_host_browsertest.cc",
"../browser/renderer_host/render_widget_host_browsertest.cc",
+ "../browser/renderer_host/render_widget_host_view_aura_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_mac_browsertest.mm",
@@ -1270,6 +1271,7 @@
sources -= [
"../browser/accessibility/touch_accessibility_aura_browsertest.cc",
"../browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc",
+ "../browser/renderer_host/render_widget_host_view_aura_browsertest.cc",
"../browser/web_contents/web_contents_view_aura_browsertest.cc",
]
}
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index a8aa03c6..b1c724f 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -219,6 +219,17 @@
std::unique_ptr<Layer> Layer::Mirror() {
auto mirror = Clone();
mirrors_.emplace_back(std::make_unique<LayerMirror>(this, mirror.get()));
+
+ if (!transfer_resource_.mailbox_holder.mailbox.IsZero()) {
+ // Send an empty release callback because we don't want the resource to be
+ // freed up until the original layer releases it.
+ mirror->SetTransferableResource(
+ transfer_resource_,
+ viz::SingleReleaseCallback::Create(base::BindOnce(
+ [](const gpu::SyncToken& sync_token, bool is_lost) {})),
+ frame_size_in_dip_);
+ }
+
return mirror;
}
@@ -756,6 +767,16 @@
transfer_release_callback_ = std::move(release_callback);
transfer_resource_ = resource;
SetTextureSize(texture_size_in_dip);
+
+ for (const auto& mirror : mirrors_) {
+ // The release callbacks should be empty as only the source layer
+ // should be able to release the texture resource.
+ mirror->dest()->SetTransferableResource(
+ transfer_resource_,
+ viz::SingleReleaseCallback::Create(base::BindOnce(
+ [](const gpu::SyncToken& sync_token, bool is_lost) {})),
+ frame_size_in_dip_);
+ }
}
void Layer::SetTextureSize(gfx::Size texture_size_in_dip) {
@@ -860,6 +881,9 @@
transfer_release_callback_.reset();
}
RecomputeDrawsContentAndUVRect();
+
+ for (const auto& mirror : mirrors_)
+ mirror->dest()->SetShowSolidColorContent();
}
void Layer::UpdateNinePatchLayerImage(const gfx::ImageSkia& image) {
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index a4eaa87a..c416e42d 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -2052,6 +2052,57 @@
EXPECT_EQ(surface_id, surface->surface_id());
}
+TEST_F(LayerWithDelegateTest, TransferableResourceMirroring) {
+ std::unique_ptr<Layer> layer(CreateLayer(LAYER_SOLID_COLOR));
+
+ auto resource = viz::TransferableResource::MakeGL(
+ gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken());
+ bool release_callback_run = false;
+
+ layer->SetTransferableResource(
+ resource,
+ viz::SingleReleaseCallback::Create(
+ base::BindOnce(ReturnMailbox, &release_callback_run)),
+ gfx::Size(10, 10));
+ EXPECT_FALSE(release_callback_run);
+ EXPECT_TRUE(layer->has_external_content());
+
+ auto mirror = layer->Mirror();
+ EXPECT_TRUE(mirror->has_external_content());
+
+ // Clearing the resource on a mirror layer should not release the source layer
+ // resource.
+ mirror.reset();
+ EXPECT_FALSE(release_callback_run);
+
+ mirror = layer->Mirror();
+ EXPECT_TRUE(mirror->has_external_content());
+
+ // Clearing the transferable resource on the source layer should clear it from
+ // the mirror layer as well.
+ layer->SetShowSolidColorContent();
+ EXPECT_TRUE(release_callback_run);
+ EXPECT_FALSE(layer->has_external_content());
+ EXPECT_FALSE(mirror->has_external_content());
+
+ resource = viz::TransferableResource::MakeGL(
+ gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken());
+ release_callback_run = false;
+
+ // Setting a transferable resource on the source layer should set it on the
+ // mirror layers as well.
+ layer->SetTransferableResource(
+ resource,
+ viz::SingleReleaseCallback::Create(
+ base::BindOnce(ReturnMailbox, &release_callback_run)),
+ gfx::Size(10, 10));
+ EXPECT_FALSE(release_callback_run);
+ EXPECT_TRUE(layer->has_external_content());
+ EXPECT_TRUE(mirror->has_external_content());
+
+ layer.reset();
+}
+
// Verifies that layer filters still attached after changing implementation
// layer.
TEST_F(LayerWithDelegateTest, LayerFiltersSurvival) {