importer.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 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/importer/importer.h"
6
7#include "app/l10n_util.h"
8#include "base/thread.h"
9#include "base/values.h"
10#include "chrome/browser/bookmarks/bookmark_model.h"
11#include "chrome/browser/browser_list.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/browser_thread.h"
14#include "chrome/browser/browsing_instance.h"
15#include "chrome/browser/importer/firefox_profile_lock.h"
16#include "chrome/browser/importer/importer_bridge.h"
17#include "chrome/browser/renderer_host/site_instance.h"
18#include "chrome/browser/search_engines/template_url.h"
19#include "chrome/browser/search_engines/template_url_model.h"
20#include "chrome/browser/tabs/tab_strip_model.h"
21#include "chrome/browser/ui/browser_navigator.h"
22#include "chrome/browser/webdata/web_data_service.h"
23#include "chrome/common/notification_service.h"
24#include "gfx/codec/png_codec.h"
25#include "gfx/favicon_size.h"
26#include "grit/generated_resources.h"
27#include "skia/ext/image_operations.h"
28#include "webkit/glue/image_decoder.h"
29
30// TODO(port): Port these files.
31#if defined(OS_WIN)
32#include "app/win_util.h"
33#include "chrome/browser/views/importer_lock_view.h"
34#include "views/window/window.h"
35#elif defined(OS_MACOSX)
36#include "chrome/browser/cocoa/importer_lock_dialog.h"
37#elif defined(TOOLKIT_USES_GTK)
38#include "chrome/browser/gtk/import_lock_dialog_gtk.h"
39#endif
40
41using webkit_glue::PasswordForm;
42
43// Importer.
44
45Importer::Importer()
46    : cancelled_(false),
47      import_to_bookmark_bar_(false),
48      bookmark_bar_disabled_(false) {
49}
50
51Importer::~Importer() {
52}
53
54// static
55bool Importer::ReencodeFavicon(const unsigned char* src_data, size_t src_len,
56                               std::vector<unsigned char>* png_data) {
57  // Decode the favicon using WebKit's image decoder.
58  webkit_glue::ImageDecoder decoder(gfx::Size(kFavIconSize, kFavIconSize));
59  SkBitmap decoded = decoder.Decode(src_data, src_len);
60  if (decoded.empty())
61    return false;  // Unable to decode.
62
63  if (decoded.width() != kFavIconSize || decoded.height() != kFavIconSize) {
64    // The bitmap is not the correct size, re-sample.
65    int new_width = decoded.width();
66    int new_height = decoded.height();
67    calc_favicon_target_size(&new_width, &new_height);
68    decoded = skia::ImageOperations::Resize(
69        decoded, skia::ImageOperations::RESIZE_LANCZOS3, new_width, new_height);
70  }
71
72  // Encode our bitmap as a PNG.
73  gfx::PNGCodec::EncodeBGRASkBitmap(decoded, false, png_data);
74  return true;
75}
76
77// ImporterHost.
78
79ImporterHost::ImporterHost()
80    : profile_(NULL),
81      observer_(NULL),
82      task_(NULL),
83      importer_(NULL),
84      waiting_for_bookmarkbar_model_(false),
85      installed_bookmark_observer_(false),
86      is_source_readable_(true),
87      headless_(false),
88      parent_window_(NULL) {
89  importer_list_.DetectSourceProfiles();
90}
91
92ImporterHost::~ImporterHost() {
93  if (NULL != importer_)
94    importer_->Release();
95  if (installed_bookmark_observer_) {
96    DCHECK(profile_);  // Only way for waiting_for_bookmarkbar_model_ to be true
97                       // is if we have a profile.
98    profile_->GetBookmarkModel()->RemoveObserver(this);
99  }
100}
101
102void ImporterHost::Loaded(BookmarkModel* model) {
103  DCHECK(model->IsLoaded());
104  model->RemoveObserver(this);
105  waiting_for_bookmarkbar_model_ = false;
106  installed_bookmark_observer_ = false;
107
108  importer_->set_import_to_bookmark_bar(!model->HasBookmarks());
109  InvokeTaskIfDone();
110}
111
112void ImporterHost::BookmarkModelBeingDeleted(BookmarkModel* model) {
113  installed_bookmark_observer_ = false;
114}
115
116void ImporterHost::Observe(NotificationType type,
117                           const NotificationSource& source,
118                           const NotificationDetails& details) {
119  DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED);
120  registrar_.RemoveAll();
121  InvokeTaskIfDone();
122}
123
124void ImporterHost::ShowWarningDialog() {
125  if (headless_) {
126    OnLockViewEnd(false);
127  } else {
128#if defined(OS_WIN)
129    views::Window::CreateChromeWindow(NULL, gfx::Rect(),
130                                      new ImporterLockView(this))->Show();
131#elif defined(TOOLKIT_USES_GTK)
132    ImportLockDialogGtk::Show(parent_window_, this);
133#else
134    ImportLockDialogCocoa::ShowWarning(this);
135#endif
136  }
137}
138
139void ImporterHost::OnLockViewEnd(bool is_continue) {
140  if (is_continue) {
141    // User chose to continue, then we check the lock again to make
142    // sure that Firefox has been closed. Try to import the settings
143    // if successful. Otherwise, show a warning dialog.
144    firefox_lock_->Lock();
145    if (firefox_lock_->HasAcquired()) {
146      is_source_readable_ = true;
147      InvokeTaskIfDone();
148    } else {
149      ShowWarningDialog();
150    }
151  } else {
152    // User chose to skip the import process. We should delete
153    // the task and notify the ImporterHost to finish.
154    delete task_;
155    task_ = NULL;
156    importer_ = NULL;
157    ImportEnded();
158  }
159}
160
161void ImporterHost::StartImportSettings(
162    const importer::ProfileInfo& profile_info,
163    Profile* target_profile,
164    uint16 items,
165    ProfileWriter* writer,
166    bool first_run) {
167  DCHECK(!profile_);  // We really only support importing from one host at a
168                      // time.
169  profile_ = target_profile;
170  // Preserves the observer and creates a task, since we do async import
171  // so that it doesn't block the UI. When the import is complete, observer
172  // will be notified.
173  writer_ = writer;
174  importer_ = importer_list_.CreateImporterByType(profile_info.browser_type);
175  // If we fail to create Importer, exit as we cannot do anything.
176  if (!importer_) {
177    ImportEnded();
178    return;
179  }
180
181  importer_->AddRef();
182
183  importer_->set_import_to_bookmark_bar(ShouldImportToBookmarkBar(first_run));
184  importer_->set_bookmark_bar_disabled(first_run);
185  scoped_refptr<ImporterBridge> bridge(
186      new InProcessImporterBridge(writer_.get(), this));
187  task_ = NewRunnableMethod(importer_, &Importer::StartImport,
188      profile_info, items, bridge);
189
190  CheckForFirefoxLock(profile_info, items, first_run);
191
192#if defined(OS_WIN)
193  // For google toolbar import, we need the user to log in and store their GAIA
194  // credentials.
195  if (profile_info.browser_type == importer::GOOGLE_TOOLBAR5) {
196    if (!toolbar_importer_utils::IsGoogleGAIACookieInstalled()) {
197      win_util::MessageBox(
198          NULL,
199          l10n_util::GetString(IDS_IMPORTER_GOOGLE_LOGIN_TEXT).c_str(),
200          L"",
201          MB_OK | MB_TOPMOST);
202
203      GURL url("https://www.google.com/accounts/ServiceLogin");
204      BrowserList::GetLastActive()->AddSelectedTabWithURL(
205          url, PageTransition::TYPED);
206
207      MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
208          this, &ImporterHost::OnLockViewEnd, false));
209
210      is_source_readable_ = false;
211    }
212  }
213#endif
214
215  CheckForLoadedModels(items);
216  AddRef();
217  InvokeTaskIfDone();
218}
219
220void ImporterHost::Cancel() {
221  if (importer_)
222    importer_->Cancel();
223}
224
225void ImporterHost::SetObserver(Observer* observer) {
226  observer_ = observer;
227}
228
229void ImporterHost::InvokeTaskIfDone() {
230  if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() ||
231      !is_source_readable_)
232    return;
233  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, task_);
234}
235
236void ImporterHost::ImportItemStarted(importer::ImportItem item) {
237  if (observer_)
238    observer_->ImportItemStarted(item);
239}
240
241void ImporterHost::ImportItemEnded(importer::ImportItem item) {
242  if (observer_)
243    observer_->ImportItemEnded(item);
244}
245
246void ImporterHost::ImportStarted() {
247  if (observer_)
248    observer_->ImportStarted();
249}
250
251void ImporterHost::ImportEnded() {
252  firefox_lock_.reset();  // Release the Firefox profile lock.
253  if (observer_)
254    observer_->ImportEnded();
255  Release();
256}
257
258bool ImporterHost::ShouldImportToBookmarkBar(bool first_run) {
259  bool import_to_bookmark_bar = first_run;
260  if (profile_ && profile_->GetBookmarkModel()->IsLoaded()) {
261    import_to_bookmark_bar = (!profile_->GetBookmarkModel()->HasBookmarks());
262  }
263  return import_to_bookmark_bar;
264}
265
266void ImporterHost::CheckForFirefoxLock(
267    const importer::ProfileInfo& profile_info, uint16 items, bool first_run) {
268  if (profile_info.browser_type == importer::FIREFOX2 ||
269      profile_info.browser_type == importer::FIREFOX3) {
270    DCHECK(!firefox_lock_.get());
271    firefox_lock_.reset(new FirefoxProfileLock(profile_info.source_path));
272    if (!firefox_lock_->HasAcquired()) {
273      // If fail to acquire the lock, we set the source unreadable and
274      // show a warning dialog, unless running without UI.
275      is_source_readable_ = false;
276      ShowWarningDialog();
277    }
278  }
279}
280
281void ImporterHost::CheckForLoadedModels(uint16 items) {
282  // BookmarkModel should be loaded before adding IE favorites. So we observe
283  // the BookmarkModel if needed, and start the task after it has been loaded.
284  if ((items & importer::FAVORITES) && !writer_->BookmarkModelIsLoaded()) {
285    profile_->GetBookmarkModel()->AddObserver(this);
286    waiting_for_bookmarkbar_model_ = true;
287    installed_bookmark_observer_ = true;
288  }
289
290  // Observes the TemplateURLModel if needed to import search engines from the
291  // other browser. We also check to see if we're importing bookmarks because
292  // we can import bookmark keywords from Firefox as search engines.
293  if ((items & importer::SEARCH_ENGINES) || (items & importer::FAVORITES)) {
294    if (!writer_->TemplateURLModelIsLoaded()) {
295      TemplateURLModel* model = profile_->GetTemplateURLModel();
296      registrar_.Add(this, NotificationType::TEMPLATE_URL_MODEL_LOADED,
297                     Source<TemplateURLModel>(model));
298      model->Load();
299    }
300  }
301}
302
303ExternalProcessImporterHost::ExternalProcessImporterHost()
304    : items_(0),
305      import_to_bookmark_bar_(false),
306      cancelled_(false),
307      import_process_launched_(false) {
308}
309
310void ExternalProcessImporterHost::Loaded(BookmarkModel* model) {
311  DCHECK(model->IsLoaded());
312  model->RemoveObserver(this);
313  waiting_for_bookmarkbar_model_ = false;
314  installed_bookmark_observer_ = false;
315
316  // Because the import process is running externally, the decision whether
317  // to import to the bookmark bar must be stored here so that it can be
318  // passed to the importer when the import task is invoked.
319  import_to_bookmark_bar_ = (!model->HasBookmarks());
320  InvokeTaskIfDone();
321}
322
323void ExternalProcessImporterHost::Cancel() {
324  cancelled_ = true;
325  if (import_process_launched_)
326    client_->Cancel();
327  ImportEnded();  // Tells the observer that we're done, and releases us.
328}
329
330void ExternalProcessImporterHost::StartImportSettings(
331    const importer::ProfileInfo& profile_info,
332    Profile* target_profile,
333    uint16 items,
334    ProfileWriter* writer,
335    bool first_run) {
336  DCHECK(!profile_);
337  profile_ = target_profile;
338  writer_ = writer;
339  profile_info_ = &profile_info;
340  items_ = items;
341
342  ImporterHost::AddRef();  // Balanced in ImporterHost::ImportEnded.
343
344  import_to_bookmark_bar_ = ShouldImportToBookmarkBar(first_run);
345  CheckForFirefoxLock(profile_info, items, first_run);
346  CheckForLoadedModels(items);
347
348  InvokeTaskIfDone();
349}
350
351void ExternalProcessImporterHost::InvokeTaskIfDone() {
352  if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() ||
353      !is_source_readable_ || cancelled_)
354    return;
355
356  // The in-process half of the bridge which catches data from the IPC pipe
357  // and feeds it to the ProfileWriter.  The external process half of the
358  // bridge lives in the external process -- see ProfileImportThread.
359  // The ExternalProcessImporterClient created in the next line owns this
360  // bridge, and will delete it.
361  InProcessImporterBridge* bridge =
362      new InProcessImporterBridge(writer_.get(), this);
363  client_ = new ExternalProcessImporterClient(this, *profile_info_, items_,
364                                              bridge, import_to_bookmark_bar_);
365  import_process_launched_ = true;
366  client_->Start();
367}
368
369ExternalProcessImporterClient::ExternalProcessImporterClient(
370    ExternalProcessImporterHost* importer_host,
371    const importer::ProfileInfo& profile_info,
372    int items,
373    InProcessImporterBridge* bridge,
374    bool import_to_bookmark_bar)
375    : bookmarks_options_(0),
376      total_bookmarks_count_(0),
377      total_history_rows_count_(0),
378      total_fav_icons_count_(0),
379      process_importer_host_(importer_host),
380      profile_import_process_host_(NULL),
381      profile_info_(profile_info),
382      items_(items),
383      import_to_bookmark_bar_(import_to_bookmark_bar),
384      bridge_(bridge),
385      cancelled_(FALSE) {
386  bridge_->AddRef();
387  process_importer_host_->ImportStarted();
388}
389
390ExternalProcessImporterClient::~ExternalProcessImporterClient() {
391  bridge_->Release();
392}
393
394void ExternalProcessImporterClient::Start() {
395  AddRef();  // balanced in Cleanup.
396  BrowserThread::ID thread_id;
397  CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_id));
398  BrowserThread::PostTask(
399      BrowserThread::IO, FROM_HERE,
400      NewRunnableMethod(this,
401          &ExternalProcessImporterClient::StartProcessOnIOThread,
402          g_browser_process->resource_dispatcher_host(), thread_id));
403}
404
405void ExternalProcessImporterClient::StartProcessOnIOThread(
406    ResourceDispatcherHost* rdh,
407    BrowserThread::ID thread_id) {
408  profile_import_process_host_ =
409      new ProfileImportProcessHost(rdh, this, thread_id);
410  profile_import_process_host_->StartProfileImportProcess(profile_info_,
411      items_, import_to_bookmark_bar_);
412}
413
414void ExternalProcessImporterClient::Cancel() {
415  if (cancelled_)
416    return;
417
418  cancelled_ = true;
419  if (profile_import_process_host_) {
420    BrowserThread::PostTask(
421        BrowserThread::IO, FROM_HERE,
422        NewRunnableMethod(this,
423            &ExternalProcessImporterClient::CancelImportProcessOnIOThread));
424  }
425  Release();
426}
427
428void ExternalProcessImporterClient::CancelImportProcessOnIOThread() {
429  profile_import_process_host_->CancelProfileImportProcess();
430}
431
432void ExternalProcessImporterClient::NotifyItemFinishedOnIOThread(
433    importer::ImportItem import_item) {
434  profile_import_process_host_->ReportImportItemFinished(import_item);
435}
436
437void ExternalProcessImporterClient::OnProcessCrashed() {
438  if (cancelled_)
439    return;
440
441  process_importer_host_->Cancel();
442}
443
444void ExternalProcessImporterClient::Cleanup() {
445  if (cancelled_)
446    return;
447
448  if (process_importer_host_)
449    process_importer_host_->ImportEnded();
450  Release();
451}
452
453void ExternalProcessImporterClient::OnImportStart() {
454  if (cancelled_)
455    return;
456
457  bridge_->NotifyStarted();
458}
459
460void ExternalProcessImporterClient::OnImportFinished(bool succeeded,
461                                                     std::string error_msg) {
462  if (cancelled_)
463    return;
464
465  if (!succeeded)
466    LOG(WARNING) << "Import failed.  Error: " << error_msg;
467  Cleanup();
468}
469
470void ExternalProcessImporterClient::OnImportItemStart(int item_data) {
471  if (cancelled_)
472    return;
473
474  bridge_->NotifyItemStarted(static_cast<importer::ImportItem>(item_data));
475}
476
477void ExternalProcessImporterClient::OnImportItemFinished(int item_data) {
478  if (cancelled_)
479    return;
480
481  importer::ImportItem import_item =
482      static_cast<importer::ImportItem>(item_data);
483  bridge_->NotifyItemEnded(import_item);
484  BrowserThread::PostTask(
485      BrowserThread::IO, FROM_HERE,
486      NewRunnableMethod(this,
487          &ExternalProcessImporterClient::NotifyItemFinishedOnIOThread,
488          import_item));
489}
490
491void ExternalProcessImporterClient::OnHistoryImportStart(
492    size_t total_history_rows_count) {
493  if (cancelled_)
494    return;
495
496  total_history_rows_count_ = total_history_rows_count;
497  history_rows_.reserve(total_history_rows_count);
498}
499
500void ExternalProcessImporterClient::OnHistoryImportGroup(
501    const std::vector<history::URLRow>& history_rows_group,
502    int visit_source) {
503  if (cancelled_)
504    return;
505
506  history_rows_.insert(history_rows_.end(), history_rows_group.begin(),
507                       history_rows_group.end());
508  if (history_rows_.size() == total_history_rows_count_)
509    bridge_->SetHistoryItems(history_rows_,
510                             static_cast<history::VisitSource>(visit_source));
511}
512
513void ExternalProcessImporterClient::OnHomePageImportReady(
514    const GURL& home_page) {
515  if (cancelled_)
516    return;
517
518  bridge_->AddHomePage(home_page);
519}
520
521void ExternalProcessImporterClient::OnBookmarksImportStart(
522    const std::wstring first_folder_name,
523    int options, size_t total_bookmarks_count) {
524  if (cancelled_)
525    return;
526
527  bookmarks_first_folder_name_ = first_folder_name;
528  bookmarks_options_ = options;
529  total_bookmarks_count_ = total_bookmarks_count;
530  bookmarks_.reserve(total_bookmarks_count);
531}
532
533void ExternalProcessImporterClient::OnBookmarksImportGroup(
534    const std::vector<ProfileWriter::BookmarkEntry>& bookmarks_group) {
535  if (cancelled_)
536    return;
537
538  // Collect sets of bookmarks from importer process until we have reached
539  // total_bookmarks_count_:
540  bookmarks_.insert(bookmarks_.end(), bookmarks_group.begin(),
541                    bookmarks_group.end());
542  if (bookmarks_.size() == total_bookmarks_count_) {
543    bridge_->AddBookmarkEntries(bookmarks_, bookmarks_first_folder_name_,
544                                bookmarks_options_);
545  }
546}
547
548void ExternalProcessImporterClient::OnFavIconsImportStart(
549    size_t total_fav_icons_count) {
550  if (cancelled_)
551    return;
552
553  total_fav_icons_count_ = total_fav_icons_count;
554  fav_icons_.reserve(total_fav_icons_count);
555}
556
557void ExternalProcessImporterClient::OnFavIconsImportGroup(
558    const std::vector<history::ImportedFavIconUsage>& fav_icons_group) {
559  if (cancelled_)
560    return;
561
562  fav_icons_.insert(fav_icons_.end(), fav_icons_group.begin(),
563                    fav_icons_group.end());
564  if (fav_icons_.size() == total_fav_icons_count_)
565    bridge_->SetFavIcons(fav_icons_);
566}
567
568void ExternalProcessImporterClient::OnPasswordFormImportReady(
569    const webkit_glue::PasswordForm& form) {
570  if (cancelled_)
571    return;
572
573  bridge_->SetPasswordForm(form);
574}
575
576void ExternalProcessImporterClient::OnKeywordsImportReady(
577    const std::vector<TemplateURL>& template_urls,
578        int default_keyword_index, bool unique_on_host_and_path) {
579  if (cancelled_)
580    return;
581
582  std::vector<TemplateURL*> template_url_vec;
583  template_url_vec.reserve(template_urls.size());
584  std::vector<TemplateURL>::const_iterator iter;
585  for (iter = template_urls.begin();
586       iter != template_urls.end();
587       ++iter) {
588    template_url_vec.push_back(new TemplateURL(*iter));
589  }
590  bridge_->SetKeywords(template_url_vec, default_keyword_index,
591                       unique_on_host_and_path);
592}
593