1 // Copyright (c) 2011 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/first_run/first_run.h" 6 7 #include "base/command_line.h" 8 #include "base/compiler_specific.h" 9 #include "base/file_util.h" 10 #include "base/path_service.h" 11 #include "base/utf_string_conversions.h" 12 #include "build/build_config.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/first_run/first_run_dialog.h" 15 #include "chrome/browser/first_run/first_run_import_observer.h" 16 #include "chrome/browser/importer/external_process_importer_host.h" 17 #include "chrome/browser/importer/importer_host.h" 18 #include "chrome/browser/importer/importer_list.h" 19 #include "chrome/browser/importer/importer_progress_dialog.h" 20 #include "chrome/browser/importer/importer_progress_observer.h" 21 #include "chrome/browser/metrics/user_metrics.h" 22 #include "chrome/browser/prefs/pref_service.h" 23 #include "chrome/browser/process_singleton.h" 24 #include "chrome/browser/profiles/profile_manager.h" 25 #include "chrome/browser/search_engines/template_url_model.h" 26 #include "chrome/browser/shell_integration.h" 27 #include "chrome/common/chrome_paths.h" 28 #include "chrome/common/chrome_switches.h" 29 #include "chrome/common/pref_names.h" 30 #include "chrome/installer/util/master_preferences.h" 31 #include "chrome/installer/util/master_preferences_constants.h" 32 #include "chrome/installer/util/util_constants.h" 33 #include "googleurl/src/gurl.h" 34 35 #if defined(OS_WIN) 36 // TODO(port): move more code in back from the first_run_win.cc module. 37 #include "chrome/installer/util/google_update_settings.h" 38 #include "chrome/installer/util/install_util.h" 39 #endif 40 41 namespace { 42 43 // The kSentinelFile file absence will tell us it is a first run. 44 const char kSentinelFile[] = "First Run"; 45 46 FilePath GetDefaultPrefFilePath(bool create_profile_dir, 47 const FilePath& user_data_dir) { 48 FilePath default_pref_dir = 49 ProfileManager::GetDefaultProfileDir(user_data_dir); 50 if (create_profile_dir) { 51 if (!file_util::PathExists(default_pref_dir)) { 52 if (!file_util::CreateDirectory(default_pref_dir)) 53 return FilePath(); 54 } 55 } 56 return ProfileManager::GetProfilePrefsPath(default_pref_dir); 57 } 58 59 } // namespace 60 61 // FirstRun ------------------------------------------------------------------- 62 63 FirstRun::FirstRunState FirstRun::first_run_ = FIRST_RUN_UNKNOWN; 64 65 FirstRun::MasterPrefs::MasterPrefs() 66 : ping_delay(0), 67 homepage_defined(false), 68 do_import_items(0), 69 dont_import_items(0), 70 run_search_engine_experiment(false), 71 randomize_search_engine_experiment(false), 72 make_chrome_default(false) { 73 } 74 75 FirstRun::MasterPrefs::~MasterPrefs() {} 76 77 // TODO(port): Import switches need to be ported to both Mac and Linux. Not all 78 // import switches here are implemented for Linux. None are implemented for Mac 79 // (as this function will not be called on Mac). 80 int FirstRun::ImportNow(Profile* profile, const CommandLine& cmdline) { 81 int return_code = true; 82 if (cmdline.HasSwitch(switches::kImportFromFile)) { 83 // Silently import preset bookmarks from file. 84 // This is an OEM scenario. 85 return_code = ImportFromFile(profile, cmdline); 86 } 87 if (cmdline.HasSwitch(switches::kImport)) { 88 #if defined(OS_WIN) 89 return_code = ImportFromBrowser(profile, cmdline); 90 #else 91 NOTIMPLEMENTED(); 92 #endif 93 } 94 return return_code; 95 } 96 97 // static 98 bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir, 99 MasterPrefs* out_prefs) { 100 DCHECK(!user_data_dir.empty()); 101 102 // The standard location of the master prefs is next to the chrome binary. 103 FilePath master_prefs; 104 if (!PathService::Get(base::DIR_EXE, &master_prefs)) 105 return true; 106 master_prefs = master_prefs.AppendASCII(installer::kDefaultMasterPrefs); 107 108 installer::MasterPreferences prefs(master_prefs); 109 if (!prefs.read_from_file()) 110 return true; 111 112 out_prefs->new_tabs = prefs.GetFirstRunTabs(); 113 114 bool value = false; 115 116 #if defined(OS_WIN) 117 // RLZ is currently a Windows-only phenomenon. When it comes to the Mac/ 118 // Linux, enable it here. 119 if (!prefs.GetInt(installer::master_preferences::kDistroPingDelay, 120 &out_prefs->ping_delay)) { 121 // 90 seconds is the default that we want to use in case master 122 // preferences is missing, corrupt or ping_delay is missing. 123 out_prefs->ping_delay = 90; 124 } 125 126 if (prefs.GetBool(installer::master_preferences::kRequireEula, &value) && 127 value) { 128 // Show the post-installation EULA. This is done by setup.exe and the 129 // result determines if we continue or not. We wait here until the user 130 // dismisses the dialog. 131 132 // The actual eula text is in a resource in chrome. We extract it to 133 // a text file so setup.exe can use it as an inner frame. 134 FilePath inner_html; 135 if (WriteEULAtoTempFile(&inner_html)) { 136 int retcode = 0; 137 if (!LaunchSetupWithParam(installer::switches::kShowEula, 138 inner_html.value(), &retcode) || 139 (retcode == installer::EULA_REJECTED)) { 140 LOG(WARNING) << "EULA rejected. Fast exit."; 141 ::ExitProcess(1); 142 } 143 if (retcode == installer::EULA_ACCEPTED) { 144 VLOG(1) << "EULA : no collection"; 145 GoogleUpdateSettings::SetCollectStatsConsent(false); 146 } else if (retcode == installer::EULA_ACCEPTED_OPT_IN) { 147 VLOG(1) << "EULA : collection consent"; 148 GoogleUpdateSettings::SetCollectStatsConsent(true); 149 } 150 } 151 } 152 #endif 153 154 if (prefs.GetBool(installer::master_preferences::kAltFirstRunBubble, 155 &value) && value) { 156 FirstRun::SetOEMFirstRunBubblePref(); 157 } 158 159 FilePath user_prefs = GetDefaultPrefFilePath(true, user_data_dir); 160 if (user_prefs.empty()) 161 return true; 162 163 // The master prefs are regular prefs so we can just copy the file 164 // to the default place and they just work. 165 if (!file_util::CopyFile(master_prefs, user_prefs)) 166 return true; 167 168 #if defined(OS_WIN) 169 DictionaryValue* extensions = 0; 170 if (prefs.GetExtensionsBlock(&extensions)) { 171 VLOG(1) << "Extensions block found in master preferences"; 172 DoDelayedInstallExtensions(); 173 } 174 #endif 175 176 if (prefs.GetBool(installer::master_preferences::kDistroImportSearchPref, 177 &value)) { 178 if (value) { 179 out_prefs->do_import_items |= importer::SEARCH_ENGINES; 180 } else { 181 out_prefs->dont_import_items |= importer::SEARCH_ENGINES; 182 } 183 } 184 185 // Check to see if search engine logos should be randomized. 186 if (prefs.GetBool( 187 installer::master_preferences:: 188 kSearchEngineExperimentRandomizePref, 189 &value) && value) { 190 out_prefs->randomize_search_engine_experiment = true; 191 } 192 193 // If we're suppressing the first-run bubble, set that preference now. 194 // Otherwise, wait until the user has completed first run to set it, so the 195 // user is guaranteed to see the bubble iff he or she has completed the first 196 // run process. 197 if (prefs.GetBool( 198 installer::master_preferences::kDistroSuppressFirstRunBubble, 199 &value) && value) 200 FirstRun::SetShowFirstRunBubblePref(false); 201 202 if (prefs.GetBool( 203 installer::master_preferences::kDistroImportHistoryPref, 204 &value)) { 205 if (value) { 206 out_prefs->do_import_items |= importer::HISTORY; 207 } else { 208 out_prefs->dont_import_items |= importer::HISTORY; 209 } 210 } 211 212 std::string not_used; 213 out_prefs->homepage_defined = prefs.GetString(prefs::kHomePage, ¬_used); 214 215 if (prefs.GetBool( 216 installer::master_preferences::kDistroImportHomePagePref, 217 &value)) { 218 if (value) { 219 out_prefs->do_import_items |= importer::HOME_PAGE; 220 } else { 221 out_prefs->dont_import_items |= importer::HOME_PAGE; 222 } 223 } 224 225 // Bookmarks are never imported unless specifically turned on. 226 if (prefs.GetBool( 227 installer::master_preferences::kDistroImportBookmarksPref, 228 &value) && value) { 229 out_prefs->do_import_items |= importer::FAVORITES; 230 } 231 232 if (prefs.GetBool( 233 installer::master_preferences::kMakeChromeDefaultForUser, 234 &value) && value) { 235 out_prefs->make_chrome_default = true; 236 } 237 238 // TODO(mirandac): Refactor skip-first-run-ui process into regular first run 239 // import process. http://crbug.com/49647 240 // Note we are skipping all other master preferences if skip-first-run-ui 241 // is *not* specified. (That is, we continue only if skipping first run ui.) 242 if (!prefs.GetBool( 243 installer::master_preferences::kDistroSkipFirstRunPref, 244 &value) || !value) { 245 return true; 246 } 247 248 #if !defined(OS_WIN) 249 // From here on we won't show first run so we need to do the work to show the 250 // bubble anyway, unless it's already been explicitly suppressed. 251 FirstRun::SetShowFirstRunBubblePref(true); 252 #endif 253 254 // We need to be able to create the first run sentinel or else we cannot 255 // proceed because ImportSettings will launch the importer process which 256 // would end up here if the sentinel is not present. 257 if (!FirstRun::CreateSentinel()) 258 return false; 259 260 if (prefs.GetBool(installer::master_preferences::kDistroShowWelcomePage, 261 &value) && value) { 262 FirstRun::SetShowWelcomePagePref(); 263 } 264 265 std::string import_bookmarks_path; 266 prefs.GetString( 267 installer::master_preferences::kDistroImportBookmarksFromFilePref, 268 &import_bookmarks_path); 269 270 #if defined(OS_WIN) 271 if (!IsOrganicFirstRun()) { 272 // If search engines aren't explicitly imported, don't import. 273 if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) { 274 out_prefs->dont_import_items |= importer::SEARCH_ENGINES; 275 } 276 // If home page isn't explicitly imported, don't import. 277 if (!(out_prefs->do_import_items & importer::HOME_PAGE)) { 278 out_prefs->dont_import_items |= importer::HOME_PAGE; 279 } 280 // If history isn't explicitly forbidden, do import. 281 if (!(out_prefs->dont_import_items & importer::HISTORY)) { 282 out_prefs->do_import_items |= importer::HISTORY; 283 } 284 } 285 286 if (out_prefs->do_import_items || !import_bookmarks_path.empty()) { 287 // There is something to import from the default browser. This launches 288 // the importer process and blocks until done or until it fails. 289 scoped_refptr<ImporterList> importer_list(new ImporterList); 290 importer_list->DetectSourceProfilesHack(); 291 if (!FirstRun::ImportSettings(NULL, 292 importer_list->GetSourceProfileAt(0).importer_type, 293 out_prefs->do_import_items, 294 FilePath::FromWStringHack(UTF8ToWide(import_bookmarks_path)), 295 true, NULL)) { 296 LOG(WARNING) << "silent import failed"; 297 } 298 } 299 #else 300 if (!import_bookmarks_path.empty()) { 301 // There are bookmarks to import from a file. 302 FilePath path = FilePath::FromWStringHack(UTF8ToWide( 303 import_bookmarks_path)); 304 if (!FirstRun::ImportBookmarks(path)) { 305 LOG(WARNING) << "silent bookmark import failed"; 306 } 307 } 308 #endif 309 310 // Even on the first run we only allow for the user choice to take effect if 311 // no policy has been set by the admin. 312 if (!g_browser_process->local_state()->IsManagedPreference( 313 prefs::kDefaultBrowserSettingEnabled)) { 314 if (prefs.GetBool( 315 installer::master_preferences::kMakeChromeDefaultForUser, 316 &value) && value) { 317 ShellIntegration::SetAsDefaultBrowser(); 318 } 319 } else { 320 if (g_browser_process->local_state()->GetBoolean( 321 prefs::kDefaultBrowserSettingEnabled)) { 322 ShellIntegration::SetAsDefaultBrowser(); 323 } 324 } 325 326 return false; 327 } 328 329 // static 330 bool FirstRun::IsChromeFirstRun() { 331 if (first_run_ != FIRST_RUN_UNKNOWN) 332 return first_run_ == FIRST_RUN_TRUE; 333 334 FilePath first_run_sentinel; 335 if (!GetFirstRunSentinelFilePath(&first_run_sentinel) || 336 file_util::PathExists(first_run_sentinel)) { 337 first_run_ = FIRST_RUN_FALSE; 338 return false; 339 } 340 first_run_ = FIRST_RUN_TRUE; 341 return true; 342 } 343 344 // static 345 bool FirstRun::RemoveSentinel() { 346 FilePath first_run_sentinel; 347 if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) 348 return false; 349 return file_util::Delete(first_run_sentinel, false); 350 } 351 352 // static 353 bool FirstRun::CreateSentinel() { 354 FilePath first_run_sentinel; 355 if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) 356 return false; 357 return file_util::WriteFile(first_run_sentinel, "", 0) != -1; 358 } 359 360 // static 361 bool FirstRun::SetShowFirstRunBubblePref(bool show_bubble) { 362 PrefService* local_state = g_browser_process->local_state(); 363 if (!local_state) 364 return false; 365 if (!local_state->FindPreference(prefs::kShouldShowFirstRunBubble)) { 366 local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false); 367 local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, show_bubble); 368 } 369 return true; 370 } 371 372 // static 373 bool FirstRun::SetShowWelcomePagePref() { 374 PrefService* local_state = g_browser_process->local_state(); 375 if (!local_state) 376 return false; 377 if (!local_state->FindPreference(prefs::kShouldShowWelcomePage)) { 378 local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false); 379 local_state->SetBoolean(prefs::kShouldShowWelcomePage, true); 380 } 381 return true; 382 } 383 384 // static 385 bool FirstRun::SetPersonalDataManagerFirstRunPref() { 386 PrefService* local_state = g_browser_process->local_state(); 387 if (!local_state) 388 return false; 389 if (!local_state->FindPreference( 390 prefs::kAutofillPersonalDataManagerFirstRun)) { 391 local_state->RegisterBooleanPref( 392 prefs::kAutofillPersonalDataManagerFirstRun, false); 393 local_state->SetBoolean(prefs::kAutofillPersonalDataManagerFirstRun, true); 394 } 395 return true; 396 } 397 398 // static 399 bool FirstRun::SearchEngineSelectorDisallowed() { 400 // For now, the only case in which the search engine dialog should never be 401 // shown is if the locale is Russia. 402 std::string locale = g_browser_process->GetApplicationLocale(); 403 return (locale == "ru"); 404 } 405 406 // static 407 bool FirstRun::SetOEMFirstRunBubblePref() { 408 PrefService* local_state = g_browser_process->local_state(); 409 if (!local_state) 410 return false; 411 if (!local_state->FindPreference(prefs::kShouldUseOEMFirstRunBubble)) { 412 local_state->RegisterBooleanPref(prefs::kShouldUseOEMFirstRunBubble, 413 false); 414 local_state->SetBoolean(prefs::kShouldUseOEMFirstRunBubble, true); 415 } 416 return true; 417 } 418 419 // static 420 bool FirstRun::SetMinimalFirstRunBubblePref() { 421 PrefService* local_state = g_browser_process->local_state(); 422 if (!local_state) 423 return false; 424 if (!local_state->FindPreference(prefs::kShouldUseMinimalFirstRunBubble)) { 425 local_state->RegisterBooleanPref(prefs::kShouldUseMinimalFirstRunBubble, 426 false); 427 local_state->SetBoolean(prefs::kShouldUseMinimalFirstRunBubble, true); 428 } 429 return true; 430 } 431 432 // static 433 int FirstRun::ImportFromFile(Profile* profile, const CommandLine& cmdline) { 434 FilePath file_path = cmdline.GetSwitchValuePath(switches::kImportFromFile); 435 if (file_path.empty()) { 436 NOTREACHED(); 437 return false; 438 } 439 scoped_refptr<ImporterHost> importer_host(new ImporterHost); 440 importer_host->set_headless(); 441 442 importer::SourceProfile source_profile; 443 source_profile.importer_type = importer::BOOKMARKS_HTML; 444 source_profile.source_path = file_path; 445 446 FirstRunImportObserver importer_observer; 447 importer::ShowImportProgressDialog(NULL, 448 importer::FAVORITES, 449 importer_host, 450 &importer_observer, 451 source_profile, 452 profile, 453 true); 454 455 importer_observer.RunLoop(); 456 return importer_observer.import_result(); 457 } 458 459 // static 460 bool FirstRun::GetFirstRunSentinelFilePath(FilePath* path) { 461 FilePath first_run_sentinel; 462 463 #if defined(OS_WIN) 464 FilePath exe_path; 465 if (!PathService::Get(base::DIR_EXE, &exe_path)) 466 return false; 467 if (InstallUtil::IsPerUserInstall(exe_path.value().c_str())) { 468 first_run_sentinel = exe_path; 469 } else { 470 if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel)) 471 return false; 472 } 473 #else 474 if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel)) 475 return false; 476 #endif 477 478 *path = first_run_sentinel.AppendASCII(kSentinelFile); 479 return true; 480 } 481 482 // static 483 void FirstRun::AutoImport( 484 Profile* profile, 485 bool homepage_defined, 486 int import_items, 487 int dont_import_items, 488 bool search_engine_experiment, 489 bool randomize_search_engine_experiment, 490 bool make_chrome_default, 491 ProcessSingleton* process_singleton) { 492 // We need to avoid dispatching new tabs when we are importing because 493 // that will lead to data corruption or a crash. Because there is no UI for 494 // the import process, we pass NULL as the window to bring to the foreground 495 // when a CopyData message comes in; this causes the message to be silently 496 // discarded, which is the correct behavior during the import process. 497 process_singleton->Lock(NULL); 498 499 PlatformSetup(); 500 501 FilePath local_state_path; 502 PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path); 503 bool local_state_file_exists = file_util::PathExists(local_state_path); 504 505 scoped_refptr<ImporterHost> importer_host; 506 // TODO(csilv,mirandac): Out-of-process import has only been qualified on 507 // MacOS X, so we will only use it on that platform since it is required. 508 // Remove this conditional logic once oop import is qualified for 509 // Linux/Windows. http://crbug.com/22142 510 #if defined(OS_MACOSX) 511 importer_host = new ExternalProcessImporterHost; 512 #else 513 importer_host = new ImporterHost; 514 #endif 515 516 scoped_refptr<ImporterList> importer_list(new ImporterList); 517 importer_list->DetectSourceProfilesHack(); 518 519 // Do import if there is an available profile for us to import. 520 if (importer_list->count() > 0) { 521 // Don't show the warning dialog if import fails. 522 importer_host->set_headless(); 523 int items = 0; 524 525 // History is always imported unless turned off in master_preferences. 526 if (!(dont_import_items & importer::HISTORY)) 527 items = items | importer::HISTORY; 528 // Home page is imported in organic builds only unless turned off or 529 // defined in master_preferences. 530 if (IsOrganicFirstRun()) { 531 if (!(dont_import_items & importer::HOME_PAGE) && !homepage_defined) 532 items = items | importer::HOME_PAGE; 533 } else { 534 if (import_items & importer::HOME_PAGE) 535 items = items | importer::HOME_PAGE; 536 } 537 // Search engines are only imported in certain builds unless overridden 538 // in master_preferences. Search engines are not imported automatically 539 // if the user already has a user preferences directory. 540 if (IsOrganicFirstRun()) { 541 if (!(dont_import_items & importer::SEARCH_ENGINES) && 542 !local_state_file_exists) { 543 items = items | importer::SEARCH_ENGINES; 544 } 545 } else if (import_items & importer::SEARCH_ENGINES) { 546 items = items | importer::SEARCH_ENGINES; 547 } 548 549 // Bookmarks are never imported, unless turned on in master_preferences. 550 if (import_items & importer::FAVORITES) 551 items = items | importer::FAVORITES; 552 553 ImportSettings(profile, importer_host, importer_list, items); 554 } 555 556 UserMetrics::RecordAction(UserMetricsAction("FirstRunDef_Accept")); 557 558 // Launch the search engine dialog only for certain builds, and only if the 559 // user has not already set preferences. 560 if (IsOrganicFirstRun() && !local_state_file_exists) { 561 // The home page string may be set in the preferences, but the user should 562 // initially use Chrome with the NTP as home page in organic builds. 563 profile->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true); 564 first_run::ShowFirstRunDialog(profile, randomize_search_engine_experiment); 565 } 566 567 if (make_chrome_default) 568 ShellIntegration::SetAsDefaultBrowser(); 569 570 // Don't display the minimal bubble if there is no default search provider. 571 TemplateURLModel* search_engines_model = profile->GetTemplateURLModel(); 572 if (search_engines_model && 573 search_engines_model->GetDefaultSearchProvider()) { 574 FirstRun::SetShowFirstRunBubblePref(true); 575 // Set the first run bubble to minimal. 576 FirstRun::SetMinimalFirstRunBubblePref(); 577 } 578 FirstRun::SetShowWelcomePagePref(); 579 FirstRun::SetPersonalDataManagerFirstRunPref(); 580 581 process_singleton->Unlock(); 582 FirstRun::CreateSentinel(); 583 } 584 585 #if defined(OS_POSIX) 586 namespace { 587 588 // This class acts as an observer for the ImporterProgressObserver::ImportEnded 589 // callback. When the import process is started, certain errors may cause 590 // ImportEnded() to be called synchronously, but the typical case is that 591 // ImportEnded() is called asynchronously. Thus we have to handle both cases. 592 class ImportEndedObserver : public importer::ImporterProgressObserver { 593 public: 594 ImportEndedObserver() : ended_(false), 595 should_quit_message_loop_(false) {} 596 virtual ~ImportEndedObserver() {} 597 598 // importer::ImporterProgressObserver: 599 virtual void ImportStarted() OVERRIDE {} 600 virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {} 601 virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {} 602 virtual void ImportEnded() OVERRIDE { 603 ended_ = true; 604 if (should_quit_message_loop_) 605 MessageLoop::current()->Quit(); 606 } 607 608 void set_should_quit_message_loop() { 609 should_quit_message_loop_ = true; 610 } 611 612 bool ended() { 613 return ended_; 614 } 615 616 private: 617 // Set if the import has ended. 618 bool ended_; 619 620 // Set by the client (via set_should_quit_message_loop) if, when the import 621 // ends, this class should quit the message loop. 622 bool should_quit_message_loop_; 623 }; 624 625 } // namespace 626 627 // static 628 bool FirstRun::ImportSettings(Profile* profile, 629 scoped_refptr<ImporterHost> importer_host, 630 scoped_refptr<ImporterList> importer_list, 631 int items_to_import) { 632 const importer::SourceProfile& source_profile = 633 importer_list->GetSourceProfileAt(0); 634 635 // Ensure that importers aren't requested to import items that they do not 636 // support. 637 items_to_import &= source_profile.services_supported; 638 639 scoped_ptr<ImportEndedObserver> observer(new ImportEndedObserver); 640 importer_host->SetObserver(observer.get()); 641 importer_host->StartImportSettings(source_profile, 642 profile, 643 items_to_import, 644 new ProfileWriter(profile), 645 true); 646 // If the import process has not errored out, block on it. 647 if (!observer->ended()) { 648 observer->set_should_quit_message_loop(); 649 MessageLoop::current()->Run(); 650 } 651 652 // Unfortunately there's no success/fail signal in ImporterHost. 653 return true; 654 } 655 656 #endif // OS_POSIX 657