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 21 namespace { 22 23 // Minimum delay between taking screenshots. 24 const int kMinScreenshotIntervalMS = 1000; 25 26 } 27 28 namespace content { 29 30 // Converts SkBitmap to grayscale and encodes to PNG data in a worker thread. 31 class 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 kA8_Config SkBitmap 57 SkBitmap a8Bitmap; 58 a8Bitmap.setConfig(SkBitmap::kA8_Config, 59 bitmap.width(), 60 bitmap.height(), 61 0); 62 a8Bitmap.allocPixels(); 63 SkCanvas canvas(a8Bitmap); 64 SkPaint paint; 65 SkColorFilter* filter = SkLumaColorFilter::Create(); 66 paint.setColorFilter(filter); 67 filter->unref(); 68 canvas.drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint); 69 // Encode the a8Bitmap to grayscale PNG treating alpha as color intensity 70 if (gfx::PNGCodec::EncodeA8SkBitmap(a8Bitmap, &data)) 71 data_ = new base::RefCountedBytes(data); 72 } 73 74 scoped_refptr<base::RefCountedBytes> data_; 75 76 DISALLOW_COPY_AND_ASSIGN(ScreenshotData); 77 }; 78 79 NavigationEntryScreenshotManager::NavigationEntryScreenshotManager( 80 NavigationControllerImpl* owner) 81 : owner_(owner), 82 screenshot_factory_(this), 83 min_screenshot_interval_ms_(kMinScreenshotIntervalMS) { 84 } 85 86 NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() { 87 } 88 89 void NavigationEntryScreenshotManager::TakeScreenshot() { 90 static bool overscroll_enabled = CommandLine::ForCurrentProcess()-> 91 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0"; 92 if (!overscroll_enabled) 93 return; 94 95 NavigationEntryImpl* entry = 96 NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry()); 97 if (!entry) 98 return; 99 100 if (!owner_->delegate()->CanOverscrollContent()) 101 return; 102 103 RenderViewHost* render_view_host = 104 owner_->delegate()->GetRenderViewHost(); 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. 123 void 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 132 void 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 147 void NavigationEntryScreenshotManager::SetMinScreenshotIntervalMS( 148 int interval_ms) { 149 DCHECK_GE(interval_ms, 0); 150 min_screenshot_interval_ms_ = interval_ms; 151 } 152 153 void 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 186 int 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 198 void 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 216 void NavigationEntryScreenshotManager::OnScreenshotSet( 217 NavigationEntryImpl* entry) { 218 if (entry->screenshot().get()) 219 PurgeScreenshotsIfNecessary(); 220 } 221 222 bool NavigationEntryScreenshotManager::ClearScreenshot( 223 NavigationEntryImpl* entry) { 224 if (!entry->screenshot().get()) 225 return false; 226 227 entry->SetScreenshotPNGData(NULL); 228 return true; 229 } 230 231 void 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