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