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