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