1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/frame_host/navigation_entry_screenshot_manager.h"
6
7#include "base/command_line.h"
8#include "base/threading/worker_pool.h"
9#include "content/browser/frame_host/navigation_controller_impl.h"
10#include "content/browser/frame_host/navigation_entry_impl.h"
11#include "content/browser/renderer_host/render_view_host_impl.h"
12#include "content/public/browser/overscroll_configuration.h"
13#include "content/public/browser/render_widget_host.h"
14#include "content/public/browser/render_widget_host_view.h"
15#include "content/public/common/content_switches.h"
16#include "ui/gfx/codec/png_codec.h"
17
18namespace {
19
20// Minimum delay between taking screenshots.
21const int kMinScreenshotIntervalMS = 1000;
22
23}
24
25namespace content {
26
27// Encodes the A8 SkBitmap to grayscale PNG in a worker thread.
28class ScreenshotData : public base::RefCountedThreadSafe<ScreenshotData> {
29 public:
30  ScreenshotData() {
31  }
32
33  void EncodeScreenshot(const SkBitmap& bitmap, base::Closure callback) {
34    if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
35            base::Bind(&ScreenshotData::EncodeOnWorker,
36                       this,
37                       bitmap),
38            callback,
39            true)) {
40      callback.Run();
41    }
42  }
43
44  scoped_refptr<base::RefCountedBytes> data() const { return data_; }
45
46 private:
47  friend class base::RefCountedThreadSafe<ScreenshotData>;
48  virtual ~ScreenshotData() {
49  }
50
51  void EncodeOnWorker(const SkBitmap& bitmap) {
52    DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType);
53    // Encode the A8 bitmap to grayscale PNG treating alpha as color intensity.
54    std::vector<unsigned char> data;
55    if (gfx::PNGCodec::EncodeA8SkBitmap(bitmap, &data))
56      data_ = new base::RefCountedBytes(data);
57  }
58
59  scoped_refptr<base::RefCountedBytes> data_;
60
61  DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
62};
63
64NavigationEntryScreenshotManager::NavigationEntryScreenshotManager(
65    NavigationControllerImpl* owner)
66    : owner_(owner),
67      min_screenshot_interval_ms_(kMinScreenshotIntervalMS),
68      screenshot_factory_(this) {
69
70}
71
72NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() {
73}
74
75void NavigationEntryScreenshotManager::TakeScreenshot() {
76  static bool overscroll_enabled = base::CommandLine::ForCurrentProcess()->
77      GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
78  if (!overscroll_enabled)
79    return;
80
81  NavigationEntryImpl* entry =
82      NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
83  if (!entry)
84    return;
85
86  if (!owner_->delegate()->CanOverscrollContent())
87    return;
88
89  RenderViewHost* render_view_host =
90      owner_->delegate()->GetRenderViewHost();
91  content::RenderWidgetHostView* view = render_view_host->GetView();
92  if (!view)
93    return;
94
95  // Make sure screenshots aren't taken too frequently.
96  base::Time now = base::Time::Now();
97  if (now - last_screenshot_time_ <
98          base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) {
99    return;
100  }
101
102  last_screenshot_time_ = now;
103
104  TakeScreenshotImpl(render_view_host, entry);
105}
106
107// Implemented here and not in NavigationEntry because this manager keeps track
108// of the total number of screen shots across all entries.
109void NavigationEntryScreenshotManager::ClearAllScreenshots() {
110  int count = owner_->GetEntryCount();
111  for (int i = 0; i < count; ++i) {
112    ClearScreenshot(NavigationEntryImpl::FromNavigationEntry(
113        owner_->GetEntryAtIndex(i)));
114  }
115  DCHECK_EQ(GetScreenshotCount(), 0);
116}
117
118void NavigationEntryScreenshotManager::TakeScreenshotImpl(
119    RenderViewHost* host,
120    NavigationEntryImpl* entry) {
121  DCHECK(host && host->GetView());
122  DCHECK(entry);
123  host->CopyFromBackingStore(
124      gfx::Rect(),
125      host->GetView()->GetViewBounds().size(),
126      base::Bind(&NavigationEntryScreenshotManager::OnScreenshotTaken,
127                 screenshot_factory_.GetWeakPtr(),
128                 entry->GetUniqueID()),
129      kAlpha_8_SkColorType);
130}
131
132void NavigationEntryScreenshotManager::SetMinScreenshotIntervalMS(
133    int interval_ms) {
134  DCHECK_GE(interval_ms, 0);
135  min_screenshot_interval_ms_ = interval_ms;
136}
137
138void NavigationEntryScreenshotManager::OnScreenshotTaken(int unique_id,
139                                                     bool success,
140                                                     const SkBitmap& bitmap) {
141  NavigationEntryImpl* entry = NULL;
142  int entry_count = owner_->GetEntryCount();
143  for (int i = 0; i < entry_count; ++i) {
144    NavigationEntry* iter = owner_->GetEntryAtIndex(i);
145    if (iter->GetUniqueID() == unique_id) {
146      entry = NavigationEntryImpl::FromNavigationEntry(iter);
147      break;
148    }
149  }
150
151  if (!entry) {
152    LOG(ERROR) << "Invalid entry with unique id: " << unique_id;
153    return;
154  }
155
156  if (!success || bitmap.empty() || bitmap.isNull()) {
157    if (!ClearScreenshot(entry))
158      OnScreenshotSet(entry);
159    return;
160  }
161
162  scoped_refptr<ScreenshotData> screenshot = new ScreenshotData();
163  screenshot->EncodeScreenshot(
164      bitmap,
165      base::Bind(&NavigationEntryScreenshotManager::OnScreenshotEncodeComplete,
166                 screenshot_factory_.GetWeakPtr(),
167                 unique_id,
168                 screenshot));
169}
170
171int NavigationEntryScreenshotManager::GetScreenshotCount() const {
172  int screenshot_count = 0;
173  int entry_count = owner_->GetEntryCount();
174  for (int i = 0; i < entry_count; ++i) {
175    NavigationEntryImpl* entry =
176        NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i));
177    if (entry->screenshot().get())
178      screenshot_count++;
179  }
180  return screenshot_count;
181}
182
183void NavigationEntryScreenshotManager::OnScreenshotEncodeComplete(
184    int unique_id,
185    scoped_refptr<ScreenshotData> screenshot) {
186  NavigationEntryImpl* entry = NULL;
187  int entry_count = owner_->GetEntryCount();
188  for (int i = 0; i < entry_count; ++i) {
189    NavigationEntry* iter = owner_->GetEntryAtIndex(i);
190    if (iter->GetUniqueID() == unique_id) {
191      entry = NavigationEntryImpl::FromNavigationEntry(iter);
192      break;
193    }
194  }
195  if (!entry)
196    return;
197  entry->SetScreenshotPNGData(screenshot->data());
198  OnScreenshotSet(entry);
199}
200
201void NavigationEntryScreenshotManager::OnScreenshotSet(
202    NavigationEntryImpl* entry) {
203  if (entry->screenshot().get())
204    PurgeScreenshotsIfNecessary();
205}
206
207bool NavigationEntryScreenshotManager::ClearScreenshot(
208    NavigationEntryImpl* entry) {
209  if (!entry->screenshot().get())
210    return false;
211
212  entry->SetScreenshotPNGData(NULL);
213  return true;
214}
215
216void NavigationEntryScreenshotManager::PurgeScreenshotsIfNecessary() {
217  // Allow only a certain number of entries to keep screenshots.
218  const int kMaxScreenshots = 10;
219  int screenshot_count = GetScreenshotCount();
220  if (screenshot_count < kMaxScreenshots)
221    return;
222
223  const int current = owner_->GetCurrentEntryIndex();
224  const int num_entries = owner_->GetEntryCount();
225  int available_slots = kMaxScreenshots;
226  if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current))
227          ->screenshot().get()) {
228    --available_slots;
229  }
230
231  // Keep screenshots closer to the current navigation entry, and purge the ones
232  // that are farther away from it. So in each step, look at the entries at
233  // each offset on both the back and forward history, and start counting them
234  // to make sure that the correct number of screenshots are kept in memory.
235  // Note that it is possible for some entries to be missing screenshots (e.g.
236  // when taking the screenshot failed for some reason). So there may be a state
237  // where there are a lot of entries in the back history, but none of them has
238  // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots|
239  // entries in the forward history list.
240  int back = current - 1;
241  int forward = current + 1;
242  while (available_slots > 0 && (back >= 0 || forward < num_entries)) {
243    if (back >= 0) {
244      NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
245          owner_->GetEntryAtIndex(back));
246      if (entry->screenshot().get())
247        --available_slots;
248      --back;
249    }
250
251    if (available_slots > 0 && forward < num_entries) {
252      NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
253          owner_->GetEntryAtIndex(forward));
254      if (entry->screenshot().get())
255        --available_slots;
256      ++forward;
257    }
258  }
259
260  // Purge any screenshot at |back| or lower indices, and |forward| or higher
261  // indices.
262  while (screenshot_count > kMaxScreenshots && back >= 0) {
263    NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
264        owner_->GetEntryAtIndex(back));
265    if (ClearScreenshot(entry))
266      --screenshot_count;
267    --back;
268  }
269
270  while (screenshot_count > kMaxScreenshots && forward < num_entries) {
271    NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
272        owner_->GetEntryAtIndex(forward));
273    if (ClearScreenshot(entry))
274      --screenshot_count;
275    ++forward;
276  }
277  CHECK_GE(screenshot_count, 0);
278  CHECK_LE(screenshot_count, kMaxScreenshots);
279}
280
281}  // namespace content
282