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 18 namespace { 19 20 // Minimum delay between taking screenshots. 21 const int kMinScreenshotIntervalMS = 1000; 22 23 } 24 25 namespace content { 26 27 // Encodes the A8 SkBitmap to grayscale PNG in a worker thread. 28 class 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 64 NavigationEntryScreenshotManager::NavigationEntryScreenshotManager( 65 NavigationControllerImpl* owner) 66 : owner_(owner), 67 min_screenshot_interval_ms_(kMinScreenshotIntervalMS), 68 screenshot_factory_(this) { 69 70 } 71 72 NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() { 73 } 74 75 void 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. 109 void 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 118 void 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 132 void NavigationEntryScreenshotManager::SetMinScreenshotIntervalMS( 133 int interval_ms) { 134 DCHECK_GE(interval_ms, 0); 135 min_screenshot_interval_ms_ = interval_ms; 136 } 137 138 void 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 171 int 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 183 void 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 201 void NavigationEntryScreenshotManager::OnScreenshotSet( 202 NavigationEntryImpl* entry) { 203 if (entry->screenshot().get()) 204 PurgeScreenshotsIfNecessary(); 205 } 206 207 bool NavigationEntryScreenshotManager::ClearScreenshot( 208 NavigationEntryImpl* entry) { 209 if (!entry->screenshot().get()) 210 return false; 211 212 entry->SetScreenshotPNGData(NULL); 213 return true; 214 } 215 216 void 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