1 // Copyright (c) 2012 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 "chrome/browser/ui/ash/screenshot_taker.h" 6 7 #include <climits> 8 #include <string> 9 10 #include "ash/shell.h" 11 #include "ash/system/system_notifier.h" 12 #include "base/base64.h" 13 #include "base/bind.h" 14 #include "base/file_util.h" 15 #include "base/i18n/time_formatting.h" 16 #include "base/logging.h" 17 #include "base/memory/ref_counted_memory.h" 18 #include "base/prefs/pref_service.h" 19 #include "base/strings/stringprintf.h" 20 #include "base/strings/utf_string_conversions.h" 21 #include "base/threading/sequenced_worker_pool.h" 22 #include "base/time/time.h" 23 #include "chrome/browser/browser_process.h" 24 #include "chrome/browser/download/download_prefs.h" 25 #include "chrome/browser/notifications/notification_ui_manager.h" 26 #include "chrome/browser/profiles/profile.h" 27 #include "chrome/browser/profiles/profile_manager.h" 28 #include "chrome/browser/ui/window_snapshot/window_snapshot.h" 29 #include "chrome/common/pref_names.h" 30 #include "content/public/browser/browser_thread.h" 31 #include "content/public/browser/user_metrics.h" 32 #include "grit/ash_strings.h" 33 #include "grit/theme_resources.h" 34 #include "grit/ui_strings.h" 35 #include "ui/aura/root_window.h" 36 #include "ui/aura/window.h" 37 #include "ui/base/clipboard/clipboard.h" 38 #include "ui/base/clipboard/scoped_clipboard_writer.h" 39 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/resource/resource_bundle.h" 41 #include "ui/gfx/image/image.h" 42 43 #if defined(USE_ASH) 44 #include "ash/shell.h" 45 #include "ash/shell_delegate.h" 46 #endif 47 48 #if defined(OS_CHROMEOS) 49 #include "chrome/browser/chromeos/drive/file_system_interface.h" 50 #include "chrome/browser/chromeos/drive/file_system_util.h" 51 #include "chrome/browser/chromeos/file_manager/open_util.h" 52 #include "chrome/browser/chromeos/login/user_manager.h" 53 #include "chrome/browser/notifications/desktop_notification_service.h" 54 #include "chrome/browser/notifications/desktop_notification_service_factory.h" 55 #include "chromeos/login/login_state.h" 56 #endif 57 58 namespace { 59 // The minimum interval between two screenshot commands. It has to be 60 // more than 1000 to prevent the conflict of filenames. 61 const int kScreenshotMinimumIntervalInMS = 1000; 62 63 const char kNotificationId[] = "screenshot"; 64 65 const char kNotificationOriginUrl[] = "chrome://screenshot"; 66 67 const char kImageClipboardFormatPrefix[] = "<img src='data:image/png;base64,"; 68 const char kImageClipboardFormatSuffix[] = "'>"; 69 70 void CopyScreenshotToClipboard(scoped_refptr<base::RefCountedString> png_data) { 71 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 72 73 std::string encoded; 74 base::Base64Encode(png_data->data(), &encoded); 75 76 // Only cares about HTML because ChromeOS doesn't need other formats. 77 // TODO(dcheng): Why don't we take advantage of the ability to write bitmaps 78 // to the clipboard here? 79 { 80 ui::ScopedClipboardWriter scw(ui::Clipboard::GetForCurrentThread(), 81 ui::CLIPBOARD_TYPE_COPY_PASTE); 82 std::string html(kImageClipboardFormatPrefix); 83 html += encoded; 84 html += kImageClipboardFormatSuffix; 85 scw.WriteHTML(UTF8ToUTF16(html), std::string()); 86 } 87 content::RecordAction(content::UserMetricsAction("Screenshot_CopyClipboard")); 88 } 89 90 void ReadFileAndCopyToClipboardLocal(const base::FilePath& screenshot_path) { 91 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 92 93 scoped_refptr<base::RefCountedString> png_data(new base::RefCountedString()); 94 if (!base::ReadFileToString(screenshot_path, &(png_data->data()))) { 95 LOG(ERROR) << "Failed to read the screenshot file: " 96 << screenshot_path.value(); 97 return; 98 } 99 100 content::BrowserThread::PostTask( 101 content::BrowserThread::UI, FROM_HERE, 102 base::Bind(CopyScreenshotToClipboard, png_data)); 103 } 104 105 #if defined(OS_CHROMEOS) 106 void ReadFileAndCopyToClipboardDrive(drive::FileError error, 107 const base::FilePath& file_path, 108 scoped_ptr<drive::ResourceEntry> entry) { 109 if (error != drive::FILE_ERROR_OK) { 110 LOG(ERROR) << "Failed to read the screenshot path on drive: " 111 << drive::FileErrorToString(error); 112 return; 113 } 114 content::BrowserThread::GetBlockingPool()->PostTask( 115 FROM_HERE, 116 base::Bind(&ReadFileAndCopyToClipboardLocal, file_path)); 117 } 118 #endif 119 120 // Delegate for a notification. This class has two roles: to implement callback 121 // methods for notification, and to provide an identity of the associated 122 // notification. 123 class ScreenshotTakerNotificationDelegate : public NotificationDelegate { 124 public: 125 ScreenshotTakerNotificationDelegate(bool success, 126 Profile* profile, 127 const base::FilePath& screenshot_path) 128 : success_(success), 129 profile_(profile), 130 screenshot_path_(screenshot_path) { 131 } 132 133 // Overridden from NotificationDelegate: 134 virtual void Display() OVERRIDE {} 135 virtual void Error() OVERRIDE {} 136 virtual void Close(bool by_user) OVERRIDE {} 137 virtual void Click() OVERRIDE { 138 if (!success_) 139 return; 140 #if defined(OS_CHROMEOS) 141 file_manager::util::ShowItemInFolder(profile_, screenshot_path_); 142 #else 143 // TODO(sschmitz): perhaps add similar action for Windows. 144 #endif 145 } 146 virtual void ButtonClick(int button_index) OVERRIDE { 147 DCHECK(success_ && button_index == 0); 148 149 // To avoid keeping the screenshot image on memory, it will re-read the 150 // screenshot file and copy it to the clipboard. 151 #if defined(OS_CHROMEOS) 152 if (drive::util::IsUnderDriveMountPoint(screenshot_path_)) { 153 drive::FileSystemInterface* file_system = 154 drive::util::GetFileSystemByProfile(profile_); 155 file_system->GetFile( 156 drive::util::ExtractDrivePath(screenshot_path_), 157 base::Bind(&ReadFileAndCopyToClipboardDrive)); 158 return; 159 } 160 #endif 161 content::BrowserThread::GetBlockingPool()->PostTask( 162 FROM_HERE, base::Bind( 163 &ReadFileAndCopyToClipboardLocal, screenshot_path_)); 164 } 165 virtual bool HasClickedListener() OVERRIDE { return success_; } 166 virtual std::string id() const OVERRIDE { 167 return std::string(kNotificationId); 168 } 169 virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE { 170 return NULL; 171 } 172 173 private: 174 virtual ~ScreenshotTakerNotificationDelegate() {} 175 176 const bool success_; 177 Profile* profile_; 178 const base::FilePath screenshot_path_; 179 180 DISALLOW_COPY_AND_ASSIGN(ScreenshotTakerNotificationDelegate); 181 }; 182 183 typedef base::Callback< 184 void(ScreenshotTakerObserver::Result screenshot_result, 185 const base::FilePath& screenshot_path)> ShowNotificationCallback; 186 187 void SaveScreenshotInternal(const ShowNotificationCallback& callback, 188 const base::FilePath& screenshot_path, 189 const base::FilePath& local_path, 190 scoped_refptr<base::RefCountedBytes> png_data) { 191 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 192 DCHECK(!local_path.empty()); 193 ScreenshotTakerObserver::Result result = 194 ScreenshotTakerObserver::SCREENSHOT_SUCCESS; 195 if (static_cast<size_t>(file_util::WriteFile( 196 local_path, 197 reinterpret_cast<char*>(&(png_data->data()[0])), 198 png_data->size())) != png_data->size()) { 199 LOG(ERROR) << "Failed to save to " << local_path.value(); 200 result = ScreenshotTakerObserver::SCREENSHOT_WRITE_FILE_FAILED; 201 } 202 content::BrowserThread::PostTask( 203 content::BrowserThread::UI, FROM_HERE, 204 base::Bind(callback, result, screenshot_path)); 205 } 206 207 void SaveScreenshot(const ShowNotificationCallback& callback, 208 const base::FilePath& screenshot_path, 209 scoped_refptr<base::RefCountedBytes> png_data) { 210 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 211 DCHECK(!screenshot_path.empty()); 212 213 if (!base::CreateDirectory(screenshot_path.DirName())) { 214 LOG(ERROR) << "Failed to ensure the existence of " 215 << screenshot_path.DirName().value(); 216 content::BrowserThread::PostTask( 217 content::BrowserThread::UI, FROM_HERE, 218 base::Bind(callback, 219 ScreenshotTakerObserver::SCREENSHOT_CREATE_DIR_FAILED, 220 screenshot_path)); 221 return; 222 } 223 SaveScreenshotInternal(callback, screenshot_path, screenshot_path, png_data); 224 } 225 226 // TODO(kinaba): crbug.com/140425, remove this ungly #ifdef dispatch. 227 #if defined(OS_CHROMEOS) 228 void SaveScreenshotToDrive(const ShowNotificationCallback& callback, 229 const base::FilePath& screenshot_path, 230 scoped_refptr<base::RefCountedBytes> png_data, 231 drive::FileError error, 232 const base::FilePath& local_path) { 233 // |screenshot_path| is used in the notification callback. 234 // |local_path| is a temporary file in a hidden cache directory used for 235 // internal work generated by drive::util::PrepareWritableFileAndRun. 236 if (error != drive::FILE_ERROR_OK) { 237 LOG(ERROR) << "Failed to write screenshot image to Google Drive: " << error; 238 content::BrowserThread::PostTask( 239 content::BrowserThread::UI, FROM_HERE, 240 base::Bind(callback, 241 ScreenshotTakerObserver::SCREENSHOT_CREATE_FILE_FAILED, 242 screenshot_path)); 243 return; 244 } 245 SaveScreenshotInternal(callback, screenshot_path, local_path, png_data); 246 } 247 248 void EnsureDirectoryExistsCallback( 249 const ShowNotificationCallback& callback, 250 Profile* profile, 251 const base::FilePath& screenshot_path, 252 scoped_refptr<base::RefCountedBytes> png_data, 253 drive::FileError error) { 254 // It is okay to fail with FILE_ERROR_EXISTS since anyway the directory 255 // of the target file exists. 256 if (error == drive::FILE_ERROR_OK || 257 error == drive::FILE_ERROR_EXISTS) { 258 drive::util::PrepareWritableFileAndRun( 259 profile, 260 screenshot_path, 261 base::Bind(&SaveScreenshotToDrive, 262 callback, 263 screenshot_path, 264 png_data)); 265 } else { 266 LOG(ERROR) << "Failed to ensure the existence of the specified directory " 267 << "in Google Drive: " << error; 268 callback.Run(ScreenshotTakerObserver::SCREENSHOT_CHECK_DIR_FAILED, 269 screenshot_path); 270 } 271 } 272 273 void PostSaveScreenshotTask(const ShowNotificationCallback& callback, 274 Profile* profile, 275 const base::FilePath& screenshot_path, 276 scoped_refptr<base::RefCountedBytes> png_data) { 277 if (drive::util::IsUnderDriveMountPoint(screenshot_path)) { 278 drive::util::EnsureDirectoryExists( 279 profile, 280 screenshot_path.DirName(), 281 base::Bind(&EnsureDirectoryExistsCallback, 282 callback, 283 profile, 284 screenshot_path, 285 png_data)); 286 } else { 287 content::BrowserThread::GetBlockingPool()->PostTask( 288 FROM_HERE, base::Bind(&SaveScreenshot, 289 callback, 290 screenshot_path, 291 png_data)); 292 } 293 } 294 #else 295 void PostSaveScreenshotTask(const ShowNotificationCallback& callback, 296 Profile* profile, 297 const base::FilePath& screenshot_path, 298 scoped_refptr<base::RefCountedBytes> png_data) { 299 content::BrowserThread::GetBlockingPool()->PostTask( 300 FROM_HERE, base::Bind(&SaveScreenshot, 301 callback, 302 screenshot_path, 303 png_data)); 304 } 305 #endif 306 307 bool GrabWindowSnapshot(aura::Window* window, 308 const gfx::Rect& snapshot_bounds, 309 std::vector<unsigned char>* png_data) { 310 return chrome::GrabWindowSnapshotForUser(window, png_data, snapshot_bounds); 311 } 312 313 bool ShouldUse24HourClock() { 314 #if defined(OS_CHROMEOS) 315 Profile* profile = ProfileManager::GetActiveUserProfileOrOffTheRecord(); 316 if (profile) { 317 return profile->GetPrefs()->GetBoolean(prefs::kUse24HourClock); 318 } 319 #endif 320 return base::GetHourClockType() == base::k24HourClock; 321 } 322 323 std::string GetScreenshotBaseFilename() { 324 base::Time::Exploded now; 325 base::Time::Now().LocalExplode(&now); 326 327 // We don't use base/i18n/time_formatting.h here because it doesn't 328 // support our format. Don't use ICU either to avoid i18n file names 329 // for non-English locales. 330 // TODO(mukai): integrate this logic somewhere time_formatting.h 331 std::string file_name = base::StringPrintf( 332 "Screenshot %d-%02d-%02d at ", now.year, now.month, now.day_of_month); 333 334 if (ShouldUse24HourClock()) { 335 file_name.append(base::StringPrintf( 336 "%02d.%02d.%02d", now.hour, now.minute, now.second)); 337 } else { 338 int hour = now.hour; 339 if (hour > 12) { 340 hour -= 12; 341 } else if (hour == 0) { 342 hour = 12; 343 } 344 file_name.append(base::StringPrintf( 345 "%d.%02d.%02d ", hour, now.minute, now.second)); 346 file_name.append((now.hour >= 12) ? "PM" : "AM"); 347 } 348 349 return file_name; 350 } 351 352 bool GetScreenshotDirectory(base::FilePath* directory) { 353 bool is_logged_in = true; 354 355 #if defined(OS_CHROMEOS) 356 is_logged_in = chromeos::LoginState::Get()->IsUserLoggedIn(); 357 #endif 358 359 if (is_logged_in) { 360 DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext( 361 ProfileManager::GetActiveUserProfileOrOffTheRecord()); 362 *directory = download_prefs->DownloadPath(); 363 } else { 364 if (!base::GetTempDir(directory)) { 365 LOG(ERROR) << "Failed to find temporary directory."; 366 return false; 367 } 368 } 369 return true; 370 } 371 372 const int GetScreenshotNotificationTitle( 373 ScreenshotTakerObserver::Result screenshot_result) { 374 switch (screenshot_result) { 375 case ScreenshotTakerObserver::SCREENSHOTS_DISABLED: 376 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_DISABLED; 377 case ScreenshotTakerObserver::SCREENSHOT_SUCCESS: 378 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS; 379 default: 380 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL; 381 } 382 } 383 384 const int GetScreenshotNotificationText( 385 ScreenshotTakerObserver::Result screenshot_result) { 386 switch (screenshot_result) { 387 case ScreenshotTakerObserver::SCREENSHOTS_DISABLED: 388 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_DISABLED; 389 case ScreenshotTakerObserver::SCREENSHOT_SUCCESS: 390 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS; 391 default: 392 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL; 393 } 394 } 395 396 } // namespace 397 398 ScreenshotTaker::ScreenshotTaker() 399 : factory_(this), 400 profile_for_test_(NULL) { 401 } 402 403 ScreenshotTaker::~ScreenshotTaker() { 404 } 405 406 void ScreenshotTaker::HandleTakeScreenshotForAllRootWindows() { 407 if (g_browser_process->local_state()-> 408 GetBoolean(prefs::kDisableScreenshots)) { 409 ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED, 410 base::FilePath()); 411 return; 412 } 413 base::FilePath screenshot_directory; 414 if (!screenshot_directory_for_test_.empty()) { 415 screenshot_directory = screenshot_directory_for_test_; 416 } else if (!GetScreenshotDirectory(&screenshot_directory)) { 417 ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED, 418 base::FilePath()); 419 return; 420 } 421 std::string screenshot_basename = !screenshot_basename_for_test_.empty() ? 422 screenshot_basename_for_test_ : GetScreenshotBaseFilename(); 423 424 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 425 // Reorder root_windows to take the primary root window's snapshot at first. 426 aura::Window* primary_root = ash::Shell::GetPrimaryRootWindow(); 427 if (*(root_windows.begin()) != primary_root) { 428 root_windows.erase(std::find( 429 root_windows.begin(), root_windows.end(), primary_root)); 430 root_windows.insert(root_windows.begin(), primary_root); 431 } 432 for (size_t i = 0; i < root_windows.size(); ++i) { 433 aura::Window* root_window = root_windows[i]; 434 scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes); 435 std::string basename = screenshot_basename; 436 gfx::Rect rect = root_window->bounds(); 437 if (root_windows.size() > 1) 438 basename += base::StringPrintf(" - Display %d", static_cast<int>(i + 1)); 439 base::FilePath screenshot_path = 440 screenshot_directory.AppendASCII(basename + ".png"); 441 if (GrabWindowSnapshot(root_window, rect, &png_data->data())) { 442 PostSaveScreenshotTask( 443 base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()), 444 GetProfile(), 445 screenshot_path, 446 png_data); 447 } else { 448 LOG(ERROR) << "Failed to grab the window screenshot for " << i; 449 ShowNotification( 450 ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_FULL_FAILED, 451 screenshot_path); 452 } 453 } 454 content::RecordAction(content::UserMetricsAction("Screenshot_TakeFull")); 455 last_screenshot_timestamp_ = base::Time::Now(); 456 } 457 458 void ScreenshotTaker::HandleTakePartialScreenshot( 459 aura::Window* window, const gfx::Rect& rect) { 460 if (g_browser_process->local_state()-> 461 GetBoolean(prefs::kDisableScreenshots)) { 462 ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED, 463 base::FilePath()); 464 return; 465 } 466 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 467 468 base::FilePath screenshot_directory; 469 if (!screenshot_directory_for_test_.empty()) { 470 screenshot_directory = screenshot_directory_for_test_; 471 } else if (!GetScreenshotDirectory(&screenshot_directory)) { 472 ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED, 473 base::FilePath()); 474 return; 475 } 476 477 scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes); 478 479 std::string screenshot_basename = !screenshot_basename_for_test_.empty() ? 480 screenshot_basename_for_test_ : GetScreenshotBaseFilename(); 481 base::FilePath screenshot_path = 482 screenshot_directory.AppendASCII(screenshot_basename + ".png"); 483 if (GrabWindowSnapshot(window, rect, &png_data->data())) { 484 last_screenshot_timestamp_ = base::Time::Now(); 485 PostSaveScreenshotTask( 486 base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()), 487 GetProfile(), 488 screenshot_path, 489 png_data); 490 } else { 491 LOG(ERROR) << "Failed to grab the window screenshot"; 492 ShowNotification( 493 ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_PARTIAL_FAILED, 494 screenshot_path); 495 } 496 content::RecordAction(content::UserMetricsAction("Screenshot_TakePartial")); 497 } 498 499 bool ScreenshotTaker::CanTakeScreenshot() { 500 return last_screenshot_timestamp_.is_null() || 501 base::Time::Now() - last_screenshot_timestamp_ > 502 base::TimeDelta::FromMilliseconds( 503 kScreenshotMinimumIntervalInMS); 504 } 505 506 Notification* ScreenshotTaker::CreateNotification( 507 ScreenshotTakerObserver::Result screenshot_result, 508 const base::FilePath& screenshot_path) { 509 const std::string notification_id(kNotificationId); 510 // We cancel a previous screenshot notification, if any, to ensure we get 511 // a fresh notification pop-up. 512 g_browser_process->notification_ui_manager()->CancelById(notification_id); 513 const base::string16 replace_id(UTF8ToUTF16(notification_id)); 514 bool success = 515 (screenshot_result == ScreenshotTakerObserver::SCREENSHOT_SUCCESS); 516 message_center::RichNotificationData optional_field; 517 if (success) { 518 const base::string16 label = l10n_util::GetStringUTF16( 519 IDS_MESSAGE_CENTER_NOTIFICATION_BUTTON_COPY_SCREENSHOT_TO_CLIPBOARD); 520 optional_field.buttons.push_back(message_center::ButtonInfo(label)); 521 } 522 return new Notification( 523 message_center::NOTIFICATION_TYPE_SIMPLE, 524 GURL(kNotificationOriginUrl), 525 l10n_util::GetStringUTF16( 526 GetScreenshotNotificationTitle(screenshot_result)), 527 l10n_util::GetStringUTF16( 528 GetScreenshotNotificationText(screenshot_result)), 529 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 530 IDR_SCREENSHOT_NOTIFICATION_ICON), 531 blink::WebTextDirectionDefault, 532 message_center::NotifierId( 533 message_center::NotifierId::SYSTEM_COMPONENT, 534 ash::system_notifier::kNotifierScreenshot), 535 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME), 536 replace_id, 537 optional_field, 538 new ScreenshotTakerNotificationDelegate( 539 success, GetProfile(), screenshot_path)); 540 } 541 542 void ScreenshotTaker::ShowNotification( 543 ScreenshotTakerObserver::Result screenshot_result, 544 const base::FilePath& screenshot_path) { 545 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 546 #if defined(OS_CHROMEOS) 547 // Do not show a notification that a screenshot was taken while no user is 548 // logged in, since it is confusing for the user to get a message about it 549 // after he logs in (crbug.com/235217). 550 if (!chromeos::LoginState::Get()->IsUserLoggedIn()) 551 return; 552 553 // TODO(sschmitz): make this work for Windows. 554 DesktopNotificationService* const service = 555 DesktopNotificationServiceFactory::GetForProfile(GetProfile()); 556 if (service->IsNotifierEnabled(message_center::NotifierId( 557 message_center::NotifierId::SYSTEM_COMPONENT, 558 ash::system_notifier::kNotifierScreenshot))) { 559 scoped_ptr<Notification> notification( 560 CreateNotification(screenshot_result, screenshot_path)); 561 g_browser_process->notification_ui_manager()->Add(*notification, 562 GetProfile()); 563 } 564 #endif 565 FOR_EACH_OBSERVER(ScreenshotTakerObserver, observers_, 566 OnScreenshotCompleted(screenshot_result, screenshot_path)); 567 } 568 569 void ScreenshotTaker::AddObserver(ScreenshotTakerObserver* observer) { 570 observers_.AddObserver(observer); 571 } 572 573 void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver* observer) { 574 observers_.RemoveObserver(observer); 575 } 576 577 bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver* observer) const { 578 return observers_.HasObserver(observer); 579 } 580 581 Profile* ScreenshotTaker::GetProfile() { 582 if (profile_for_test_) 583 return profile_for_test_; 584 return ProfileManager::GetActiveUserProfileOrOffTheRecord(); 585 } 586 587 void ScreenshotTaker::SetScreenshotDirectoryForTest( 588 const base::FilePath& directory) { 589 screenshot_directory_for_test_ = directory; 590 } 591 592 void ScreenshotTaker::SetScreenshotBasenameForTest( 593 const std::string& basename) { 594 screenshot_basename_for_test_ = basename; 595 } 596 597 void ScreenshotTaker::SetScreenshotProfileForTest(Profile* profile) { 598 profile_for_test_ = profile; 599 } 600