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