1 // Copyright (c) 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/web_contents/web_contents_screenshot_manager.h" 6 7 #include "base/command_line.h" 8 #include "base/threading/worker_pool.h" 9 #include "content/browser/renderer_host/render_view_host_impl.h" 10 #include "content/browser/web_contents/navigation_controller_impl.h" 11 #include "content/browser/web_contents/navigation_entry_impl.h" 12 #include "content/browser/web_contents/web_contents_impl.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 an SkBitmap to PNG data 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 std::vector<unsigned char> data; 53 if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &data)) 54 data_ = new base::RefCountedBytes(data); 55 } 56 57 scoped_refptr<base::RefCountedBytes> data_; 58 59 DISALLOW_COPY_AND_ASSIGN(ScreenshotData); 60 }; 61 62 WebContentsScreenshotManager::WebContentsScreenshotManager( 63 NavigationControllerImpl* owner) 64 : owner_(owner), 65 screenshot_factory_(this), 66 min_screenshot_interval_ms_(kMinScreenshotIntervalMS) { 67 } 68 69 WebContentsScreenshotManager::~WebContentsScreenshotManager() { 70 } 71 72 void WebContentsScreenshotManager::TakeScreenshot() { 73 static bool overscroll_enabled = CommandLine::ForCurrentProcess()-> 74 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0"; 75 if (!overscroll_enabled) 76 return; 77 78 NavigationEntryImpl* entry = 79 NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry()); 80 if (!entry) 81 return; 82 83 RenderViewHost* render_view_host = 84 owner_->web_contents()->GetRenderViewHost(); 85 if (!static_cast<RenderViewHostImpl*> 86 (render_view_host)->overscroll_controller()) { 87 return; 88 } 89 content::RenderWidgetHostView* view = render_view_host->GetView(); 90 if (!view) 91 return; 92 93 // Make sure screenshots aren't taken too frequently. 94 base::Time now = base::Time::Now(); 95 if (now - last_screenshot_time_ < 96 base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) { 97 return; 98 } 99 100 last_screenshot_time_ = now; 101 102 TakeScreenshotImpl(render_view_host, entry); 103 } 104 105 // Implemented here and not in NavigationEntry because this manager keeps track 106 // of the total number of screen shots across all entries. 107 void WebContentsScreenshotManager::ClearAllScreenshots() { 108 int count = owner_->GetEntryCount(); 109 for (int i = 0; i < count; ++i) { 110 ClearScreenshot(NavigationEntryImpl::FromNavigationEntry( 111 owner_->GetEntryAtIndex(i))); 112 } 113 DCHECK_EQ(GetScreenshotCount(), 0); 114 } 115 116 void WebContentsScreenshotManager::TakeScreenshotImpl( 117 RenderViewHost* host, 118 NavigationEntryImpl* entry) { 119 DCHECK(host && host->GetView()); 120 DCHECK(entry); 121 host->CopyFromBackingStore(gfx::Rect(), 122 host->GetView()->GetViewBounds().size(), 123 base::Bind(&WebContentsScreenshotManager::OnScreenshotTaken, 124 screenshot_factory_.GetWeakPtr(), 125 entry->GetUniqueID())); 126 } 127 128 void WebContentsScreenshotManager::SetMinScreenshotIntervalMS(int interval_ms) { 129 DCHECK_GE(interval_ms, 0); 130 min_screenshot_interval_ms_ = interval_ms; 131 } 132 133 void WebContentsScreenshotManager::OnScreenshotTaken(int unique_id, 134 bool success, 135 const SkBitmap& bitmap) { 136 NavigationEntryImpl* entry = NULL; 137 int entry_count = owner_->GetEntryCount(); 138 for (int i = 0; i < entry_count; ++i) { 139 NavigationEntry* iter = owner_->GetEntryAtIndex(i); 140 if (iter->GetUniqueID() == unique_id) { 141 entry = NavigationEntryImpl::FromNavigationEntry(iter); 142 break; 143 } 144 } 145 146 if (!entry) { 147 LOG(ERROR) << "Invalid entry with unique id: " << unique_id; 148 return; 149 } 150 151 if (!success || bitmap.empty() || bitmap.isNull()) { 152 ClearScreenshot(entry); 153 return; 154 } 155 156 scoped_refptr<ScreenshotData> screenshot = new ScreenshotData(); 157 screenshot->EncodeScreenshot( 158 bitmap, 159 base::Bind(&WebContentsScreenshotManager::OnScreenshotEncodeComplete, 160 screenshot_factory_.GetWeakPtr(), 161 unique_id, 162 screenshot)); 163 } 164 165 int WebContentsScreenshotManager::GetScreenshotCount() const { 166 int screenshot_count = 0; 167 int entry_count = owner_->GetEntryCount(); 168 for (int i = 0; i < entry_count; ++i) { 169 NavigationEntryImpl* entry = 170 NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i)); 171 if (entry->screenshot().get()) 172 screenshot_count++; 173 } 174 return screenshot_count; 175 } 176 177 void WebContentsScreenshotManager::OnScreenshotEncodeComplete( 178 int unique_id, 179 scoped_refptr<ScreenshotData> screenshot) { 180 NavigationEntryImpl* entry = NULL; 181 int entry_count = owner_->GetEntryCount(); 182 for (int i = 0; i < entry_count; ++i) { 183 NavigationEntry* iter = owner_->GetEntryAtIndex(i); 184 if (iter->GetUniqueID() == unique_id) { 185 entry = NavigationEntryImpl::FromNavigationEntry(iter); 186 break; 187 } 188 } 189 if (!entry) 190 return; 191 entry->SetScreenshotPNGData(screenshot->data()); 192 OnScreenshotSet(entry); 193 } 194 195 void WebContentsScreenshotManager::OnScreenshotSet(NavigationEntryImpl* entry) { 196 PurgeScreenshotsIfNecessary(); 197 } 198 199 bool WebContentsScreenshotManager::ClearScreenshot(NavigationEntryImpl* entry) { 200 if (!entry->screenshot().get()) 201 return false; 202 203 entry->SetScreenshotPNGData(NULL); 204 return true; 205 } 206 207 void WebContentsScreenshotManager::PurgeScreenshotsIfNecessary() { 208 // Allow only a certain number of entries to keep screenshots. 209 const int kMaxScreenshots = 10; 210 int screenshot_count = GetScreenshotCount(); 211 if (screenshot_count < kMaxScreenshots) 212 return; 213 214 const int current = owner_->GetCurrentEntryIndex(); 215 const int num_entries = owner_->GetEntryCount(); 216 int available_slots = kMaxScreenshots; 217 if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current)) 218 ->screenshot().get()) { 219 --available_slots; 220 } 221 222 // Keep screenshots closer to the current navigation entry, and purge the ones 223 // that are farther away from it. So in each step, look at the entries at 224 // each offset on both the back and forward history, and start counting them 225 // to make sure that the correct number of screenshots are kept in memory. 226 // Note that it is possible for some entries to be missing screenshots (e.g. 227 // when taking the screenshot failed for some reason). So there may be a state 228 // where there are a lot of entries in the back history, but none of them has 229 // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots| 230 // entries in the forward history list. 231 int back = current - 1; 232 int forward = current + 1; 233 while (available_slots > 0 && (back >= 0 || forward < num_entries)) { 234 if (back >= 0) { 235 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( 236 owner_->GetEntryAtIndex(back)); 237 if (entry->screenshot().get()) 238 --available_slots; 239 --back; 240 } 241 242 if (available_slots > 0 && forward < num_entries) { 243 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( 244 owner_->GetEntryAtIndex(forward)); 245 if (entry->screenshot().get()) 246 --available_slots; 247 ++forward; 248 } 249 } 250 251 // Purge any screenshot at |back| or lower indices, and |forward| or higher 252 // indices. 253 while (screenshot_count > kMaxScreenshots && back >= 0) { 254 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( 255 owner_->GetEntryAtIndex(back)); 256 if (ClearScreenshot(entry)) 257 --screenshot_count; 258 --back; 259 } 260 261 while (screenshot_count > kMaxScreenshots && forward < num_entries) { 262 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( 263 owner_->GetEntryAtIndex(forward)); 264 if (ClearScreenshot(entry)) 265 --screenshot_count; 266 ++forward; 267 } 268 CHECK_GE(screenshot_count, 0); 269 CHECK_LE(screenshot_count, kMaxScreenshots); 270 } 271 272 } // namespace content 273