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