Fix thumbnail capture timing.
Thumbnails are now captured at a time that makes sense for tab previews
rather than the old NTP timing (which was largely when switching away
from or leaving a page).
Bug: 928954
Change-Id: I79e2ae1bd066de753a6f6b592639f0db09f35fd3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1531640
Commit-Queue: Dana Fried <dfried@chromium.org>
Reviewed-by: Collin Baker <collinbaker@chromium.org>
Cr-Commit-Position: refs/heads/master@{#642920}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index f29cbc5..ed58c47 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1062,6 +1062,8 @@
"thumbnails/thumbnail_tab_helper.h",
"thumbnails/thumbnail_utils.cc",
"thumbnails/thumbnail_utils.h",
+ "thumbnails/thumbnail_web_contents_observer.cc",
+ "thumbnails/thumbnail_web_contents_observer.h",
"toolbar/app_menu_icon_controller.cc",
"toolbar/app_menu_icon_controller.h",
"toolbar/app_menu_model.cc",
diff --git a/chrome/browser/ui/thumbnails/thumbnail_image.cc b/chrome/browser/ui/thumbnails/thumbnail_image.cc
index d139e210..b71fd63d 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_image.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_image.cc
@@ -66,11 +66,10 @@
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
- {base::TaskPriority::BEST_EFFORT,
+ {base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::ToImageSkia, image_representation_),
std::move(callback));
-
return true;
}
@@ -98,7 +97,7 @@
CreateThumbnailCallback callback) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
- {base::TaskPriority::BEST_EFFORT,
+ {base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::FromSkBitmap, bitmap),
base::BindOnce(
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index ac77bea..06ff85b1 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -5,277 +5,223 @@
#include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h"
#include "base/bind.h"
-#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/tabs/tab_style.h"
#include "chrome/browser/ui/thumbnails/thumbnail_utils.h"
-#include "chrome/common/chrome_features.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.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 "content/public/browser/web_contents.h"
#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/color_utils.h"
#include "ui/gfx/scrollbar_size.h"
ThumbnailTabHelper::ThumbnailTabHelper(content::WebContents* contents)
- : content::WebContentsObserver(contents) {}
+ : ThumbnailWebContentsObserver(contents),
+ view_is_visible_(contents->GetVisibility() ==
+ content::Visibility::VISIBLE) {}
ThumbnailTabHelper::~ThumbnailTabHelper() = default;
-void ThumbnailTabHelper::RenderWidgetHostVisibilityChanged(
- content::RenderWidgetHost* widget_host,
- bool became_visible) {
- if (!became_visible)
- TabHidden();
+void ThumbnailTabHelper::TopLevelNavigationStarted(const GURL& url) {
+ UpdateCurrentUrl(url);
}
-void ThumbnailTabHelper::RenderWidgetHostDestroyed(
- content::RenderWidgetHost* widget_host) {
- observer_.Remove(widget_host);
+void ThumbnailTabHelper::TopLevelNavigationEnded(const GURL& url) {
+ UpdateCurrentUrl(url);
}
-void ThumbnailTabHelper::RenderViewCreated(
- content::RenderViewHost* render_view_host) {
- StartWatchingRenderViewHost(render_view_host);
-}
-
-void ThumbnailTabHelper::RenderViewHostChanged(
- content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) {
- StopWatchingRenderViewHost(old_host);
- StartWatchingRenderViewHost(new_host);
-}
-
-void ThumbnailTabHelper::RenderViewDeleted(
- content::RenderViewHost* render_view_host) {
- StopWatchingRenderViewHost(render_view_host);
-}
-
-void ThumbnailTabHelper::DidStartNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!navigation_handle->IsInMainFrame() ||
- navigation_handle->IsSameDocument()) {
- return;
- }
-
- // At this point, the new navigation has just been started, but the
- // WebContents still shows the previous page. Grab a thumbnail before it
- // goes away.
- StartThumbnailCaptureIfNecessary(TriggerReason::NAVIGATING_AWAY);
-
- // Now reset navigation-related state. It's important that this happens after
- // calling StartThumbnailCaptureIfNecessary.
- did_navigation_finish_ = false;
- has_received_document_since_navigation_finished_ = false;
- has_painted_since_document_received_ = false;
- // Reset the page transition to some uninteresting type, since the actual
- // type isn't available at this point. We'll get it in DidFinishNavigation
- // (if that happens, which isn't guaranteed).
- page_transition_ = ui::PAGE_TRANSITION_LINK;
-}
-
-void ThumbnailTabHelper::DidFinishNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!navigation_handle->HasCommitted() ||
- !navigation_handle->IsInMainFrame() ||
- navigation_handle->IsSameDocument()) {
- return;
- }
- did_navigation_finish_ = true;
- page_transition_ = navigation_handle->GetPageTransition();
-}
-
-void ThumbnailTabHelper::DocumentAvailableInMainFrame() {
- // If there's currently a screen capture going on, ignore its result.
- // Otherwise there's a risk that we'll get a picture of the wrong page.
- // Note: It *looks* like WebContentsObserver::DidFirstVisuallyNonEmptyPaint
- // would be a better signal for this, but it uses a weird heuristic to detect
- // "visually non empty" paints, so it might not be entirely safe.
- waiting_for_capture_ = false;
-
- // Mark that we got the document, unless we're in the middle of a navigation.
- // In that case, this refers to the previous document, but we're tracking the
- // state of the new one.
- if (did_navigation_finish_) {
- // From now on, we'll start watching for paint events.
- has_received_document_since_navigation_finished_ = true;
+void ThumbnailTabHelper::UpdateCurrentUrl(const GURL& url) {
+ current_url_ = url;
+ if (current_url_ != thumbnail_url_ &&
+ thumbnail_state_ != ThumbnailState::kNoThumbnail) {
+ thumbnail_state_ = ThumbnailState::kNoThumbnail;
+ thumbnail_ = ThumbnailImage();
+ NotifyTabPreviewChanged();
}
}
-void ThumbnailTabHelper::DocumentOnLoadCompletedInMainFrame() {
- // Usually, DocumentAvailableInMainFrame always gets called first, so this one
- // shouldn't be necessary. However, DocumentAvailableInMainFrame is not fired
- // for empty documents (i.e. about:blank), which are thus handled here.
- DocumentAvailableInMainFrame();
+void ThumbnailTabHelper::PageLoadStarted(FrameContext frame_context) {
+ if (frame_context == FrameContext::kMainFrame)
+ is_loading_ = true;
+ ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
}
-void ThumbnailTabHelper::DidFirstVisuallyNonEmptyPaint() {
- // If we haven't gotten the current document since navigating, then this paint
- // refers to the *previous* document, so ignore it.
- if (has_received_document_since_navigation_finished_) {
- has_painted_since_document_received_ = true;
+void ThumbnailTabHelper::PageLoadFinished(FrameContext frame_context) {
+ if (frame_context == FrameContext::kMainFrame)
+ is_loading_ = false;
+ ScheduleThumbnailCapture(CaptureSchedule::kAttemptImmediate);
+}
+
+void ThumbnailTabHelper::PageUpdated(FrameContext frame_context) {
+ ScheduleThumbnailCapture(CaptureSchedule::kAttemptImmediate);
+}
+
+void ThumbnailTabHelper::VisibilityChanged(bool visible) {
+ // When the visibility of the current tab changes (most importantly, when the
+ // user is switching away from the current tab) we want to capture a snapshot
+ // of the tab to capture e.g. its scroll position, so that the preview will
+ // look like the tab did when the user last switched to/from it.
+ const bool was_visible = view_is_visible_;
+ view_is_visible_ = visible;
+ if (was_visible != visible) {
+ // Because it can take a moment for tabs to re-render, use a delay when
+ // returning to a tab, but capture immediately when switching away.
+ const CaptureSchedule schedule =
+ visible ? CaptureSchedule::kDelayed : CaptureSchedule::kImmediate;
+ ScheduleThumbnailCapture(schedule);
}
}
-void ThumbnailTabHelper::DidStartLoading() {
- load_interrupted_ = false;
-}
-
-void ThumbnailTabHelper::NavigationStopped() {
- // This function gets called when the page loading is interrupted by the
- // stop button.
- load_interrupted_ = true;
-}
-
-void ThumbnailTabHelper::StartWatchingRenderViewHost(
- content::RenderViewHost* render_view_host) {
- // We get notified whenever a new RenderView is created, which does not
- // necessarily come with a new RenderViewHost, and there is no good way to get
- // notifications of new RenderViewHosts only. So just be tolerant of
- // re-registrations.
- content::RenderWidgetHost* render_widget_host = render_view_host->GetWidget();
- if (!observer_.IsObserving(render_widget_host))
- observer_.Add(render_widget_host);
-}
-
-void ThumbnailTabHelper::StopWatchingRenderViewHost(
- content::RenderViewHost* render_view_host) {
- if (!render_view_host) {
- return;
- }
-
- content::RenderWidgetHost* render_widget_host = render_view_host->GetWidget();
- if (observer_.IsObserving(render_widget_host))
- observer_.Remove(render_widget_host);
-}
-
-void ThumbnailTabHelper::StartThumbnailCaptureIfNecessary(
- TriggerReason trigger) {
+void ThumbnailTabHelper::ScheduleThumbnailCapture(CaptureSchedule schedule) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- // Don't take a screenshot if we haven't painted anything since the last
- // navigation. This can happen when navigating away again very quickly.
- if (!has_painted_since_document_received_) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_PAINT_YET);
+ if (schedule != CaptureSchedule::kImmediate && !view_is_visible_)
+ return;
+
+ constexpr base::TimeDelta kDelayTime = base::TimeDelta::FromMilliseconds(250);
+ constexpr base::TimeDelta kMinTimeBetweenCaptures =
+ base::TimeDelta::FromMilliseconds(500);
+
+ // We will do the capture either now or at some point in the future.
+ base::TimeDelta delay;
+ if (schedule == CaptureSchedule::kDelayed)
+ delay += kDelayTime;
+
+ // The time until the next scheduled capture, or the time since the most
+ // recent (durations in the past are negative).
+ const base::TimeDelta until_scheduled =
+ last_scheduled_capture_time_ - base::TimeTicks::Now();
+
+ // If we would schedule a non-immediate capture too close to an existing
+ // capture, push it out or discard it altogether.
+ if (schedule != CaptureSchedule::kImmediate &&
+ delay - until_scheduled < kMinTimeBetweenCaptures) {
+ if (until_scheduled > delay)
+ return;
+ delay = until_scheduled + kMinTimeBetweenCaptures;
+ }
+
+ last_scheduled_capture_time_ = base::TimeTicks::Now() + delay;
+
+ if (delay.is_zero()) {
+ StartThumbnailCapture(schedule);
return;
}
- // Ignore thumbnail update requests if one is already in progress.
- if (thumbnailing_in_progress_) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_IN_PROGRESS);
+ base::PostDelayedTaskWithTraits(
+ FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(&ThumbnailTabHelper::StartThumbnailCapture,
+ weak_factory_.GetWeakPtr(), schedule),
+ delay);
+}
+
+void ThumbnailTabHelper::StartThumbnailCapture(CaptureSchedule schedule) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ CaptureInfo capture_info{web_contents()->GetVisibleURL(),
+ is_loading_ ? ThumbnailState::kLoadInProgress
+ : ThumbnailState::kFinishedLoading};
+ DCHECK(!capture_info.url.is_empty());
+
+ if (!view_is_visible_ && schedule != CaptureSchedule::kImmediate)
return;
- }
// Destroying a WebContents may trigger it to be hidden, prompting a snapshot
// which would be unwise to attempt <http://crbug.com/130097>. If the
// WebContents is in the middle of destruction, do not risk it.
- if (!web_contents() || web_contents()->IsBeingDestroyed()) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_WEBCONTENTS);
+ if (!web_contents() || web_contents()->IsBeingDestroyed())
return;
- }
- // Note: Do *not* use GetLastVisibleURL - it might already have been updated
- // for a new pending navigation. The committed URL is the one corresponding
- // to the currently visible content.
- const GURL& url = web_contents()->GetLastCommittedURL();
- if (!url.is_valid()) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_URL);
- return;
- }
+ base::TimeTicks start_time = base::TimeTicks::Now();
- // Check if the thumbnail needs to be updated. If not, log and return.
+ content::RenderWidgetHostView* const source_view =
+ web_contents()->GetRenderViewHost()->GetWidget()->GetView();
- content::RenderWidgetHost* render_widget_host =
- web_contents()->GetRenderViewHost()->GetWidget();
- content::RenderWidgetHostView* view = render_widget_host->GetView();
- if (!view || !view->IsSurfaceAvailableForCopy()) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_VIEW_NOT_AVAILABLE);
+ // If there's no view or the view isn't available right now, put off
+ // capturing.
+ if (!source_view || !source_view->IsSurfaceAvailableForCopy()) {
+ ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
return;
}
// Note: this is the size in pixels on-screen, not the size in DIPs.
- gfx::Size source_size = view->GetViewBounds().size();
+ gfx::Size source_size = source_view->GetViewBounds().size();
// Clip the pixels that will commonly hold a scrollbar, which looks bad in
// thumbnails.
- const float scale_factor = view->GetDeviceScaleFactor();
+ const float scale_factor = source_view->GetDeviceScaleFactor();
const int scrollbar_size = gfx::scrollbar_size() * scale_factor;
source_size.Enlarge(-scrollbar_size, -scrollbar_size);
- if (source_size.IsEmpty()) {
- LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_EMPTY_RECT);
+ if (source_size.IsEmpty())
return;
- }
-
- thumbnailing_in_progress_ = true;
const gfx::Size desired_size = TabStyle::GetPreviewImageSize();
thumbnails::CanvasCopyInfo copy_info =
thumbnails::GetCanvasCopyInfo(source_size, scale_factor, desired_size);
- copy_from_surface_start_time_ = base::TimeTicks::Now();
- waiting_for_capture_ = true;
- view->CopyFromSurface(
+
+ source_view->CopyFromSurface(
copy_info.copy_rect, copy_info.target_size,
- base::BindOnce(&ThumbnailTabHelper::ProcessCapturedBitmap,
- weak_factory_.GetWeakPtr(), trigger));
+ base::BindOnce(&ThumbnailTabHelper::ProcessCapturedThumbnail,
+ weak_factory_.GetWeakPtr(), capture_info, start_time));
}
-void ThumbnailTabHelper::ProcessCapturedBitmap(TriggerReason trigger,
- const SkBitmap& bitmap) {
- // If |waiting_for_capture_| is false, that means something happened in the
- // meantime which makes the captured image unsafe to use.
- bool was_canceled = !waiting_for_capture_;
- waiting_for_capture_ = false;
+void ThumbnailTabHelper::ProcessCapturedThumbnail(
+ const CaptureInfo& capture_info,
+ base::TimeTicks start_time,
+ const SkBitmap& bitmap) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- base::TimeDelta copy_from_surface_time =
- base::TimeTicks::Now() - copy_from_surface_start_time_;
+ DCHECK(!capture_info.url.is_empty());
+ DCHECK(capture_info.target_state != ThumbnailState::kNoThumbnail);
+
+ const base::TimeTicks finish_time = base::TimeTicks::Now();
+ const base::TimeDelta copy_from_surface_time = finish_time - start_time;
UMA_HISTOGRAM_TIMES("Thumbnails.CopyFromSurfaceTime", copy_from_surface_time);
- if (!bitmap.drawsNothing() && !was_canceled) {
- // On success, we must be on the UI thread.
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- // From here on, nothing can fail, so log success.
- LogThumbnailingOutcome(trigger, Outcome::SUCCESS);
- thumbnail_ = ThumbnailImage::FromSkBitmap(bitmap);
- web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
- } else {
- LogThumbnailingOutcome(
- trigger, was_canceled ? Outcome::CANCELED : Outcome::READBACK_FAILED);
- }
- thumbnailing_in_progress_ = false;
-}
-
-void ThumbnailTabHelper::TabHidden() {
- // Skip if a pending entry exists. TabHidden can be called while navigating
- // pages and this is not a time when thumbnails should be generated.
- if (!web_contents() || web_contents()->GetController().GetPendingEntry()) {
- LogThumbnailingOutcome(TriggerReason::TAB_HIDDEN,
- Outcome::NOT_ATTEMPTED_PENDING_NAVIGATION);
+ if (bitmap.drawsNothing()) {
+ // TODO(dfried): Log capture failed.
+ MaybeScheduleAnotherCapture(capture_info, finish_time);
return;
}
- StartThumbnailCaptureIfNecessary(TriggerReason::TAB_HIDDEN);
+
+ // TODO(dfried): Log capture succeeded.
+ ThumbnailImage::FromSkBitmapAsync(
+ bitmap,
+ base::BindOnce(&ThumbnailTabHelper::StoreThumbnail,
+ weak_factory_.GetWeakPtr(), capture_info, finish_time));
}
-// static
-void ThumbnailTabHelper::LogThumbnailingOutcome(TriggerReason trigger,
- Outcome outcome) {
- UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome", outcome,
- Outcome::COUNT);
+void ThumbnailTabHelper::StoreThumbnail(const CaptureInfo& capture_info,
+ base::TimeTicks start_time,
+ ThumbnailImage thumbnail) {
+ DCHECK(thumbnail.HasData());
+ const base::TimeTicks finish_time = base::TimeTicks::Now();
+ const base::TimeDelta process_time = finish_time - start_time;
+ UMA_HISTOGRAM_TIMES("Thumbnails.ProcessBitmapTime", process_time);
+ thumbnail_state_ = capture_info.target_state;
+ thumbnail_url_ = capture_info.url;
+ thumbnail_ = thumbnail;
- switch (trigger) {
- case TriggerReason::TAB_HIDDEN:
- UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome.TabHidden", outcome,
- Outcome::COUNT);
- break;
- case TriggerReason::NAVIGATING_AWAY:
- UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome.NavigatingAway",
- outcome, Outcome::COUNT);
- break;
+ NotifyTabPreviewChanged();
+ MaybeScheduleAnotherCapture(capture_info, finish_time);
+}
+
+void ThumbnailTabHelper::NotifyTabPreviewChanged() {
+ web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+}
+
+void ThumbnailTabHelper::MaybeScheduleAnotherCapture(
+ const CaptureInfo& capture_info,
+ base::TimeTicks finish_time) {
+ // If the page is still loading, schedule another capture a short time later.
+ if (capture_info.target_state != ThumbnailState::kFinishedLoading &&
+ finish_time > last_scheduled_capture_time_) {
+ ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
}
}
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
index a4fe69ec..687d8abd 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
@@ -7,107 +7,76 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
#include "base/time/time.h"
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
-#include "content/public/browser/render_widget_host_observer.h"
-#include "content/public/browser/web_contents_observer.h"
+#include "chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
-#include "ui/base/page_transition_types.h"
-
-namespace content {
-class NavigationHandle;
-class RenderViewHost;
-} // namespace content
class ThumbnailTabHelper
- : public content::RenderWidgetHostObserver,
- public content::WebContentsObserver,
+ : public ThumbnailWebContentsObserver,
public content::WebContentsUserData<ThumbnailTabHelper> {
public:
+ enum class ThumbnailState {
+ kNoThumbnail, // no thumbnail is available
+ kLoadInProgress, // thumbnail available but is of a page that is loading
+ kFinishedLoading // thumbnail should represent the finished page
+ };
+
~ThumbnailTabHelper() override;
+ ThumbnailState thumbnail_state() const { return thumbnail_state_; }
ThumbnailImage thumbnail() const { return thumbnail_; }
+ protected:
+ // ThumbnailWebContentsObserver:
+ void TopLevelNavigationStarted(const GURL& url) override;
+ void TopLevelNavigationEnded(const GURL& url) override;
+ void PageLoadStarted(FrameContext frame_context) override;
+ void PageLoadFinished(FrameContext frame_context) override;
+ void PageUpdated(FrameContext frame_context) override;
+ void VisibilityChanged(bool visible) override;
+
private:
+ enum class CaptureSchedule { kImmediate, kAttemptImmediate, kDelayed };
+
+ struct CaptureInfo {
+ GURL url;
+ ThumbnailState target_state;
+ };
+
explicit ThumbnailTabHelper(content::WebContents* contents);
friend class content::WebContentsUserData<ThumbnailTabHelper>;
- enum class TriggerReason {
- TAB_HIDDEN,
- NAVIGATING_AWAY,
- };
+ void UpdateCurrentUrl(const GURL& url);
+ void ScheduleThumbnailCapture(CaptureSchedule schedule);
+ void StartThumbnailCapture(CaptureSchedule schedule);
+ void ProcessCapturedThumbnail(const CaptureInfo& capture_info,
+ base::TimeTicks start_time,
+ const SkBitmap& bitmap);
+ void StoreThumbnail(const CaptureInfo& capture_info,
+ base::TimeTicks start_time,
+ ThumbnailImage thumbnail);
+ void NotifyTabPreviewChanged();
- // Used for UMA histograms. Don't change or delete entries, and only add new
- // ones at the end.
- enum class Outcome {
- SUCCESS = 0,
- NOT_ATTEMPTED_PENDING_NAVIGATION,
- NOT_ATTEMPTED_NO_PAINT_YET,
- NOT_ATTEMPTED_IN_PROGRESS,
- NOT_ATTEMPTED_NO_WEBCONTENTS,
- NOT_ATTEMPTED_NO_URL,
- NOT_ATTEMPTED_SHOULD_NOT_ACQUIRE,
- NOT_ATTEMPTED_VIEW_NOT_AVAILABLE,
- NOT_ATTEMPTED_EMPTY_RECT,
- CANCELED,
- READBACK_FAILED,
- // Add new entries here!
- COUNT
- };
-
- // content::RenderWidgetHostObserver overrides.
- void RenderWidgetHostVisibilityChanged(content::RenderWidgetHost* widget_host,
- bool became_visible) override;
- void RenderWidgetHostDestroyed(
- content::RenderWidgetHost* widget_host) override;
-
- // content::WebContentsObserver overrides.
- void RenderViewCreated(content::RenderViewHost* render_view_host) override;
- void RenderViewHostChanged(content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) override;
- void RenderViewDeleted(content::RenderViewHost* render_view_host) override;
- void DidStartNavigation(
- content::NavigationHandle* navigation_handle) override;
- void DidFinishNavigation(
- content::NavigationHandle* navigation_handle) override;
- void DocumentAvailableInMainFrame() override;
- void DocumentOnLoadCompletedInMainFrame() override;
- void DidFirstVisuallyNonEmptyPaint() override;
- void DidStartLoading() override;
- void NavigationStopped() override;
-
- void StartWatchingRenderViewHost(content::RenderViewHost* render_view_host);
- void StopWatchingRenderViewHost(content::RenderViewHost* render_view_host);
-
- // Starts the process of capturing a thumbnail of the current tab contents if
- // necessary and possible.
- void StartThumbnailCaptureIfNecessary(TriggerReason trigger);
-
- // Creates a thumbnail from the web contents bitmap.
- void ProcessCapturedBitmap(TriggerReason trigger, const SkBitmap& bitmap);
-
- // Called when the current tab gets hidden.
- void TabHidden();
-
- static void LogThumbnailingOutcome(TriggerReason trigger, Outcome outcome);
-
- bool did_navigation_finish_ = false;
- bool has_received_document_since_navigation_finished_ = false;
- bool has_painted_since_document_received_ = false;
-
- ui::PageTransition page_transition_ = ui::PAGE_TRANSITION_LINK;
- bool load_interrupted_ = false;
-
- bool thumbnailing_in_progress_ = false;
- bool waiting_for_capture_ = false;
-
- base::TimeTicks copy_from_surface_start_time_;
+ // For tabs in the process of loading, schedules another capture if none is
+ // currently queued.
+ void MaybeScheduleAnotherCapture(const CaptureInfo& capture_info,
+ base::TimeTicks finish_time);
ThumbnailImage thumbnail_;
+ ThumbnailState thumbnail_state_ = ThumbnailState::kNoThumbnail;
+ GURL thumbnail_url_;
- ScopedObserver<content::RenderWidgetHost, content::RenderWidgetHostObserver>
- observer_{this};
+ // Caches whether or not the web contents view is visible. See notes in
+ // VisibilityChanged() for more information.
+ bool view_is_visible_; // set in constructor
+ bool is_loading_ = false;
+ GURL current_url_;
+
+ // The time that the most recently-scheduled capture is/was scheduled for.
+ // Can be in the past. Used to prevent captures from bunching up or being
+ // scheduled in the wrong order.
+ base::TimeTicks last_scheduled_capture_time_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
diff --git a/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc
new file mode 100644
index 0000000..35a75f4
--- /dev/null
+++ b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc
@@ -0,0 +1,93 @@
+// Copyright 2019 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 "chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h"
+
+#include "content/public/browser/navigation_handle.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 "content/public/browser/web_contents.h"
+
+ThumbnailWebContentsObserver::ThumbnailWebContentsObserver(
+ content::WebContents* contents)
+ : content::WebContentsObserver(contents) {}
+
+ThumbnailWebContentsObserver::~ThumbnailWebContentsObserver() = default;
+
+void ThumbnailWebContentsObserver::OnVisibilityChanged(
+ content::Visibility visibility) {
+ VisibilityChanged(visibility == content::Visibility::VISIBLE);
+}
+
+void ThumbnailWebContentsObserver::DidStartNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (navigation_handle->IsInMainFrame() &&
+ !navigation_handle->IsSameDocument()) {
+ TopLevelNavigationStarted(
+ navigation_handle->GetWebContents()->GetVisibleURL());
+ }
+}
+
+void ThumbnailWebContentsObserver::DidRedirectNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (navigation_handle->IsInMainFrame() &&
+ !navigation_handle->IsSameDocument()) {
+ TopLevelNavigationStarted(
+ navigation_handle->GetWebContents()->GetVisibleURL());
+ }
+}
+
+void ThumbnailWebContentsObserver::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (navigation_handle->IsInMainFrame()) {
+ TopLevelNavigationEnded(
+ navigation_handle->GetWebContents()->GetVisibleURL());
+ }
+}
+
+void ThumbnailWebContentsObserver::DidStartLoading() {
+ PageLoadStarted(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::DidStopLoading() {
+ PageLoadFinished(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::DidFinishLoad(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url) {
+ PageLoadFinished(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::DidFailLoad(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description) {
+ PageLoadFinished(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::MainFrameWasResized(bool width_changed) {
+ PageUpdated(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::FrameSizeChanged(
+ content::RenderFrameHost* render_frame_host,
+ const gfx::Size& frame_size) {
+ PageUpdated(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::NavigationStopped() {
+ TopLevelNavigationEnded(web_contents()->GetVisibleURL());
+ PageLoadFinished(FrameContext::kMainFrame);
+}
+
+// static
+ThumbnailWebContentsObserver::FrameContext
+ThumbnailWebContentsObserver::ContextFromRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ return render_frame_host->GetParent() ? FrameContext::kChildFrame
+ : FrameContext::kMainFrame;
+}
diff --git a/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h
new file mode 100644
index 0000000..285fe1e
--- /dev/null
+++ b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h
@@ -0,0 +1,81 @@
+// Copyright 2019 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 CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_
+#define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "url/gurl.h"
+
+namespace content {
+class NavigationHandle;
+class RenderFrameHost;
+} // namespace content
+
+// Base class for thumbnail tab helper; processes specific web contents events
+// into a filtered-down set of navigation and loading events for ease of
+// processing.
+class ThumbnailWebContentsObserver : public content::WebContentsObserver {
+ public:
+ ~ThumbnailWebContentsObserver() override;
+
+ protected:
+ enum class FrameContext { kMainFrame, kChildFrame };
+
+ explicit ThumbnailWebContentsObserver(content::WebContents* contents);
+
+ // Called when navigation in the top-level browser window starts.
+ virtual void TopLevelNavigationStarted(const GURL& url) = 0;
+ // Called when navigation in the top-level browser window completes.
+ virtual void TopLevelNavigationEnded(const GURL& url) = 0;
+ // Called when the page/tab's visibility changes.
+ // If |view_is_valid| is false, no attempt should be made to read from the
+ // contents pane.
+ virtual void VisibilityChanged(bool visible) = 0;
+ // Called when a page begins to load.
+ virtual void PageLoadStarted(FrameContext frame_context) = 0;
+ // Called when a page finishes loading.
+ virtual void PageLoadFinished(FrameContext frame_context) = 0;
+ // Called when the page is resized or otherwise updated.
+ virtual void PageUpdated(FrameContext frame_context) = 0;
+
+ void OnVisibilityChanged(content::Visibility visibility) override;
+
+ // Track when navigation happens so that we know when a thumbnail is no longer
+ // valid (thumbnail will still be valid for the old URL until the new
+ // navigation has committed and the new page render occurs).
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void DidRedirectNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+
+ // Track the progress of loading. Thumbnails should be captured during the
+ // loading process, since some pages take a long time to load, but there is no
+ // point to capturing a thumbnail of a page that has not rendered anything
+ // yet.
+ void DidStartLoading() override;
+ void DidStopLoading() override;
+ void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url) override;
+ void DidFailLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description) override;
+ void NavigationStopped() override;
+ void MainFrameWasResized(bool width_changed) override;
+ void FrameSizeChanged(content::RenderFrameHost* render_frame_host,
+ const gfx::Size& frame_size) override;
+
+ private:
+ static FrameContext ContextFromRenderFrameHost(
+ content::RenderFrameHost* render_frame_host);
+
+ DISALLOW_COPY_AND_ASSIGN(ThumbnailWebContentsObserver);
+};
+
+#endif // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_