summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorforshaw <forshaw@chromium.org>2015-12-03 06:08:12 -0800
committerCommit bot <commit-bot@chromium.org>2015-12-03 14:09:17 +0000
commit57e648081288644895bf6f5fe184597a63282336 (patch)
tree76f51cb9e552c318ec91ce13e9524e490c32bc58
parentcbe916974f8558032775f461b03b33358911f7c2 (diff)
downloadchromium_src-57e648081288644895bf6f5fe184597a63282336.zip
chromium_src-57e648081288644895bf6f5fe184597a63282336.tar.gz
chromium_src-57e648081288644895bf6f5fe184597a63282336.tar.bz2
Implement support for importing favorites from Edge on Windows 10.
This CL adds support for importing favorties from the Edge browser on Windows 10. It supports the old style importation from internet shortcuts (without supporting order due to structure changes) as well as importing from Edge 13 database format. BUG=539746 Review URL: https://codereview.chromium.org/1465853002 Cr-Commit-Position: refs/heads/master@{#362977}
-rw-r--r--chrome/app/bookmarks_strings.grdp3
-rw-r--r--chrome/app/generated_resources.grd3
-rw-r--r--chrome/browser/importer/edge_importer_browsertest_win.cc295
-rw-r--r--chrome/browser/importer/ie_importer_browsertest_win.cc4
-rw-r--r--chrome/browser/importer/importer_list.cc26
-rw-r--r--chrome/browser/importer/importer_uma.cc6
-rw-r--r--chrome/chrome_common.gypi6
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/chrome_utility.gypi8
-rw-r--r--chrome/common/importer/edge_importer_utils_win.cc87
-rw-r--r--chrome/common/importer/edge_importer_utils_win.h28
-rw-r--r--chrome/common/importer/ie_importer_utils_win.cc4
-rw-r--r--chrome/common/importer/ie_importer_utils_win.h6
-rw-r--r--chrome/common/importer/importer_test_registry_overrider_win.cc (renamed from chrome/common/importer/ie_importer_test_registry_overrider_win.cc)10
-rw-r--r--chrome/common/importer/importer_test_registry_overrider_win.h (renamed from chrome/common/importer/ie_importer_test_registry_overrider_win.h)20
-rw-r--r--chrome/common/importer/importer_type.h5
-rw-r--r--chrome/common/importer/profile_import_process_param_traits_macros.h6
-rw-r--r--chrome/test/data/edge_database_reader/random.edb.gzbin0 -> 2082 bytes
-rw-r--r--chrome/test/data/edge_database_reader/testdata.edb.gzbin0 -> 9548 bytes
-rw-r--r--chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/DBStore/spartan.edb.gzbin0 -> 15443 bytes
-rw-r--r--chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/Favorites/dummy.icobin0 -> 119 bytes
-rw-r--r--chrome/test/data/edge_profile/Favorites/Google.url10
-rw-r--r--chrome/utility/BUILD.gn4
-rw-r--r--chrome/utility/importer/DEPS1
-rw-r--r--chrome/utility/importer/edge_database_reader_unittest_win.cc448
-rw-r--r--chrome/utility/importer/edge_database_reader_win.cc257
-rw-r--r--chrome/utility/importer/edge_database_reader_win.h95
-rw-r--r--chrome/utility/importer/edge_importer_win.cc288
-rw-r--r--chrome/utility/importer/edge_importer_win.h43
-rw-r--r--chrome/utility/importer/ie_importer_win.cc25
-rw-r--r--chrome/utility/importer/ie_importer_win.h5
-rw-r--r--chrome/utility/importer/importer_creator.cc7
-rw-r--r--tools/metrics/histograms/histograms.xml1
34 files changed, 1669 insertions, 34 deletions
diff --git a/chrome/app/bookmarks_strings.grdp b/chrome/app/bookmarks_strings.grdp
index 5e9c0f0..23395d1 100644
--- a/chrome/app/bookmarks_strings.grdp
+++ b/chrome/app/bookmarks_strings.grdp
@@ -16,6 +16,9 @@
<message name="IDS_BOOKMARK_GROUP_FROM_IE" desc="The group name of bookmarks from Internet Explorer">
Imported From IE
</message>
+ <message name="IDS_BOOKMARK_GROUP_FROM_EDGE" desc="The group name of bookmarks from Edge">
+ Imported From Edge
+ </message>
</if>
<message name="IDS_BOOKMARK_GROUP_FROM_FIREFOX" desc="The group name of bookmarks from Firefox">
Imported From Firefox
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index b8fe7b2..c9a811d 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7467,6 +7467,9 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_IMPORT_FROM_IE" desc="browser combo box: Microsoft Internet Explorer">
Microsoft Internet Explorer
</message>
+ <message name="IDS_IMPORT_FROM_EDGE" desc="browser combo box: Microsoft Edge">
+ Microsoft Edge
+ </message>
</if>
<message name="IDS_IMPORT_FROM_FIREFOX" desc="browser combo box: Mozilla Firefox">
Mozilla Firefox
diff --git a/chrome/browser/importer/edge_importer_browsertest_win.cc b/chrome/browser/importer/edge_importer_browsertest_win.cc
new file mode 100644
index 0000000..e0f883a
--- /dev/null
+++ b/chrome/browser/importer/edge_importer_browsertest_win.cc
@@ -0,0 +1,295 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "chrome/browser/importer/external_process_importer_host.h"
+#include "chrome/browser/importer/importer_progress_observer.h"
+#include "chrome/browser/importer/importer_unittest_utils.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/importer/edge_importer_utils_win.h"
+#include "chrome/common/importer/imported_bookmark_entry.h"
+#include "chrome/common/importer/importer_bridge.h"
+#include "chrome/common/importer/importer_data_types.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/compression/compression_utils.h"
+#include "components/favicon_base/favicon_usage_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct FaviconGroup {
+ const base::char16* favicon_url;
+ const base::char16* site_url;
+};
+
+class TestObserver : public ProfileWriter,
+ public importer::ImporterProgressObserver {
+ public:
+ explicit TestObserver(
+ const std::vector<BookmarkInfo>& expected_bookmark_entries,
+ const std::vector<FaviconGroup>& expected_favicon_groups)
+ : ProfileWriter(nullptr),
+ bookmark_count_(0),
+ expected_bookmark_entries_(expected_bookmark_entries),
+ expected_favicon_groups_(expected_favicon_groups),
+ favicon_count_(0) {}
+
+ // importer::ImporterProgressObserver:
+ void ImportStarted() override {}
+ void ImportItemStarted(importer::ImportItem item) override {}
+ void ImportItemEnded(importer::ImportItem item) override {}
+ void ImportEnded() override {
+ base::MessageLoop::current()->QuitWhenIdle();
+ EXPECT_EQ(expected_bookmark_entries_.size(), bookmark_count_);
+ EXPECT_EQ(expected_favicon_groups_.size(), favicon_count_);
+ }
+
+ // ProfileWriter:
+ bool BookmarkModelIsLoaded() const override {
+ // Profile is ready for writing.
+ return true;
+ }
+
+ bool TemplateURLServiceIsLoaded() const override { return true; }
+
+ void AddBookmarks(const std::vector<ImportedBookmarkEntry>& bookmarks,
+ const base::string16& top_level_folder_name) override {
+ ASSERT_EQ(expected_bookmark_entries_.size(), bookmarks.size());
+ for (size_t i = 0; i < bookmarks.size(); ++i) {
+ EXPECT_NO_FATAL_FAILURE(
+ TestEqualBookmarkEntry(bookmarks[i], expected_bookmark_entries_[i]))
+ << i;
+ ++bookmark_count_;
+ }
+ }
+
+ void AddFavicons(const favicon_base::FaviconUsageDataList& usage) override {
+ // Importer should group the favicon information for each favicon URL.
+ ASSERT_EQ(expected_favicon_groups_.size(), usage.size());
+ for (size_t i = 0; i < expected_favicon_groups_.size(); ++i) {
+ GURL favicon_url(expected_favicon_groups_[i].favicon_url);
+ std::set<GURL> urls;
+ urls.insert(GURL(expected_favicon_groups_[i].site_url));
+
+ bool expected_favicon_url_found = false;
+ for (size_t j = 0; j < usage.size(); ++j) {
+ if (usage[j].favicon_url == favicon_url) {
+ EXPECT_EQ(urls, usage[j].urls);
+ expected_favicon_url_found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(expected_favicon_url_found);
+ }
+ favicon_count_ += usage.size();
+ }
+
+ private:
+ ~TestObserver() override {}
+
+ // This is the count of bookmark entries observed during the test.
+ size_t bookmark_count_;
+ // This is the expected list of bookmark entries to observe during the test.
+ std::vector<BookmarkInfo> expected_bookmark_entries_;
+ // This is the expected list of favicon groups to observe during the test.
+ std::vector<FaviconGroup> expected_favicon_groups_;
+ // This is the count of favicon groups observed during the test.
+ size_t favicon_count_;
+};
+
+bool DecompressDatabase(const base::FilePath& data_path) {
+ base::FilePath output_file = data_path.Append(
+ L"DataStore\\Data\\nouser1\\120712-0049\\DBStore\\spartan.edb");
+ base::FilePath gzip_file = output_file.AddExtension(L".gz");
+ std::string gzip_data;
+ if (!base::ReadFileToString(gzip_file, &gzip_data))
+ return false;
+ if (!compression::GzipUncompress(gzip_data, &gzip_data))
+ return false;
+ return base::WriteFile(output_file, gzip_data.c_str(), gzip_data.size()) >= 0;
+}
+
+const char kDummyFaviconImageData[] =
+ "\x42\x4D" // Magic signature 'BM'
+ "\x1E\x00\x00\x00" // File size
+ "\x00\x00\x00\x00" // Reserved
+ "\x1A\x00\x00\x00" // Offset of the pixel data
+ "\x0C\x00\x00\x00" // Header Size
+ "\x01\x00\x01\x00" // Size: 1x1
+ "\x01\x00" // Reserved
+ "\x18\x00" // 24-bits
+ "\x00\xFF\x00\x00"; // The pixel
+
+} // namespace
+
+// These tests need to be browser tests in order to be able to run the OOP
+// import (via ExternalProcessImporterHost) which launches a utility process.
+class EdgeImporterBrowserTest : public InProcessBrowserTest {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ // This will launch the browser test and thus needs to happen last.
+ InProcessBrowserTest::SetUp();
+ }
+
+ base::ScopedTempDir temp_dir_;
+
+ // Overrides the default registry key for Edge tests.
+ ImporterTestRegistryOverrider test_registry_overrider_;
+};
+
+IN_PROC_BROWSER_TEST_F(EdgeImporterBrowserTest, EdgeImporter) {
+ // Only verified to work with ESE library on Windows 8.1 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN8_1)
+ return;
+
+ const BookmarkInfo kEdgeBookmarks[] = {
+ {true,
+ 2,
+ {"Links", "SubFolderOfLinks"},
+ L"SubLink",
+ "http://www.links-sublink.com/"},
+ {true, 1, {"Links"}, L"TheLink", "http://www.links-thelink.com/"},
+ {false, 0, {}, L"Google Home Page", "http://www.google.com/"},
+ {false, 0, {}, L"TheLink", "http://www.links-thelink.com/"},
+ {false, 1, {"SubFolder"}, L"Title", "http://www.link.com/"},
+ {false, 0, {}, L"WithPortAndQuery", "http://host:8080/cgi?q=query"},
+ {false, 1, {"a"}, L"\x4E2D\x6587", "http://chinese-title-favorite/"},
+ {false, 0, {}, L"SubFolder", "http://www.subfolder.com/"},
+ {false, 0, {}, L"InvalidFavicon", "http://www.invalid-favicon.com/"},
+ };
+ std::vector<BookmarkInfo> bookmark_entries(
+ kEdgeBookmarks, kEdgeBookmarks + arraysize(kEdgeBookmarks));
+
+ const FaviconGroup kEdgeFaviconGroup[] = {
+ {L"http://www.links-sublink.com/favicon.ico",
+ L"http://www.links-sublink.com"},
+ {L"http://www.links-thelink.com/favicon.ico",
+ L"http://www.links-thelink.com"},
+ {L"http://www.google.com/favicon.ico", L"http://www.google.com"},
+ {L"http://www.links-thelink.com/favicon.ico",
+ L"http://www.links-thelink.com"},
+ {L"http://www.link.com/favicon.ico", L"http://www.link.com"},
+ {L"http://host:8080/favicon.ico", L"http://host:8080/cgi?q=query"},
+ {L"http://chinese-title-favorite/favicon.ico",
+ L"http://chinese-title-favorite"},
+ {L"http://www.subfolder.com/favicon.ico", L"http://www.subfolder.com"},
+ };
+
+ std::vector<FaviconGroup> favicon_groups(
+ kEdgeFaviconGroup, kEdgeFaviconGroup + arraysize(kEdgeFaviconGroup));
+
+ base::FilePath data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("edge_profile");
+
+ base::FilePath temp_path = temp_dir_.path();
+ ASSERT_TRUE(base::CopyDirectory(data_path, temp_path, true));
+ ASSERT_TRUE(DecompressDatabase(temp_path.AppendASCII("edge_profile")));
+
+ base::string16 key_path(importer::GetEdgeSettingsKey());
+ base::win::RegKey key;
+ ASSERT_EQ(ERROR_SUCCESS,
+ key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE));
+ key.WriteValue(L"FavoritesESEEnabled", 1);
+ ASSERT_FALSE(importer::IsEdgeFavoritesLegacyMode());
+
+ // Starts to import the above settings.
+ // Deletes itself.
+ ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
+ scoped_refptr<TestObserver> observer(
+ new TestObserver(bookmark_entries, favicon_groups));
+ host->set_observer(observer.get());
+
+ importer::SourceProfile source_profile;
+ source_profile.importer_type = importer::TYPE_EDGE;
+ source_profile.source_path = temp_path.AppendASCII("edge_profile");
+
+ host->StartImportSettings(source_profile, browser()->profile(),
+ importer::FAVORITES, observer.get());
+ base::MessageLoop::current()->Run();
+}
+
+IN_PROC_BROWSER_TEST_F(EdgeImporterBrowserTest, EdgeImporterLegacyFallback) {
+ const BookmarkInfo kEdgeBookmarks[] = {
+ {false, 0, {}, L"Google", "http://www.google.com/"}};
+ std::vector<BookmarkInfo> bookmark_entries(
+ kEdgeBookmarks, kEdgeBookmarks + arraysize(kEdgeBookmarks));
+ const FaviconGroup kEdgeFaviconGroup[] = {
+ {L"http://www.google.com/favicon.ico", L"http://www.google.com/"}};
+ std::vector<FaviconGroup> favicon_groups(
+ kEdgeFaviconGroup, kEdgeFaviconGroup + arraysize(kEdgeFaviconGroup));
+
+ base::FilePath data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("edge_profile");
+
+ ASSERT_TRUE(base::CopyDirectory(data_path, temp_dir_.path(), true));
+ ASSERT_TRUE(importer::IsEdgeFavoritesLegacyMode());
+
+ // Starts to import the above settings.
+ // Deletes itself.
+ ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
+ scoped_refptr<TestObserver> observer(
+ new TestObserver(bookmark_entries, favicon_groups));
+ host->set_observer(observer.get());
+
+ importer::SourceProfile source_profile;
+ source_profile.importer_type = importer::TYPE_EDGE;
+ base::FilePath source_path = temp_dir_.path().AppendASCII("edge_profile");
+ ASSERT_NE(base::WriteFile(
+ source_path.AppendASCII("Favorites\\Google.url:favicon:$DATA"),
+ kDummyFaviconImageData, sizeof(kDummyFaviconImageData)),
+ -1);
+ source_profile.source_path = source_path;
+
+ host->StartImportSettings(source_profile, browser()->profile(),
+ importer::FAVORITES, observer.get());
+ base::MessageLoop::current()->Run();
+}
+
+IN_PROC_BROWSER_TEST_F(EdgeImporterBrowserTest, EdgeImporterNoDatabase) {
+ // Only verified to work with ESE library on Windows 8.1 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN8_1)
+ return;
+
+ std::vector<BookmarkInfo> bookmark_entries;
+ std::vector<FaviconGroup> favicon_groups;
+
+ base::string16 key_path(importer::GetEdgeSettingsKey());
+ base::win::RegKey key;
+ ASSERT_EQ(ERROR_SUCCESS,
+ key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE));
+ key.WriteValue(L"FavoritesESEEnabled", 1);
+ ASSERT_FALSE(importer::IsEdgeFavoritesLegacyMode());
+
+ // Starts to import the above settings.
+ // Deletes itself.
+ ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
+ scoped_refptr<TestObserver> observer(
+ new TestObserver(bookmark_entries, favicon_groups));
+ host->set_observer(observer.get());
+
+ importer::SourceProfile source_profile;
+ source_profile.importer_type = importer::TYPE_EDGE;
+ source_profile.source_path = temp_dir_.path();
+
+ host->StartImportSettings(source_profile, browser()->profile(),
+ importer::FAVORITES, observer.get());
+ base::MessageLoop::current()->Run();
+}
diff --git a/chrome/browser/importer/ie_importer_browsertest_win.cc b/chrome/browser/importer/ie_importer_browsertest_win.cc
index 168ab45..800d726 100644
--- a/chrome/browser/importer/ie_importer_browsertest_win.cc
+++ b/chrome/browser/importer/ie_importer_browsertest_win.cc
@@ -32,11 +32,11 @@
#include "chrome/browser/importer/importer_unittest_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
-#include "chrome/common/importer/ie_importer_test_registry_overrider_win.h"
#include "chrome/common/importer/ie_importer_utils_win.h"
#include "chrome/common/importer/imported_bookmark_entry.h"
#include "chrome/common/importer/importer_bridge.h"
#include "chrome/common/importer/importer_data_types.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/core/common/password_form.h"
@@ -436,7 +436,7 @@ class IEImporterBrowserTest : public InProcessBrowserTest {
// Overrides the default registry key for IE registry keys like favorites,
// settings, password store, etc.
- IEImporterTestRegistryOverrider test_registry_overrider_;
+ ImporterTestRegistryOverrider test_registry_overrider_;
};
IN_PROC_BROWSER_TEST_F(IEImporterBrowserTest, IEImporter) {
diff --git a/chrome/browser/importer/importer_list.cc b/chrome/browser/importer/importer_list.cc
index de8987d..e006503 100644
--- a/chrome/browser/importer/importer_list.cc
+++ b/chrome/browser/importer/importer_list.cc
@@ -20,6 +20,10 @@
#include "chrome/common/importer/safari_importer_utils.h"
#endif
+#if defined(OS_WIN)
+#include "chrome/common/importer/edge_importer_utils_win.h"
+#endif
+
using content::BrowserThread;
namespace {
@@ -37,6 +41,24 @@ void DetectIEProfiles(std::vector<importer::SourceProfile*>* profiles) {
importer::COOKIES | importer::PASSWORDS | importer::SEARCH_ENGINES;
profiles->push_back(ie);
}
+
+void DetectEdgeProfiles(std::vector<importer::SourceProfile*>* profiles) {
+ importer::SourceProfile* edge = new importer::SourceProfile;
+ edge->importer_name = l10n_util::GetStringUTF16(IDS_IMPORT_FROM_EDGE);
+ edge->importer_type = importer::TYPE_EDGE;
+ edge->services_supported = importer::FAVORITES;
+ edge->source_path = importer::GetEdgeDataFilePath();
+ profiles->push_back(edge);
+}
+
+void DetectBuiltinWindowsProfiles(
+ std::vector<importer::SourceProfile*>* profiles) {
+ // Make the assumption on Windows 10 that Edge exists and is probably default.
+ if (importer::EdgeImporterCanImport())
+ DetectEdgeProfiles(profiles);
+ DetectIEProfiles(profiles);
+}
+
#endif // defined(OS_WIN)
#if defined(OS_MACOSX)
@@ -111,9 +133,9 @@ std::vector<importer::SourceProfile*> DetectSourceProfilesWorker(
#if defined(OS_WIN)
if (ShellIntegration::IsFirefoxDefaultBrowser()) {
DetectFirefoxProfiles(locale, &profiles);
- DetectIEProfiles(&profiles);
+ DetectBuiltinWindowsProfiles(&profiles);
} else {
- DetectIEProfiles(&profiles);
+ DetectBuiltinWindowsProfiles(&profiles);
DetectFirefoxProfiles(locale, &profiles);
}
#elif defined(OS_MACOSX)
diff --git a/chrome/browser/importer/importer_uma.cc b/chrome/browser/importer/importer_uma.cc
index 839dd6e..da87e84f 100644
--- a/chrome/browser/importer/importer_uma.cc
+++ b/chrome/browser/importer/importer_uma.cc
@@ -22,6 +22,9 @@ enum ImporterTypeMetrics {
#endif
IMPORTER_METRICS_GOOGLE_TOOLBAR5 = 5, // obsolete
IMPORTER_METRICS_BOOKMARKS_FILE = 6,
+#if defined(OS_WIN)
+ IMPORTER_METRICS_EDGE = 7,
+#endif
// Insert new values here. Never remove any existing values, as this enum is
// used to bucket a UMA histogram, and removing values breaks that.
@@ -41,6 +44,9 @@ void LogImporterUseToMetrics(const std::string& metric_postfix,
case TYPE_IE:
metrics_type = IMPORTER_METRICS_IE;
break;
+ case TYPE_EDGE:
+ metrics_type = IMPORTER_METRICS_EDGE;
+ break;
#endif
case TYPE_FIREFOX:
metrics_type = IMPORTER_METRICS_FIREFOX3;
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index c605f17..691651d 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -232,13 +232,13 @@
'common/safe_browsing/zip_analyzer_results.h',
],
'chrome_common_importer_sources': [
+ 'common/importer/edge_importer_utils_win.cc',
+ 'common/importer/edge_importer_utils_win.h',
'common/importer/firefox_importer_utils.cc',
'common/importer/firefox_importer_utils.h',
'common/importer/firefox_importer_utils_linux.cc',
'common/importer/firefox_importer_utils_mac.mm',
'common/importer/firefox_importer_utils_win.cc',
- 'common/importer/ie_importer_test_registry_overrider_win.cc',
- 'common/importer/ie_importer_test_registry_overrider_win.h',
'common/importer/ie_importer_utils_win.cc',
'common/importer/ie_importer_utils_win.h',
'common/importer/imported_bookmark_entry.cc',
@@ -249,6 +249,8 @@
'common/importer/importer_bridge.h',
'common/importer/importer_data_types.cc',
'common/importer/importer_data_types.h',
+ 'common/importer/importer_test_registry_overrider_win.cc',
+ 'common/importer/importer_test_registry_overrider_win.h',
'common/importer/importer_type.h',
'common/importer/importer_url_row.cc',
'common/importer/importer_url_row.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 5ff50cc..20c6f40 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -306,6 +306,7 @@
'browser/history/redirect_browsertest.cc',
'browser/iframe_browsertest.cc',
'browser/image_decoder_browsertest.cc',
+ 'browser/importer/edge_importer_browsertest_win.cc',
'browser/importer/firefox_importer_browsertest.cc',
'browser/importer/ie_importer_browsertest_win.cc',
'browser/importer/importer_unittest_utils.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index b510481..6ab5482 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1607,6 +1607,7 @@
'test/base/browser_with_test_window_test.h',
'utility/importer/bookmark_html_reader_unittest.cc',
'utility/importer/bookmarks_file_importer_unittest.cc',
+ 'utility/importer/edge_database_reader_unittest_win.cc',
'utility/importer/firefox_importer_unittest.cc',
'utility/importer/firefox_importer_unittest_messages_internal.h',
'utility/importer/firefox_importer_unittest_utils.h',
diff --git a/chrome/chrome_utility.gypi b/chrome/chrome_utility.gypi
index c487273..7665f0b 100644
--- a/chrome/chrome_utility.gypi
+++ b/chrome/chrome_utility.gypi
@@ -44,6 +44,10 @@
'utility/importer/bookmark_html_reader.h',
'utility/importer/bookmarks_file_importer.cc',
'utility/importer/bookmarks_file_importer.h',
+ 'utility/importer/edge_database_reader_win.cc',
+ 'utility/importer/edge_database_reader_win.h',
+ 'utility/importer/edge_importer_win.cc',
+ 'utility/importer/edge_importer_win.h',
'utility/importer/external_process_importer_bridge.cc',
'utility/importer/external_process_importer_bridge.h',
'utility/importer/favicon_reencode.cc',
@@ -145,7 +149,11 @@
# Prevent wininet from loading in the renderer.
# http://crbug.com/460679
'wininet.dll',
+ 'esent.dll',
],
+ 'AdditionalDependencies': [
+ 'esent.lib',
+ ]
},
},
},
diff --git a/chrome/common/importer/edge_importer_utils_win.cc b/chrome/common/importer/edge_importer_utils_win.cc
new file mode 100644
index 0000000..c2abd64
--- /dev/null
+++ b/chrome/common/importer/edge_importer_utils_win.cc
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/importer/edge_importer_utils_win.h"
+
+#include <Shlobj.h>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
+
+namespace {
+
+const base::char16 kEdgeSettingsMainKey[] = L"MicrosoftEdge\\Main";
+
+const base::char16 kEdgePackageName[] =
+ L"microsoft.microsoftedge_8wekyb3d8bbwe";
+
+// We assume at the moment that the package name never changes for Edge.
+base::string16 GetEdgePackageName() {
+ return kEdgePackageName;
+}
+
+base::string16 GetEdgeRegistryKey(const base::string16& key_name) {
+ base::string16 registry_key =
+ L"Software\\Classes\\Local Settings\\"
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\"
+ L"Storage\\";
+ registry_key += GetEdgePackageName();
+ registry_key += L"\\";
+ registry_key += key_name;
+ return registry_key;
+}
+
+base::string16 GetPotentiallyOverridenEdgeKey(
+ const base::string16& desired_key_path) {
+ base::string16 test_registry_override(
+ ImporterTestRegistryOverrider::GetTestRegistryOverride());
+ return test_registry_override.empty() ? GetEdgeRegistryKey(desired_key_path)
+ : test_registry_override;
+}
+
+} // namespace
+
+namespace importer {
+
+base::string16 GetEdgeSettingsKey() {
+ return GetPotentiallyOverridenEdgeKey(kEdgeSettingsMainKey);
+}
+
+base::FilePath GetEdgeDataFilePath() {
+ wchar_t buffer[MAX_PATH];
+ if (::SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT,
+ buffer) != S_OK)
+ return base::FilePath();
+
+ base::FilePath base_path(buffer);
+ base::string16 rel_path = L"Packages\\";
+ rel_path += GetEdgePackageName();
+ rel_path += L"\\AC\\MicrosoftEdge\\User\\Default";
+ return base_path.Append(rel_path);
+}
+
+bool IsEdgeFavoritesLegacyMode() {
+ base::win::RegKey key(HKEY_CURRENT_USER, GetEdgeSettingsKey().c_str(),
+ KEY_READ);
+ DWORD ese_enabled = 0;
+ // Check whether Edge is using the new Extensible Store Engine (ESE) format
+ // for its favorites.
+ if (key.ReadValueDW(L"FavoritesESEEnabled", &ese_enabled) == ERROR_SUCCESS)
+ return !ese_enabled;
+ return true;
+}
+
+bool EdgeImporterCanImport() {
+ base::File::Info file_info;
+ if (base::win::GetVersion() < base::win::VERSION_WIN10)
+ return false;
+ return base::GetFileInfo(GetEdgeDataFilePath(), &file_info) &&
+ file_info.is_directory;
+}
+
+} // namespace importer
diff --git a/chrome/common/importer/edge_importer_utils_win.h b/chrome/common/importer/edge_importer_utils_win.h
new file mode 100644
index 0000000..4d54be7
--- /dev/null
+++ b/chrome/common/importer/edge_importer_utils_win.h
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
+#define CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+
+namespace importer {
+
+// Returns the key to be used in HKCU to look for Edge's settings.
+// Overridable by tests via ImporterTestRegistryOverrider.
+base::string16 GetEdgeSettingsKey();
+
+// Returns the data path for the Edge browser. Returns an empty path on error.
+base::FilePath GetEdgeDataFilePath();
+
+// Returns true if Edge favorites is currently in legacy (pre-Edge 13) mode.
+bool IsEdgeFavoritesLegacyMode();
+
+// Returns true if the Edge browser is installed and available for import.
+bool EdgeImporterCanImport();
+
+} // namespace importer
+
+#endif // CHROME_COMMON_IMPORTER_EDGE_IMPORTER_UTILS_WIN_H_
diff --git a/chrome/common/importer/ie_importer_utils_win.cc b/chrome/common/importer/ie_importer_utils_win.cc
index 4275a8e..726e5b0 100644
--- a/chrome/common/importer/ie_importer_utils_win.cc
+++ b/chrome/common/importer/ie_importer_utils_win.cc
@@ -4,7 +4,7 @@
#include "chrome/common/importer/ie_importer_utils_win.h"
-#include "chrome/common/importer/ie_importer_test_registry_overrider_win.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
namespace {
@@ -21,7 +21,7 @@ const base::char16 kIESettingsMainKey[] =
base::string16 GetPotentiallyOverridenIEKey(
const base::string16& desired_key_path) {
base::string16 test_reg_override(
- IEImporterTestRegistryOverrider::GetTestRegistryOverride());
+ ImporterTestRegistryOverrider::GetTestRegistryOverride());
return test_reg_override.empty() ? desired_key_path : test_reg_override;
}
diff --git a/chrome/common/importer/ie_importer_utils_win.h b/chrome/common/importer/ie_importer_utils_win.h
index 0269115..8e43bba 100644
--- a/chrome/common/importer/ie_importer_utils_win.h
+++ b/chrome/common/importer/ie_importer_utils_win.h
@@ -10,15 +10,15 @@
namespace importer {
// Returns the key to be used in HKCU to look for IE's favorites order blob.
-// Overridable by tests via IEImporterTestRegistryOverrider.
+// Overridable by tests via ImporterTestRegistryOverrider.
base::string16 GetIEFavoritesOrderKey();
// Returns the key to be used in HKCU to look for IE7 passwords.
-// Overridable by tests via IEImporterTestRegistryOverrider.
+// Overridable by tests via ImporterTestRegistryOverrider.
base::string16 GetIE7PasswordsKey();
// Returns the key to be used in HKCU to look for IE settings.
-// Overridable by tests via IEImporterTestRegistryOverrider.
+// Overridable by tests via ImporterTestRegistryOverrider.
base::string16 GetIESettingsKey();
} // namespace importer
diff --git a/chrome/common/importer/ie_importer_test_registry_overrider_win.cc b/chrome/common/importer/importer_test_registry_overrider_win.cc
index e84cb65..d4f3f47 100644
--- a/chrome/common/importer/ie_importer_test_registry_overrider_win.cc
+++ b/chrome/common/importer/importer_test_registry_overrider_win.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/common/importer/ie_importer_test_registry_overrider_win.h"
+#include "chrome/common/importer/importer_test_registry_overrider_win.h"
#include <windows.h>
@@ -37,9 +37,9 @@ bool GetTestKeyFromEnvironment(base::string16* key) {
} // namespace
////////////////////////////////////////////////////////////////////////////////
-// IEImporterTestRegistryOverrider, public:
+// ImporterTestRegistryOverrider, public:
-IEImporterTestRegistryOverrider::IEImporterTestRegistryOverrider()
+ImporterTestRegistryOverrider::ImporterTestRegistryOverrider()
: temporary_key_(kTestHKCUOverrideKeyPrefix +
base::UTF8ToUTF16(base::GenerateGUID())) {
DCHECK(!GetTestKeyFromEnvironment(NULL));
@@ -50,7 +50,7 @@ IEImporterTestRegistryOverrider::IEImporterTestRegistryOverrider()
DCHECK(success);
}
-IEImporterTestRegistryOverrider::~IEImporterTestRegistryOverrider() {
+ImporterTestRegistryOverrider::~ImporterTestRegistryOverrider() {
base::win::RegKey reg_key(HKEY_CURRENT_USER, temporary_key_.c_str(),
KEY_ALL_ACCESS);
DCHECK(reg_key.Valid());
@@ -62,7 +62,7 @@ IEImporterTestRegistryOverrider::~IEImporterTestRegistryOverrider() {
}
// static
-base::string16 IEImporterTestRegistryOverrider::GetTestRegistryOverride() {
+base::string16 ImporterTestRegistryOverrider::GetTestRegistryOverride() {
base::string16 key;
if (!GetTestKeyFromEnvironment(&key))
return base::string16();
diff --git a/chrome/common/importer/ie_importer_test_registry_overrider_win.h b/chrome/common/importer/importer_test_registry_overrider_win.h
index de86e7e..4f493fc 100644
--- a/chrome/common/importer/ie_importer_test_registry_overrider_win.h
+++ b/chrome/common/importer/importer_test_registry_overrider_win.h
@@ -2,24 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CHROME_COMMON_IMPORTER_IE_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
-#define CHROME_COMMON_IMPORTER_IE_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
+#ifndef CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
+#define CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
#include "base/basictypes.h"
#include "base/strings/string16.h"
// A helper class to let tests generate a random registry key to be used in
// HKEY_CURRENT_USER in tests. After the key has been generated by constructing
-// an IEImporterTestRegistryOverrider, consumers in this process (or in any
+// an ImporterTestRegistryOverrider, consumers in this process (or in any
// child processes created after the key has been generated) can obtain the key
-// via GetTestRegistryOverride(). IEImporterTestRegistryOverrider will delete
+// via GetTestRegistryOverride(). ImporterTestRegistryOverrider will delete
// the temporary key upon being deleted itself. Only one
-// IEImporterTestRegistryOverrider should live at once in a given process
+// ImporterTestRegistryOverrider should live at once in a given process
// hiearchy.
-class IEImporterTestRegistryOverrider {
+class ImporterTestRegistryOverrider {
public:
- IEImporterTestRegistryOverrider();
- ~IEImporterTestRegistryOverrider();
+ ImporterTestRegistryOverrider();
+ ~ImporterTestRegistryOverrider();
// Returns a test key if one was chosen and set by a call to
// SetTestRegistryOverride(); returns the empty string if none.
@@ -28,7 +28,7 @@ class IEImporterTestRegistryOverrider {
private:
base::string16 temporary_key_;
- DISALLOW_COPY_AND_ASSIGN(IEImporterTestRegistryOverrider);
+ DISALLOW_COPY_AND_ASSIGN(ImporterTestRegistryOverrider);
};
-#endif // CHROME_COMMON_IMPORTER_IE_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
+#endif // CHROME_COMMON_IMPORTER_IMPORTER_TEST_REGISTRY_OVERRIDER_WIN_H_
diff --git a/chrome/common/importer/importer_type.h b/chrome/common/importer/importer_type.h
index 5837cb3..c172f8a 100644
--- a/chrome/common/importer/importer_type.h
+++ b/chrome/common/importer/importer_type.h
@@ -24,7 +24,10 @@ enum ImporterType {
TYPE_SAFARI = 3,
#endif
// Value 4 was the (now deleted) Google Toolbar importer.
- TYPE_BOOKMARKS_FILE = 5 // Identifies a 'bookmarks.html' file.
+ TYPE_BOOKMARKS_FILE = 5, // Identifies a 'bookmarks.html' file.
+#if defined(OS_WIN)
+ TYPE_EDGE = 6,
+#endif
};
} // namespace importer
diff --git a/chrome/common/importer/profile_import_process_param_traits_macros.h b/chrome/common/importer/profile_import_process_param_traits_macros.h
index 62efb3d..6140d57 100644
--- a/chrome/common/importer/profile_import_process_param_traits_macros.h
+++ b/chrome/common/importer/profile_import_process_param_traits_macros.h
@@ -25,9 +25,15 @@
#include "content/public/common/common_param_traits.h"
#include "ipc/ipc_message_macros.h"
+#if defined(OS_WIN)
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(importer::ImporterType,
+ importer::TYPE_UNKNOWN,
+ importer::TYPE_EDGE)
+#else
IPC_ENUM_TRAITS_MIN_MAX_VALUE(importer::ImporterType,
importer::TYPE_UNKNOWN,
importer::TYPE_BOOKMARKS_FILE)
+#endif
IPC_STRUCT_TRAITS_BEGIN(importer::SourceProfile)
IPC_STRUCT_TRAITS_MEMBER(importer_name)
diff --git a/chrome/test/data/edge_database_reader/random.edb.gz b/chrome/test/data/edge_database_reader/random.edb.gz
new file mode 100644
index 0000000..e1736b8
--- /dev/null
+++ b/chrome/test/data/edge_database_reader/random.edb.gz
Binary files differ
diff --git a/chrome/test/data/edge_database_reader/testdata.edb.gz b/chrome/test/data/edge_database_reader/testdata.edb.gz
new file mode 100644
index 0000000..42033c5
--- /dev/null
+++ b/chrome/test/data/edge_database_reader/testdata.edb.gz
Binary files differ
diff --git a/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/DBStore/spartan.edb.gz b/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/DBStore/spartan.edb.gz
new file mode 100644
index 0000000..5f997be
--- /dev/null
+++ b/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/DBStore/spartan.edb.gz
Binary files differ
diff --git a/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/Favorites/dummy.ico b/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/Favorites/dummy.ico
new file mode 100644
index 0000000..818c71d
--- /dev/null
+++ b/chrome/test/data/edge_profile/DataStore/Data/nouser1/120712-0049/Favorites/dummy.ico
Binary files differ
diff --git a/chrome/test/data/edge_profile/Favorites/Google.url b/chrome/test/data/edge_profile/Favorites/Google.url
new file mode 100644
index 0000000..3cf70ba
--- /dev/null
+++ b/chrome/test/data/edge_profile/Favorites/Google.url
@@ -0,0 +1,10 @@
+[DEFAULT]
+BASEURL=http://www.google.com/
+[{000214A0-0000-0000-C000-000000000046}]
+Prop3=19,2
+[InternetShortcut]
+IDList=
+URL=http://www.google.com/
+IconFile=http://www.google.com/favicon.ico
+IconIndex=1
+HotKey=0
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
index ace1569..62a69d1 100644
--- a/chrome/utility/BUILD.gn
+++ b/chrome/utility/BUILD.gn
@@ -66,6 +66,10 @@ static_library("utility") {
# Prevent wininet from loading in the renderer. http://crbug.com/460679
if (is_win) {
ldflags = [ "/DELAYLOAD:wininet.dll" ]
+
+ # Add ESE library for Edge Import support.
+ libs = [ "esent.lib" ]
+ ldflags += [ "/DELAYLOAD:esent.dll" ]
}
if (is_win || is_mac) {
diff --git a/chrome/utility/importer/DEPS b/chrome/utility/importer/DEPS
index e25c5f2..100c7d6 100644
--- a/chrome/utility/importer/DEPS
+++ b/chrome/utility/importer/DEPS
@@ -1,5 +1,6 @@
include_rules = [
"+components/autofill/core/common", # For PasswordForm.
+ "+components/compression", # For edge database reader unittests.
"+components/favicon_base",
"+components/search_engines",
"+components/strings/grit",
diff --git a/chrome/utility/importer/edge_database_reader_unittest_win.cc b/chrome/utility/importer/edge_database_reader_unittest_win.cc
new file mode 100644
index 0000000..d0a0b77
--- /dev/null
+++ b/chrome/utility/importer/edge_database_reader_unittest_win.cc
@@ -0,0 +1,448 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/utility/importer/edge_database_reader_win.h"
+
+#include <windows.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/compression/compression_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class EdgeDatabaseReaderTest : public ::testing::Test {
+ protected:
+ bool CopyTestDatabase(const base::string16& database_name,
+ base::FilePath* copied_path) {
+ base::FilePath input_path;
+ input_path = test_data_path_.AppendASCII("edge_database_reader")
+ .Append(database_name)
+ .AddExtension(L".gz");
+ base::FilePath output_path = temp_dir_.path().Append(database_name);
+
+ if (DecompressDatabase(input_path, output_path)) {
+ *copied_path = output_path;
+ return true;
+ }
+ return false;
+ }
+
+ bool WriteFile(const base::string16& name,
+ const std::string& contents,
+ base::FilePath* output_path) {
+ *output_path = temp_dir_.path().Append(name);
+ return base::WriteFile(*output_path, contents.c_str(), contents.size()) >=
+ 0;
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_path_));
+ }
+
+ private:
+ bool DecompressDatabase(const base::FilePath& gzip_file,
+ const base::FilePath& output_file) {
+ std::string gzip_data;
+ if (!base::ReadFileToString(gzip_file, &gzip_data))
+ return false;
+ if (!compression::GzipUncompress(gzip_data, &gzip_data))
+ return false;
+ return base::WriteFile(output_file, gzip_data.c_str(), gzip_data.size()) >=
+ 0;
+ }
+
+ base::ScopedTempDir temp_dir_;
+ base::FilePath test_data_path_;
+};
+
+} // namespace
+
+TEST_F(EdgeDatabaseReaderTest, OpenFileTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+}
+
+TEST_F(EdgeDatabaseReaderTest, NoFileTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ EdgeDatabaseReader reader;
+ EXPECT_FALSE(reader.OpenDatabase(L"ThisIsntARealFileName.edb"));
+}
+
+TEST_F(EdgeDatabaseReaderTest, RandomGarbageDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"random.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_FALSE(reader.OpenDatabase(database_path.value()));
+}
+
+TEST_F(EdgeDatabaseReaderTest, ZerosDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ std::string zeros(0x10000, '\0');
+ ASSERT_TRUE(WriteFile(L"zeros.edb", zeros, &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_FALSE(reader.OpenDatabase(database_path.value()));
+}
+
+TEST_F(EdgeDatabaseReaderTest, EmptyDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(WriteFile(L"empty.edb", "", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_FALSE(reader.OpenDatabase(database_path.value()));
+}
+
+TEST_F(EdgeDatabaseReaderTest, OpenTableDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_NE(table_enum, nullptr);
+}
+
+TEST_F(EdgeDatabaseReaderTest, InvalidTableDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"NotARealTableName");
+ EXPECT_EQ(table_enum, nullptr);
+}
+
+TEST_F(EdgeDatabaseReaderTest, NotOpenDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ EdgeDatabaseReader reader;
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_EQ(table_enum, nullptr);
+ EXPECT_EQ(reader.last_error(), JET_errDatabaseNotFound);
+}
+
+TEST_F(EdgeDatabaseReaderTest, AlreadyOpenDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ EXPECT_FALSE(reader.OpenDatabase(database_path.value()));
+ EXPECT_EQ(reader.last_error(), JET_errOneDatabasePerSession);
+}
+
+TEST_F(EdgeDatabaseReaderTest, OpenTableAndReadDataDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_NE(table_enum, nullptr);
+ int row_count = 0;
+ do {
+ int32_t int_col_value = 0;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"IntCol", &int_col_value));
+ EXPECT_EQ(int_col_value, -row_count);
+
+ uint32_t uint_col_value = 0;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"UIntCol", &uint_col_value));
+ EXPECT_EQ(uint_col_value, row_count);
+
+ int64_t longlong_col_value = 0;
+ EXPECT_TRUE(
+ table_enum->RetrieveColumn(L"LongLongCol", &longlong_col_value));
+ EXPECT_EQ(longlong_col_value, row_count);
+
+ GUID guid_col_value = {};
+ GUID expected_guid_col_value = {};
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"GuidCol", &guid_col_value));
+ memset(&expected_guid_col_value, row_count,
+ sizeof(expected_guid_col_value));
+ EXPECT_EQ(guid_col_value, expected_guid_col_value);
+
+ FILETIME filetime_col_value = {};
+ FILETIME expected_filetime_col_value = {};
+ SYSTEMTIME system_time = {};
+ // Expected time value is row_count+1/January/1970.
+ system_time.wYear = 1970;
+ system_time.wMonth = 1;
+ system_time.wDay = row_count + 1;
+ EXPECT_TRUE(
+ SystemTimeToFileTime(&system_time, &expected_filetime_col_value));
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"DateCol", &filetime_col_value));
+ EXPECT_EQ(filetime_col_value.dwLowDateTime,
+ expected_filetime_col_value.dwLowDateTime);
+ EXPECT_EQ(filetime_col_value.dwHighDateTime,
+ expected_filetime_col_value.dwHighDateTime);
+
+ std::wstring row_string = base::StringPrintf(L"String: %d", row_count);
+ base::string16 str_col_value;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"StrCol", &str_col_value));
+ EXPECT_EQ(str_col_value, row_string);
+
+ bool bool_col_value;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"BoolCol", &bool_col_value));
+ EXPECT_EQ(bool_col_value, (row_count % 2) == 0);
+
+ row_count++;
+ } while (table_enum->Next());
+ EXPECT_EQ(row_count, 16);
+}
+
+TEST_F(EdgeDatabaseReaderTest, CheckEnumResetDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_NE(table_enum, nullptr);
+ int row_count = 0;
+ do {
+ row_count++;
+ } while (table_enum->Next());
+ EXPECT_NE(row_count, 0);
+ EXPECT_TRUE(table_enum->Reset());
+ do {
+ row_count--;
+ } while (table_enum->Next());
+ EXPECT_EQ(row_count, 0);
+}
+
+TEST_F(EdgeDatabaseReaderTest, InvalidColumnDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_NE(table_enum, nullptr);
+ int32_t int_col_value = 0;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"NotARealNameCol", &int_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errColumnNotFound);
+}
+
+TEST_F(EdgeDatabaseReaderTest, NoColumnDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"NoColsTable");
+ EXPECT_NE(table_enum, nullptr);
+ int32_t int_col_value = 0;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"IntCol", &int_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errColumnNotFound);
+}
+
+TEST_F(EdgeDatabaseReaderTest, EmptyTableDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"EmptyTable");
+ EXPECT_NE(table_enum, nullptr);
+ int32_t int_col_value = 0;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"IntCol", &int_col_value));
+ EXPECT_NE(table_enum->last_error(), JET_errColumnNotFound);
+ EXPECT_FALSE(table_enum->Reset());
+ EXPECT_FALSE(table_enum->Next());
+}
+
+TEST_F(EdgeDatabaseReaderTest, UnicodeStringsDatabaseTest) {
+ const char* utf8_strings[] = {
+ "\xE3\x81\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF",
+ "\xE4\xBD\xA0\xE5\xA5\xBD",
+ "\xD0\x97\xD0\xB4\xD1\x80\xD0\xB0\xD0\xB2\xD1\x81\xD1\x82\xD0\xB2\xD1\x83"
+ "\xD0\xB9\xD1\x82\xD0\xB5",
+ "\x48\x65\x6C\x6C\x6F",
+ "\xEC\x95\x88\xEB\x85\x95\xED\x95\x98\xEC\x84\xB8\xEC\x9A\x94",
+ };
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"UnicodeTable");
+ EXPECT_NE(table_enum, nullptr);
+ size_t utf8_strings_count = arraysize(utf8_strings);
+ for (size_t row_count = 0; row_count < utf8_strings_count; ++row_count) {
+ std::wstring row_string = base::UTF8ToWide(utf8_strings[row_count]);
+ base::string16 str_col_value;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"StrCol", &str_col_value));
+ EXPECT_EQ(str_col_value, row_string);
+ if (row_count < utf8_strings_count - 1)
+ EXPECT_TRUE(table_enum->Next());
+ else
+ EXPECT_FALSE(table_enum->Next());
+ }
+}
+
+TEST_F(EdgeDatabaseReaderTest, NonUnicodeStringsDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"NonUnicodeTable");
+ EXPECT_NE(table_enum, nullptr);
+ base::string16 str_col_value;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"StrCol", &str_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+}
+
+TEST_F(EdgeDatabaseReaderTest, CheckNullColumnDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"NullTable");
+ EXPECT_NE(table_enum, nullptr);
+
+ // We expect to successfully open a column value but get the default value
+ // back.
+ int32_t int_col_value = 1;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"IntCol", &int_col_value));
+ EXPECT_EQ(int_col_value, 0);
+
+ uint32_t uint_col_value = 1;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"UIntCol", &uint_col_value));
+ EXPECT_EQ(uint_col_value, 0);
+
+ int64_t longlong_col_value = 1;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"LongLongCol", &longlong_col_value));
+ EXPECT_EQ(longlong_col_value, 0);
+
+ GUID guid_col_value = {};
+ GUID expected_guid_col_value = {};
+ memset(&guid_col_value, 0x1, sizeof(guid_col_value));
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"GuidCol", &guid_col_value));
+ memset(&expected_guid_col_value, 0, sizeof(expected_guid_col_value));
+ EXPECT_EQ(guid_col_value, expected_guid_col_value);
+
+ FILETIME filetime_col_value = {1, 1};
+ FILETIME expected_filetime_col_value = {};
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"DateCol", &filetime_col_value));
+ EXPECT_EQ(filetime_col_value.dwLowDateTime,
+ expected_filetime_col_value.dwLowDateTime);
+ EXPECT_EQ(filetime_col_value.dwHighDateTime,
+ expected_filetime_col_value.dwHighDateTime);
+
+ base::string16 str_col_value;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"StrCol", &str_col_value));
+ EXPECT_TRUE(str_col_value.empty());
+
+ bool bool_col_value;
+ EXPECT_TRUE(table_enum->RetrieveColumn(L"BoolCol", &bool_col_value));
+ EXPECT_EQ(bool_col_value, false);
+}
+
+TEST_F(EdgeDatabaseReaderTest, CheckInvalidColumnTypeDatabaseTest) {
+ // Only verified to work with ESE library on Windows 7 and above.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+
+ base::FilePath database_path;
+ ASSERT_TRUE(CopyTestDatabase(L"testdata.edb", &database_path));
+ EdgeDatabaseReader reader;
+ EXPECT_TRUE(reader.OpenDatabase(database_path.value()));
+ scoped_ptr<EdgeDatabaseTableEnumerator> table_enum =
+ reader.OpenTableEnumerator(L"TestTable");
+ EXPECT_NE(table_enum, nullptr);
+
+ uint32_t uint_col_value = 0;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"IntCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ // Check unsigned int with a signed int.
+ int32_t int_col_value = 0;
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"UIntCol", &int_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"LongLongCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"GuidCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"DateCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"StrCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+ EXPECT_FALSE(table_enum->RetrieveColumn(L"BoolCol", &uint_col_value));
+ EXPECT_EQ(table_enum->last_error(), JET_errInvalidColumnType);
+}
diff --git a/chrome/utility/importer/edge_database_reader_win.cc b/chrome/utility/importer/edge_database_reader_win.cc
new file mode 100644
index 0000000..58f4b20
--- /dev/null
+++ b/chrome/utility/importer/edge_database_reader_win.cc
@@ -0,0 +1,257 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/utility/importer/edge_database_reader_win.h"
+
+#include <windows.h>
+
+#include <vector>
+
+namespace {
+
+// This is an arbitary size chosen for the database error message buffer.
+const size_t kErrorMessageSize = 1024;
+// This is the page size of the Edge data. It's unlikely to change.
+const JET_API_PTR kEdgeDatabasePageSize = 8192;
+// This is the code page value for a Unicode (UCS-2) column.
+const unsigned short kJetUnicodeCodePage = 1200;
+
+template <typename T>
+bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type,
+ const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ T* value) {
+ if ((column_type == match_column_type) && (column_data.size() == sizeof(T))) {
+ memcpy(value, &column_data[0], sizeof(T));
+ return true;
+ }
+ return false;
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ bool* value) {
+ if ((column_type == JET_coltypBit) && (column_data.size() == 1)) {
+ *value = (column_data[0] & 1) == 1;
+ return true;
+ }
+ return false;
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ base::string16* value) {
+ if ((column_type == JET_coltypLongText) &&
+ ((column_data.size() % sizeof(base::char16)) == 0)) {
+ base::string16& value_ref = *value;
+ size_t char_length = column_data.size() / sizeof(base::char16);
+ value_ref.resize(char_length);
+ memcpy(&value_ref[0], &column_data[0], column_data.size());
+ // Remove any trailing NUL characters.
+ while (char_length > 0) {
+ if (value_ref[char_length - 1])
+ break;
+ char_length--;
+ }
+ value_ref.resize(char_length);
+ return true;
+ }
+ return false;
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ GUID* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypGUID, column_type,
+ column_data, value);
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ int32_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLong, column_type,
+ column_data, value);
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ int64_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
+ column_data, value);
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ FILETIME* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
+ column_data, value);
+}
+
+bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ uint32_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type,
+ column_data, value);
+}
+
+} // namespace
+
+base::string16 EdgeErrorObject::GetErrorMessage() const {
+ WCHAR error_message[kErrorMessageSize] = {};
+ JET_API_PTR err = last_error_;
+ JET_ERR result = JetGetSystemParameter(JET_instanceNil, JET_sesidNil,
+ JET_paramErrorToString, &err,
+ error_message, sizeof(error_message));
+ if (result != JET_errSuccess)
+ return L"";
+
+ return error_message;
+}
+
+bool EdgeErrorObject::SetLastError(JET_ERR error) {
+ last_error_ = error;
+ return error == JET_errSuccess;
+}
+
+EdgeDatabaseTableEnumerator::EdgeDatabaseTableEnumerator(
+ const base::string16& table_name,
+ JET_SESID session_id,
+ JET_TABLEID table_id)
+ : table_id_(table_id), table_name_(table_name), session_id_(session_id) {}
+
+EdgeDatabaseTableEnumerator::~EdgeDatabaseTableEnumerator() {
+ if (table_id_ != JET_tableidNil)
+ JetCloseTable(session_id_, table_id_);
+}
+
+bool EdgeDatabaseTableEnumerator::Reset() {
+ return SetLastError(JetMove(session_id_, table_id_, JET_MoveFirst, 0));
+}
+
+bool EdgeDatabaseTableEnumerator::Next() {
+ return SetLastError(JetMove(session_id_, table_id_, JET_MoveNext, 0));
+}
+
+template <typename T>
+bool EdgeDatabaseTableEnumerator::RetrieveColumn(
+ const base::string16& column_name,
+ T* value) {
+ const JET_COLUMNBASE& column_base = GetColumnByName(column_name);
+ if (column_base.cbMax == 0) {
+ SetLastError(JET_errColumnNotFound);
+ return false;
+ }
+ if (column_base.coltyp == JET_coltypLongText &&
+ column_base.cp != kJetUnicodeCodePage) {
+ SetLastError(JET_errInvalidColumnType);
+ return false;
+ }
+ std::vector<uint8_t> column_data(column_base.cbMax);
+ unsigned long actual_size = 0;
+ JET_ERR err = JetRetrieveColumn(session_id_, table_id_, column_base.columnid,
+ &column_data[0], column_data.size(),
+ &actual_size, 0, nullptr);
+ SetLastError(err);
+ if (err != JET_errSuccess && err != JET_wrnColumnNull) {
+ return false;
+ }
+
+ if (err == JET_errSuccess) {
+ column_data.resize(actual_size);
+ if (!ValidateAndConvertValue(column_base.coltyp, column_data, value)) {
+ SetLastError(JET_errInvalidColumnType);
+ return false;
+ }
+ } else {
+ *value = T();
+ }
+
+ return true;
+}
+
+// Explicitly instantiate implementations of RetrieveColumn for various types.
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ bool*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ FILETIME*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ GUID*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ int32_t*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ int64_t*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ base::string16*);
+template bool EdgeDatabaseTableEnumerator::RetrieveColumn(const base::string16&,
+ uint32_t*);
+
+const JET_COLUMNBASE& EdgeDatabaseTableEnumerator::GetColumnByName(
+ const base::string16& column_name) {
+ auto found_col = columns_by_name_.find(column_name);
+ if (found_col == columns_by_name_.end()) {
+ JET_COLUMNBASE column_base = {};
+ column_base.cbStruct = sizeof(JET_COLUMNBASE);
+ if (!SetLastError(JetGetTableColumnInfo(
+ session_id_, table_id_, column_name.c_str(), &column_base,
+ sizeof(column_base), JET_ColInfoBase))) {
+ // 0 indicates an invalid column.
+ column_base.cbMax = 0;
+ }
+ columns_by_name_[column_name] = column_base;
+ found_col = columns_by_name_.find(column_name);
+ }
+ return found_col->second;
+}
+
+EdgeDatabaseReader::~EdgeDatabaseReader() {
+ // We don't need to collect other ID handles, terminating instance
+ // is enough to shut the entire session down.
+ if (instance_id_ != JET_instanceNil)
+ JetTerm(instance_id_);
+}
+
+bool EdgeDatabaseReader::OpenDatabase(const base::string16& database_file) {
+ if (IsOpen()) {
+ SetLastError(JET_errOneDatabasePerSession);
+ return false;
+ }
+ if (!SetLastError(JetSetSystemParameter(nullptr, JET_sesidNil,
+ JET_paramDatabasePageSize,
+ kEdgeDatabasePageSize, nullptr)))
+ return false;
+ if (!SetLastError(JetCreateInstance(&instance_id_, L"EdgeDataImporter")))
+ return false;
+ if (!SetLastError(JetSetSystemParameter(&instance_id_, JET_sesidNil,
+ JET_paramRecovery, 0, L"Off")))
+ return false;
+ if (!SetLastError(JetInit(&instance_id_)))
+ return false;
+ if (!SetLastError(
+ JetBeginSession(instance_id_, &session_id_, nullptr, nullptr)))
+ return false;
+ if (!SetLastError(JetAttachDatabase2(session_id_, database_file.c_str(), 0,
+ JET_bitDbReadOnly)))
+ return false;
+ if (!SetLastError(JetOpenDatabase(session_id_, database_file.c_str(), nullptr,
+ &db_id_, JET_bitDbReadOnly)))
+ return false;
+ return true;
+}
+
+scoped_ptr<EdgeDatabaseTableEnumerator> EdgeDatabaseReader::OpenTableEnumerator(
+ const base::string16& table_name) {
+ JET_TABLEID table_id;
+
+ if (!IsOpen()) {
+ SetLastError(JET_errDatabaseNotFound);
+ return nullptr;
+ }
+
+ if (!SetLastError(JetOpenTable(session_id_, db_id_, table_name.c_str(),
+ nullptr, 0, JET_bitTableReadOnly, &table_id)))
+ return nullptr;
+
+ return make_scoped_ptr(
+ new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id));
+}
diff --git a/chrome/utility/importer/edge_database_reader_win.h b/chrome/utility/importer/edge_database_reader_win.h
new file mode 100644
index 0000000..1b87801
--- /dev/null
+++ b/chrome/utility/importer/edge_database_reader_win.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UTILITY_IMPORTER_EDGE_DATABASE_READER_WIN_H_
+#define CHROME_UTILITY_IMPORTER_EDGE_DATABASE_READER_WIN_H_
+
+#define JET_UNICODE
+#include <esent.h>
+#undef JET_UNICODE
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+class EdgeErrorObject {
+ public:
+ EdgeErrorObject() : last_error_(JET_errSuccess) {}
+
+ // Get the last error converted to a descriptive string.
+ base::string16 GetErrorMessage() const;
+ // Get the last error value.
+ JET_ERR last_error() const { return last_error_; }
+
+ protected:
+ // This function returns true if the passed error parameter is equal
+ // to JET_errSuccess
+ bool SetLastError(JET_ERR error);
+
+ private:
+ JET_ERR last_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject);
+};
+
+class EdgeDatabaseTableEnumerator : public EdgeErrorObject {
+ public:
+ EdgeDatabaseTableEnumerator(const base::string16& table_name,
+ JET_SESID session_id,
+ JET_TABLEID table_id);
+
+ ~EdgeDatabaseTableEnumerator();
+
+ const base::string16& table_name() { return table_name_; }
+
+ // Reset the enumerator to the start of the table. Returns true if successful.
+ bool Reset();
+ // Move to the next row in the table. Returns false on error or no more rows.
+ bool Next();
+
+ // Retrieve a column's data value. If a NULL is encountered in the column the
+ // default value for the template type is placed in |value|.
+ template <typename T>
+ bool RetrieveColumn(const base::string16& column_name, T* value);
+
+ private:
+ const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name);
+
+ std::map<const base::string16, JET_COLUMNBASE> columns_by_name_;
+ JET_TABLEID table_id_;
+ base::string16 table_name_;
+ JET_SESID session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator);
+};
+
+class EdgeDatabaseReader : public EdgeErrorObject {
+ public:
+ EdgeDatabaseReader()
+ : db_id_(JET_dbidNil),
+ instance_id_(JET_instanceNil),
+ session_id_(JET_sesidNil) {}
+
+ ~EdgeDatabaseReader();
+
+ // Open the database from a file path. Returns true on success.
+ bool OpenDatabase(const base::string16& database_file);
+
+ // Open a row enumerator for a specified table. Returns a nullptr on error.
+ scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator(
+ const base::string16& table_name);
+
+ private:
+ bool IsOpen() { return instance_id_ != JET_instanceNil; }
+
+ JET_DBID db_id_;
+ JET_INSTANCE instance_id_;
+ JET_SESID session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseReader);
+};
+
+#endif // CHROME_UTILITY_IMPORTER_EDGE_DATABASE_READER_WIN_H_
diff --git a/chrome/utility/importer/edge_importer_win.cc b/chrome/utility/importer/edge_importer_win.cc
new file mode 100644
index 0000000..b84f06b
--- /dev/null
+++ b/chrome/utility/importer/edge_importer_win.cc
@@ -0,0 +1,288 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/utility/importer/edge_importer_win.h"
+
+#include <Shlobj.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/importer/edge_importer_utils_win.h"
+#include "chrome/common/importer/imported_bookmark_entry.h"
+#include "chrome/common/importer/importer_bridge.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/utility/importer/edge_database_reader_win.h"
+#include "chrome/utility/importer/favicon_reencode.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Toolbar favorites are placed under this special folder name.
+const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_";
+const base::char16 kSpartanDatabaseFile[] = L"spartan.edb";
+
+struct EdgeFavoriteEntry {
+ EdgeFavoriteEntry()
+ : is_folder(false),
+ order_number(0),
+ item_id(GUID_NULL),
+ parent_id(GUID_NULL) {}
+
+ base::string16 title;
+ GURL url;
+ base::FilePath favicon_file;
+ bool is_folder;
+ int64_t order_number;
+ base::Time date_updated;
+ GUID item_id;
+ GUID parent_id;
+
+ std::vector<const EdgeFavoriteEntry*> children;
+
+ ImportedBookmarkEntry ToBookmarkEntry(
+ bool in_toolbar,
+ const std::vector<base::string16>& path) const {
+ ImportedBookmarkEntry entry;
+ entry.in_toolbar = in_toolbar;
+ entry.is_folder = is_folder;
+ entry.url = url;
+ entry.path = path;
+ entry.title = title;
+ entry.creation_time = date_updated;
+ return entry;
+ }
+};
+
+struct EdgeFavoriteEntryComparator {
+ bool operator()(const EdgeFavoriteEntry* lhs,
+ const EdgeFavoriteEntry* rhs) const {
+ return std::tie(lhs->order_number, lhs->title) <
+ std::tie(rhs->order_number, rhs->title);
+ }
+};
+
+// The name of the database file is spartan.edb, however it isn't clear how
+// the intermediate path between the DataStore and the database is generated.
+// Therefore we just do a simple recursive search until we find a matching name.
+base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) {
+ base::FilePath data_path =
+ profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path;
+ if (data_path.empty())
+ return base::FilePath();
+
+ base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true,
+ base::FileEnumerator::FILES);
+ base::FilePath path = enumerator.Next();
+ while (!path.empty()) {
+ if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(),
+ kSpartanDatabaseFile))
+ return path;
+ path = enumerator.Next();
+ }
+ return base::FilePath();
+}
+
+struct GuidComparator {
+ bool operator()(const GUID& a, const GUID& b) const {
+ return memcmp(&a, &b, sizeof(a)) < 0;
+ }
+};
+
+bool ReadFaviconData(const base::FilePath& file,
+ std::vector<unsigned char>* data) {
+ std::string image_data;
+ if (!base::ReadFileToString(file, &image_data))
+ return false;
+
+ const unsigned char* ptr =
+ reinterpret_cast<const unsigned char*>(image_data.c_str());
+ return importer::ReencodeFavicon(ptr, image_data.size(), data);
+}
+
+void BuildBookmarkEntries(const EdgeFavoriteEntry& current_entry,
+ bool is_toolbar,
+ std::vector<ImportedBookmarkEntry>* bookmarks,
+ favicon_base::FaviconUsageDataList* favicons,
+ std::vector<base::string16>* path) {
+ for (const EdgeFavoriteEntry* entry : current_entry.children) {
+ if (entry->is_folder) {
+ // If the favorites bar then load all children as toolbar items.
+ if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) {
+ // Replace name with Links similar to IE.
+ path->push_back(L"Links");
+ BuildBookmarkEntries(*entry, true, bookmarks, favicons, path);
+ path->pop_back();
+ } else {
+ path->push_back(entry->title);
+ BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, path);
+ path->pop_back();
+ }
+ } else {
+ bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path));
+ favicon_base::FaviconUsageData favicon;
+ if (entry->url.is_valid() && !entry->favicon_file.empty() &&
+ ReadFaviconData(entry->favicon_file, &favicon.png_data)) {
+ // As the database doesn't provide us a favicon URL we'll fake one.
+ GURL::Replacements path_replace;
+ path_replace.SetPathStr("/favicon.ico");
+ favicon.favicon_url =
+ entry->url.GetWithEmptyPath().ReplaceComponents(path_replace);
+ favicon.urls.insert(entry->url);
+ favicons->push_back(favicon);
+ }
+ }
+ }
+}
+
+} // namespace
+
+EdgeImporter::EdgeImporter() {}
+
+void EdgeImporter::StartImport(const importer::SourceProfile& source_profile,
+ uint16 items,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ bridge_->NotifyStarted();
+ source_path_ = source_profile.source_path;
+
+ if ((items & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportFavorites();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ bridge_->NotifyEnded();
+}
+
+EdgeImporter::~EdgeImporter() {}
+
+void EdgeImporter::ImportFavorites() {
+ std::vector<ImportedBookmarkEntry> bookmarks;
+ favicon_base::FaviconUsageDataList favicons;
+ ParseFavoritesDatabase(&bookmarks, &favicons);
+
+ if (!bookmarks.empty() && !cancelled()) {
+ const base::string16& first_folder_name =
+ l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE);
+ bridge_->AddBookmarks(bookmarks, first_folder_name);
+ }
+ if (!favicons.empty() && !cancelled())
+ bridge_->SetFavicons(favicons);
+}
+
+// From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET
+// database within the Edge local storage. The import uses the ESE library to
+// open and read the data file. The data is stored in a Favorites table with
+// the following schema.
+// Column Name Column Type
+// ------------------------------------------
+// DateUpdated LongLong - FILETIME
+// FaviconFile LongText - Relative path
+// HashedUrl ULong
+// IsDeleted Bit
+// IsFolder Bit
+// ItemId Guid
+// OrderNumber LongLong
+// ParentId Guid
+// RoamDisabled Bit
+// RowId Long
+// Title LongText
+// URL LongText
+void EdgeImporter::ParseFavoritesDatabase(
+ std::vector<ImportedBookmarkEntry>* bookmarks,
+ favicon_base::FaviconUsageDataList* favicons) {
+ base::FilePath database_path = FindSpartanDatabase(source_path_);
+ if (database_path.empty())
+ return;
+
+ EdgeDatabaseReader database;
+ if (!database.OpenDatabase(database_path.value())) {
+ DVLOG(1) << "Error opening database " << database.GetErrorMessage();
+ return;
+ }
+
+ scoped_ptr<EdgeDatabaseTableEnumerator> enumerator =
+ database.OpenTableEnumerator(L"Favorites");
+ if (!enumerator) {
+ DVLOG(1) << "Error opening database table " << database.GetErrorMessage();
+ return;
+ }
+
+ if (!enumerator->Reset())
+ return;
+
+ std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries;
+ base::FilePath favicon_base =
+ source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_;
+ favicon_base = favicon_base.Append(L"DataStore");
+
+ do {
+ EdgeFavoriteEntry entry;
+ bool is_deleted = false;
+ if (!enumerator->RetrieveColumn(L"IsDeleted", &is_deleted))
+ continue;
+ if (is_deleted)
+ continue;
+ if (!enumerator->RetrieveColumn(L"IsFolder", &entry.is_folder))
+ continue;
+ base::string16 url;
+ if (!enumerator->RetrieveColumn(L"URL", &url))
+ continue;
+ entry.url = GURL(url);
+ if (!entry.is_folder && !entry.url.is_valid())
+ continue;
+ if (!enumerator->RetrieveColumn(L"Title", &entry.title))
+ continue;
+ base::string16 favicon_file;
+ if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file))
+ continue;
+ if (!favicon_file.empty())
+ entry.favicon_file = favicon_base.Append(favicon_file);
+ if (!enumerator->RetrieveColumn(L"ParentId", &entry.parent_id))
+ continue;
+ if (!enumerator->RetrieveColumn(L"ItemId", &entry.item_id))
+ continue;
+ if (!enumerator->RetrieveColumn(L"OrderNumber", &entry.order_number))
+ continue;
+ FILETIME data_updated;
+ if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated))
+ continue;
+ entry.date_updated = base::Time::FromFileTime(data_updated);
+ database_entries[entry.item_id] = entry;
+ } while (enumerator->Next() && !cancelled());
+
+ // Build simple tree.
+ EdgeFavoriteEntry root_entry;
+ for (auto& entry : database_entries) {
+ auto found_parent = database_entries.find(entry.second.parent_id);
+ if (found_parent == database_entries.end() ||
+ !found_parent->second.is_folder) {
+ root_entry.children.push_back(&entry.second);
+ } else {
+ found_parent->second.children.push_back(&entry.second);
+ }
+ }
+ // With tree built sort the children of each node including the root.
+ std::sort(root_entry.children.begin(), root_entry.children.end(),
+ EdgeFavoriteEntryComparator());
+ for (auto& entry : database_entries) {
+ std::sort(entry.second.children.begin(), entry.second.children.end(),
+ EdgeFavoriteEntryComparator());
+ }
+ std::vector<base::string16> path;
+ BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path);
+}
diff --git a/chrome/utility/importer/edge_importer_win.h b/chrome/utility/importer/edge_importer_win.h
new file mode 100644
index 0000000..79e1f7a
--- /dev/null
+++ b/chrome/utility/importer/edge_importer_win.h
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UTILITY_IMPORTER_EDGE_IMPORTER_WIN_H_
+#define CHROME_UTILITY_IMPORTER_EDGE_IMPORTER_WIN_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "chrome/utility/importer/importer.h"
+#include "components/favicon_base/favicon_usage_data.h"
+
+struct ImportedBookmarkEntry;
+
+class EdgeImporter : public Importer {
+ public:
+ EdgeImporter();
+
+ // Importer:
+ void StartImport(const importer::SourceProfile& source_profile,
+ uint16 items,
+ ImporterBridge* bridge) override;
+
+ private:
+ ~EdgeImporter() override;
+
+ void ImportFavorites();
+ // This function will read the favorites from the spartan database storing
+ // the bookmark items in |bookmarks| and favicon information in |favicons|.
+ void ParseFavoritesDatabase(std::vector<ImportedBookmarkEntry>* bookmarks,
+ favicon_base::FaviconUsageDataList* favicons);
+
+ // Edge does not have source path. It's used in unit tests only for providing
+ // a fake source for the spartan database location.
+ base::FilePath source_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeImporter);
+};
+
+#endif // CHROME_UTILITY_IMPORTER_EDGE_IMPORTER_WIN_H_
diff --git a/chrome/utility/importer/ie_importer_win.cc b/chrome/utility/importer/ie_importer_win.cc
index bc017ac..f2fceb0 100644
--- a/chrome/utility/importer/ie_importer_win.cc
+++ b/chrome/utility/importer/ie_importer_win.cc
@@ -29,6 +29,7 @@
#include "base/win/scoped_handle.h"
#include "base/win/scoped_propvariant.h"
#include "base/win/windows_version.h"
+#include "chrome/common/importer/edge_importer_utils_win.h"
#include "chrome/common/importer/ie_importer_utils_win.h"
#include "chrome/common/importer/imported_bookmark_entry.h"
#include "chrome/common/importer/importer_bridge.h"
@@ -420,13 +421,20 @@ const GUID IEImporter::kUnittestGUID = {
{ 0xb8, 0x7, 0x3d, 0x46, 0xab, 0x15, 0x45, 0xdf }
};
-IEImporter::IEImporter() {
-}
+IEImporter::IEImporter() : edge_import_mode_(false) {}
void IEImporter::StartImport(const importer::SourceProfile& source_profile,
uint16 items,
ImporterBridge* bridge) {
+ edge_import_mode_ = source_profile.importer_type == importer::TYPE_EDGE;
bridge_ = bridge;
+
+ if (edge_import_mode_) {
+ // When using for Edge imports we only support Favorites.
+ DCHECK_EQ(items, importer::FAVORITES);
+ // As coming from untrusted source ensure items is correct.
+ items = importer::FAVORITES;
+ }
source_path_ = source_profile.source_path;
bridge_->NotifyStarted();
@@ -478,7 +486,10 @@ void IEImporter::ImportFavorites() {
if (!bookmarks.empty() && !cancelled()) {
const base::string16& first_folder_name =
- l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_IE);
+ edge_import_mode_
+ ? l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE)
+ : l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_IE);
+
bridge_->AddBookmarks(bookmarks, first_folder_name);
}
if (!favicons.empty() && !cancelled())
@@ -782,7 +793,7 @@ void IEImporter::ImportHomepage() {
bool IEImporter::GetFavoritesInfo(IEImporter::FavoritesInfo* info) {
if (!source_path_.empty()) {
- // Source path exists during testing.
+ // Source path exists during testing as well as when importing from Edge.
info->path = source_path_;
info->path = info->path.AppendASCII("Favorites");
info->links_folder = L"Links";
@@ -888,8 +899,10 @@ void IEImporter::ParseFavoritesFolder(
bookmarks->push_back(entry);
}
- // Reflect the menu order in IE.
- SortBookmarksInIEOrder(this, bookmarks);
+ if (!edge_import_mode_) {
+ // Reflect the menu order in IE.
+ SortBookmarksInIEOrder(this, bookmarks);
+ }
// Record favicon data.
for (FaviconMap::iterator iter = favicon_map.begin();
diff --git a/chrome/utility/importer/ie_importer_win.h b/chrome/utility/importer/ie_importer_win.h
index 1d38034..acf5527 100644
--- a/chrome/utility/importer/ie_importer_win.h
+++ b/chrome/utility/importer/ie_importer_win.h
@@ -76,8 +76,11 @@ class IEImporter : public Importer {
// Determines which version of IE is in use.
int CurrentIEVersion() const;
+ // Set to true when importing favorites from old Edge on Windows 10.
+ bool edge_import_mode_;
+
// IE does not have source path. It's used in unit tests only for providing a
- // fake source.
+ // fake source and it's used if importing old Edge favorites on Windows 10.
base::FilePath source_path_;
DISALLOW_COPY_AND_ASSIGN(IEImporter);
diff --git a/chrome/utility/importer/importer_creator.cc b/chrome/utility/importer/importer_creator.cc
index d1ebe1a..f260f8c 100644
--- a/chrome/utility/importer/importer_creator.cc
+++ b/chrome/utility/importer/importer_creator.cc
@@ -9,6 +9,8 @@
#include "chrome/utility/importer/firefox_importer.h"
#if defined(OS_WIN)
+#include "chrome/common/importer/edge_importer_utils_win.h"
+#include "chrome/utility/importer/edge_importer_win.h"
#include "chrome/utility/importer/ie_importer_win.h"
#endif
@@ -26,6 +28,11 @@ Importer* CreateImporterByType(ImporterType type) {
#if defined(OS_WIN)
case TYPE_IE:
return new IEImporter();
+ case TYPE_EDGE:
+ // If legacy mode we pass back an IE importer.
+ if (IsEdgeFavoritesLegacyMode())
+ return new IEImporter();
+ return new EdgeImporter();
#endif
case TYPE_BOOKMARKS_FILE:
return new BookmarksFileImporter();
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d8e09df..d3c6e00 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -65887,6 +65887,7 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
<int value="6" label="IMPORTER_METRICS_BOOKMARKS_FILE">
A bookmarks.html file
</int>
+ <int value="7" label="IMPORTER_METRICS_EDGE">Edge (Windows-only)</int>
</enum>
<enum name="IncidentType" type="int">