Bug 1356531 - Remove moz_places_faviconindex and old Places migrations. r=Paolo

MozReview-Commit-ID: CUCuW4iMSrk

--HG--
rename : toolkit/components/places/tests/migration/places_v6.sqlite => toolkit/components/places/tests/migration/places_outdated.sqlite
rename : toolkit/components/places/tests/migration/test_current_from_v6.js => toolkit/components/places/tests/migration/test_current_from_outdated.js
extra : rebase_source : 9cb04e915547b2a8d0196d03162f77d3aa0e0603
This commit is contained in:
Marco Bonardo 2017-10-20 12:48:37 +02:00
parent 56aee668af
commit 92a855b75a
33 changed files with 51 additions and 1004 deletions

View File

@ -974,9 +974,12 @@ Database::InitSchema(bool* aDatabaseMigrated)
if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
*aDatabaseMigrated = true;
if (currentSchemaVersion < 11) {
// These are versions older than Firefox 4 that are not supported
if (currentSchemaVersion < 30) {
// These are versions older than Firefox 45 that are not supported
// anymore. In this case it's safer to just replace the database.
// Note that Firefox 45 is the ESR release before the latest one (52),
// and Firefox 48 is a watershed release, so any version older than 48
// will first have to go through it.
return NS_ERROR_FILE_CORRUPTED;
}
@ -988,104 +991,7 @@ Database::InitSchema(bool* aDatabaseMigrated)
}
});
// Firefox 4 uses schema version 11.
// Firefox 8 uses schema version 12.
if (currentSchemaVersion < 13) {
rv = MigrateV13Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 15) {
rv = MigrateV15Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 17) {
rv = MigrateV17Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 12 uses schema version 17.
if (currentSchemaVersion < 18) {
rv = MigrateV18Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 19) {
rv = MigrateV19Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 13 uses schema version 19.
if (currentSchemaVersion < 20) {
rv = MigrateV20Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 21) {
rv = MigrateV21Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 14 uses schema version 21.
if (currentSchemaVersion < 22) {
rv = MigrateV22Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 22 uses schema version 22.
if (currentSchemaVersion < 23) {
rv = MigrateV23Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 24 uses schema version 23.
if (currentSchemaVersion < 24) {
rv = MigrateV24Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 34 uses schema version 24.
if (currentSchemaVersion < 25) {
rv = MigrateV25Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 36 uses schema version 25.
if (currentSchemaVersion < 26) {
rv = MigrateV26Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 37 uses schema version 26.
if (currentSchemaVersion < 27) {
rv = MigrateV27Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 28) {
rv = MigrateV28Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 39 uses schema version 28.
if (currentSchemaVersion < 30) {
rv = MigrateV30Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 41 uses schema version 30.
// Firefox 45 ESR uses schema version 30.
if (currentSchemaVersion < 31) {
rv = MigrateV31Up();
@ -1153,7 +1059,12 @@ Database::InitSchema(bool* aDatabaseMigrated)
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 58 uses schema version 40.
if (currentSchemaVersion < 41) {
rv = MigrateV41Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 58 uses schema version 41.
// Schema Upgrades must add migration code here.
// >>> IMPORTANT! <<<
@ -1480,528 +1391,6 @@ Database::UpdateBookmarkRootTitles()
return NS_OK;
}
nsresult
Database::MigrateV13Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Dynamic containers are no longer supported.
nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt;
nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_bookmarks WHERE type = :item_type"),
getter_AddRefs(deleteDynContainersStmt));
rv = deleteDynContainersStmt->BindInt32ByName(
NS_LITERAL_CSTRING("item_type"),
nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER
);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = deleteDynContainersStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV15Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Drop moz_bookmarks_beforedelete_v1_trigger, since it's more expensive than
// useful.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER IF EXISTS moz_bookmarks_beforedelete_v1_trigger"
));
NS_ENSURE_SUCCESS(rv, rv);
// Remove any orphan keywords.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_keywords "
"WHERE NOT EXISTS ( "
"SELECT id "
"FROM moz_bookmarks "
"WHERE keyword_id = moz_keywords.id "
")"
));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV17Up()
{
MOZ_ASSERT(NS_IsMainThread());
bool tableExists = false;
nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!tableExists) {
// For anyone who used in-development versions of this autocomplete,
// drop the old tables and its indexes.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_hostnames_frecencyindex"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE IF EXISTS moz_hostnames"
));
NS_ENSURE_SUCCESS(rv, rv);
// Add the moz_hosts table so we can get hostnames for URL autocomplete.
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fill the moz_hosts table with all the domains in moz_places.
nsCOMPtr<mozIStorageAsyncStatement> fillHostsStmt;
rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"INSERT OR IGNORE INTO moz_hosts (host, frecency) "
"SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, "
"(SELECT MAX(frecency) FROM moz_places "
"WHERE rev_host = h.rev_host "
"OR rev_host = h.rev_host || 'www.' "
") AS frecency "
"FROM moz_places h "
"WHERE LENGTH(h.rev_host) > 1 "
"GROUP BY h.rev_host"
), getter_AddRefs(fillHostsStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = fillHostsStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV18Up()
{
MOZ_ASSERT(NS_IsMainThread());
// moz_hosts should distinguish on typed entries.
// Check if the profile already has a typed column.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT typed FROM moz_hosts"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_hosts ADD COLUMN typed NOT NULL DEFAULT 0"
));
NS_ENSURE_SUCCESS(rv, rv);
}
// With the addition of the typed column the covering index loses its
// advantages. On the other side querying on host and (optionally) typed
// largely restricts the number of results, making scans decently fast.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_hosts_frecencyhostindex"
));
NS_ENSURE_SUCCESS(rv, rv);
// Update typed data.
nsCOMPtr<mozIStorageAsyncStatement> updateTypedStmt;
rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"UPDATE moz_hosts SET typed = 1 WHERE host IN ( "
"SELECT fixup_url(get_unreversed_host(rev_host)) "
"FROM moz_places WHERE typed = 1 "
") "
), getter_AddRefs(updateTypedStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = updateTypedStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV19Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Livemarks children are no longer bookmarks.
// Remove all children of folders annotated as livemarks.
nsCOMPtr<mozIStorageStatement> deleteLivemarksChildrenStmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_bookmarks WHERE parent IN("
"SELECT b.id FROM moz_bookmarks b "
"JOIN moz_items_annos a ON a.item_id = b.id "
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
"WHERE b.type = :item_type AND n.name = :anno_name "
")"
), getter_AddRefs(deleteLivemarksChildrenStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksChildrenStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI)
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksChildrenStmt->BindInt32ByName(
NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_FOLDER
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksChildrenStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Clear obsolete livemark prefs.
(void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_seconds");
(void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_limit_count");
(void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_delay_time");
// Remove the old status annotations.
nsCOMPtr<mozIStorageStatement> deleteLivemarksAnnosStmt;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos WHERE anno_attribute_id IN("
"SELECT id FROM moz_anno_attributes "
"WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
")"
), getter_AddRefs(deleteLivemarksAnnosStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Remove orphan annotation names.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_anno_attributes "
"WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
), getter_AddRefs(deleteLivemarksAnnosStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteLivemarksAnnosStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV20Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Remove obsolete bookmark GUID annotations.
nsCOMPtr<mozIStorageStatement> deleteOldBookmarkGUIDAnnosStmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos WHERE anno_attribute_id = ("
"SELECT id FROM moz_anno_attributes "
"WHERE name = :anno_guid"
")"
), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Remove the orphan annotation name.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_anno_attributes "
"WHERE name = :anno_guid"
), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV21Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Add a prefix column to moz_hosts.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT prefix FROM moz_hosts"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_hosts ADD COLUMN prefix"
));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV22Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Reset all session IDs to 0 since we don't support them anymore.
// We don't set them to NULL to avoid breaking downgrades.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT session FROM moz_historyvisits"
), getter_AddRefs(stmt));
if (NS_SUCCEEDED(rv)) {
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_historyvisits SET session = 0"
));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV23Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Recalculate hosts prefixes.
nsCOMPtr<mozIStorageAsyncStatement> updatePrefixesStmt;
nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"UPDATE moz_hosts SET prefix = ( " HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
), getter_AddRefs(updatePrefixesStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = updatePrefixesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV24Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Add a foreign_count column to moz_places
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT foreign_count FROM moz_places"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_places ADD COLUMN foreign_count INTEGER DEFAULT 0 NOT NULL"));
NS_ENSURE_SUCCESS(rv, rv);
}
// Adjust counts for all the rows
nsCOMPtr<mozIStorageStatement> updateStmt;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_places SET foreign_count = "
"(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) "
), getter_AddRefs(updateStmt));
NS_ENSURE_SUCCESS(rv, rv);
mozStorageStatementScoper updateScoper(updateStmt);
rv = updateStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV25Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Change bookmark roots GUIDs to constant values.
// If moz_bookmarks_roots doesn't exist anymore, it's because we finally have
// been able to remove it. In such a case, we already assigned constant GUIDs
// to the roots and we can skip this migration.
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT root_name FROM moz_bookmarks_roots"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
return NS_OK;
}
}
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET guid = :guid "
"WHERE id = (SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :name) "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
const char *rootNames[] = { "places", "menu", "toolbar", "tags", "unfiled" };
const char *rootGuids[] = { "root________"
, "menu________"
, "toolbar_____"
, "tags________"
, "unfiled_____"
};
for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) {
// Since this is using the synchronous API, we cannot use
// a BindingParamsArray.
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
nsDependentCString(rootNames[i]));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
nsDependentCString(rootGuids[i]));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV26Up() {
MOZ_ASSERT(NS_IsMainThread());
// Round down dateAdded and lastModified values to milliseconds precision.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET dateAdded = dateAdded - dateAdded % 1000, "
" lastModified = lastModified - lastModified % 1000"));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV27Up() {
MOZ_ASSERT(NS_IsMainThread());
// Change keywords store, moving their relation from bookmarks to urls.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT place_id FROM moz_keywords"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
// Even if these 2 columns have a unique constraint, we allow NULL values
// for backwards compatibility. NULL never breaks a unique constraint.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_keywords ADD COLUMN place_id INTEGER"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_keywords ADD COLUMN post_data TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
NS_ENSURE_SUCCESS(rv, rv);
}
// Associate keywords with uris. A keyword could be associated to multiple
// bookmarks uris, or multiple keywords could be associated to the same uri.
// The new system only allows multiple uris per keyword, provided they have
// a different post_data value.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) "
"SELECT k.id, k.keyword, h.id, MAX(a.content) "
"FROM moz_places h "
"JOIN moz_bookmarks b ON b.fk = h.id "
"JOIN moz_keywords k ON k.id = b.keyword_id "
"LEFT JOIN moz_items_annos a ON a.item_id = b.id "
"AND a.anno_attribute_id = (SELECT id FROM moz_anno_attributes "
"WHERE name = 'bookmarkProperties/POSTData') "
"WHERE k.place_id ISNULL "
"GROUP BY keyword"));
NS_ENSURE_SUCCESS(rv, rv);
// Remove any keyword that points to a non-existing place id.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_keywords "
"WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = moz_keywords.place_id)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET keyword_id = NULL "
"WHERE NOT EXISTS (SELECT 1 FROM moz_keywords WHERE id = moz_bookmarks.keyword_id)"));
NS_ENSURE_SUCCESS(rv, rv);
// Adjust foreign_count for all the rows.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places SET foreign_count = "
"(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
"(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV28Up() {
MOZ_ASSERT(NS_IsMainThread());
// v27 migration was bogus and set some unrelated annotations as post_data for
// keywords having an annotated bookmark.
// The current v27 migration function is fixed, but we still need to handle
// users that hit the bogus version. Since we can't distinguish, we'll just
// set again all of the post data.
DebugOnly<nsresult> rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_keywords "
"SET post_data = ( "
"SELECT content FROM moz_items_annos a "
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
"JOIN moz_bookmarks b on b.id = a.item_id "
"WHERE n.name = 'bookmarkProperties/POSTData' "
"AND b.keyword_id = moz_keywords.id "
"ORDER BY b.lastModified DESC "
"LIMIT 1 "
") "
"WHERE EXISTS(SELECT 1 FROM moz_bookmarks WHERE keyword_id = moz_keywords.id) "
));
// In case the update fails a constraint, we don't want to throw away the
// whole database for just a few keywords. In rare cases the user might have
// to recreate them. Though, at this point, there shouldn't be 2 keywords
// pointing to the same url and post data, cause the previous migration step
// removed them.
MOZ_ASSERT(NS_SUCCEEDED(rv));
return NS_OK;
}
nsresult
Database::MigrateV30Up() {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_favicons_guid_uniqueindex"
));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV31Up() {
MOZ_ASSERT(NS_IsMainThread());
@ -2412,6 +1801,19 @@ Database::MigrateV40Up() {
return NS_OK;
}
nsresult
Database::MigrateV41Up() {
MOZ_ASSERT(NS_IsMainThread());
// Remove old favicons entities.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_places_faviconindex"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE IF EXISTS moz_favicons"));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
nsTArray<int64_t>& aItemIds)

View File

@ -19,7 +19,7 @@
// This is the schema version. Update it at any schema change and add a
// corresponding migrateVxx method below.
#define DATABASE_SCHEMA_VERSION 40
#define DATABASE_SCHEMA_VERSION 41
// Fired after Places inited.
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
@ -280,21 +280,6 @@ protected:
/**
* Helpers used by schema upgrades.
*/
nsresult MigrateV13Up();
nsresult MigrateV15Up();
nsresult MigrateV17Up();
nsresult MigrateV18Up();
nsresult MigrateV19Up();
nsresult MigrateV20Up();
nsresult MigrateV21Up();
nsresult MigrateV22Up();
nsresult MigrateV23Up();
nsresult MigrateV24Up();
nsresult MigrateV25Up();
nsresult MigrateV26Up();
nsresult MigrateV27Up();
nsresult MigrateV28Up();
nsresult MigrateV30Up();
nsresult MigrateV31Up();
nsresult MigrateV32Up();
nsresult MigrateV33Up();
@ -305,6 +290,7 @@ protected:
nsresult MigrateV38Up();
nsresult MigrateV39Up();
nsresult MigrateV40Up();
nsresult MigrateV41Up();
nsresult UpdateBookmarkRootTitles();

View File

@ -6,8 +6,8 @@
// It is expected that the test files importing this file define Cu etc.
/* global Cu, Ci, Cc, Cr */
const CURRENT_SCHEMA_VERSION = 40;
const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
const CURRENT_SCHEMA_VERSION = 41;
const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";

View File

@ -2,7 +2,19 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase(`places_v${CURRENT_SCHEMA_VERSION}.sqlite`);
// Find the latest available version, this allows to skip .sqlite files when
// the migration was trivial and uninsteresting to test.
let version = CURRENT_SCHEMA_VERSION;
while (version > 0) {
let dbFile = OS.Path.join(do_get_cwd().path, `places_v${version}.sqlite`);
if (await OS.File.exists(dbFile)) {
do_print("Using database version " + version);
break;
}
version--;
}
Assert.ok(version > 0, "Found a valid database version");
await setupPlacesDatabase(`places_v${version}.sqlite`);
// Downgrade the schema version to the first supported one.
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = await Sqlite.openConnection({ path });

View File

@ -7,7 +7,7 @@
*/
add_task(async function setup() {
await setupPlacesDatabase("places_v6.sqlite");
await setupPlacesDatabase("places_outdated.sqlite");
});
add_task(async function corrupt_database_not_exists() {

View File

@ -1,48 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase("places_v11.sqlite");
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_moz_hosts() {
let db = await PlacesUtils.promiseDBConnection();
// This will throw if the column does not exist.
await db.execute("SELECT host, frecency, typed, prefix FROM moz_hosts");
// moz_hosts is populated asynchronously, so we need to wait.
await PlacesTestUtils.promiseAsyncUpdates();
// check the number of entries in moz_hosts equals the number of
// unique rev_host in moz_places
let rows = await db.execute(
`SELECT (SELECT COUNT(host) FROM moz_hosts),
(SELECT COUNT(DISTINCT rev_host)
FROM moz_places
WHERE LENGTH(rev_host) > 1)
`);
Assert.equal(rows.length, 1);
let mozHostsCount = rows[0].getResultByIndex(0);
let mozPlacesCount = rows[0].getResultByIndex(1);
Assert.ok(mozPlacesCount > 0, "There is some url in the database");
Assert.equal(mozPlacesCount, mozHostsCount, "moz_hosts has the expected number of entries");
});
add_task(async function test_journal() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute("PRAGMA journal_mode");
Assert.equal(rows.length, 1);
// WAL journal mode should be set on this database.
Assert.equal(rows[0].getResultByIndex(0), "wal");
});

View File

@ -1,41 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const ANNO_LEGACYGUID = "placesInternal/GUID";
var getTotalGuidAnnotationsCount = async function(db) {
let rows = await db.execute(
`SELECT count(*)
FROM moz_items_annos a
JOIN moz_anno_attributes b ON a.anno_attribute_id = b.id
WHERE b.name = :attr_name
`, { attr_name: ANNO_LEGACYGUID });
return rows[0].getResultByIndex(0);
};
add_task(async function setup() {
await setupPlacesDatabase("places_v19.sqlite");
});
add_task(async function initial_state() {
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = await Sqlite.openConnection({ path });
Assert.equal((await getTotalGuidAnnotationsCount(db)), 1,
"There should be 1 obsolete guid annotation");
await db.close();
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_bookmark_guid_annotation_removed() {
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await getTotalGuidAnnotationsCount(db)), 0,
"There should be no more obsolete GUID annotations.");
});

View File

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase("places_v24.sqlite");
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_bookmark_guid_annotation_removed() {
await PlacesUtils.bookmarks.eraseEverything();
let db = await PlacesUtils.promiseDBConnection();
let m = new Map([
[PlacesUtils.placesRootId, PlacesUtils.bookmarks.rootGuid],
[PlacesUtils.bookmarksMenuFolderId, PlacesUtils.bookmarks.menuGuid],
[PlacesUtils.toolbarFolderId, PlacesUtils.bookmarks.toolbarGuid],
[PlacesUtils.unfiledBookmarksFolderId, PlacesUtils.bookmarks.unfiledGuid],
[PlacesUtils.tagsFolderId, PlacesUtils.bookmarks.tagsGuid],
[PlacesUtils.mobileFolderId, PlacesUtils.bookmarks.mobileGuid],
]);
let rows = await db.execute(`SELECT id, guid FROM moz_bookmarks`);
for (let row of rows) {
let id = row.getResultByName("id");
let guid = row.getResultByName("guid");
Assert.equal(m.get(id), guid, "The root folder has the correct GUID");
}
});

View File

@ -1,30 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase("places_v25.sqlite");
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_dates_rounded() {
let root = await PlacesUtils.promiseBookmarksTree();
function ensureDates(node) {
// When/if promiseBookmarksTree returns these as Date objects, switch this
// test to use getItemDateAdded and getItemLastModified. And when these
// methods are removed, this test can be eliminated altogether.
Assert.strictEqual(typeof(node.dateAdded), "number");
Assert.strictEqual(typeof(node.lastModified), "number");
Assert.strictEqual(node.dateAdded % 1000, 0);
Assert.strictEqual(node.lastModified % 1000, 0);
if ("children" in node)
node.children.forEach(ensureDates);
}
ensureDates(root);
});

View File

@ -1,98 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase("places_v26.sqlite");
// Setup database contents to be migrated.
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = await Sqlite.openConnection({ path });
// Add pages.
await db.execute(`INSERT INTO moz_places (url, guid)
VALUES ("http://test1.com/", "test1_______")
, ("http://test2.com/", "test2_______")
, ("http://test3.com/", "test3_______")
`);
// Add keywords.
await db.execute(`INSERT INTO moz_keywords (keyword)
VALUES ("kw1")
, ("kw2")
, ("kw3")
, ("kw4")
, ("kw5")
`);
// Add bookmarks.
let now = Date.now() * 1000;
let index = 0;
await db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, keyword_id, guid)
VALUES (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark1___")
/* same uri, different keyword */
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw2'), "bookmark2___")
/* different uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = 'test2_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark3___")
/* same uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark4___")
/* same uri, same keyword as 2 */
, (1, (SELECT id FROM moz_places WHERE guid = 'test2_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw2'), "bookmark5___")
/* different uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw3'), "bookmark6___")
, (1, (SELECT id FROM moz_places WHERE guid = 'test3_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw4'), "bookmark7___")
/* same uri and post_data as bookmark7, different keyword */
, (1, (SELECT id FROM moz_places WHERE guid = 'test3_______'), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = 'kw5'), "bookmark8___")
`);
// Add postData.
await db.execute(`INSERT INTO moz_anno_attributes (name)
VALUES ("bookmarkProperties/POSTData")
, ("someOtherAnno")`);
await db.execute(`INSERT INTO moz_items_annos(anno_attribute_id, item_id, content)
VALUES ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark3___"), "postData1")
, ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")
, ((SELECT id FROM moz_anno_attributes where name = "someOtherAnno"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "zzzzzzzzzz")
, ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark7___"), "postData3")
, ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark8___"), "postData3")
`);
await db.close();
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_keywords() {
// When 2 urls have the same keyword, if one has postData it will be
// preferred.
let entry1 = await PlacesUtils.keywords.fetch("kw1");
Assert.equal(entry1.url.href, "http://test2.com/");
Assert.equal(entry1.postData, "postData1");
let entry2 = await PlacesUtils.keywords.fetch("kw2");
Assert.equal(entry2.url.href, "http://test2.com/");
Assert.equal(entry2.postData, "postData2");
let entry3 = await PlacesUtils.keywords.fetch("kw3");
Assert.equal(entry3.url.href, "http://test1.com/");
Assert.equal(entry3.postData, null);
let entry4 = await PlacesUtils.keywords.fetch("kw4");
Assert.equal(entry4, null);
let entry5 = await PlacesUtils.keywords.fetch("kw5");
Assert.equal(entry5.url.href, "http://test3.com/");
Assert.equal(entry5.postData, "postData3");
Assert.equal((await foreign_count("http://test1.com/")), 5); // 4 bookmark2 + 1 keywords
Assert.equal((await foreign_count("http://test2.com/")), 4); // 2 bookmark2 + 2 keywords
Assert.equal((await foreign_count("http://test3.com/")), 3); // 2 bookmark2 + 1 keywords
});

View File

@ -1,77 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await setupPlacesDatabase("places_v27.sqlite");
// Setup database contents to be migrated.
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = await Sqlite.openConnection({ path });
// Add pages.
await db.execute(`INSERT INTO moz_places (url, guid)
VALUES ("http://test1.com/", "test1_______")
, ("http://test2.com/", "test2_______")
`);
// Add keywords.
await db.execute(`INSERT INTO moz_keywords (keyword, place_id, post_data)
VALUES ("kw1", (SELECT id FROM moz_places WHERE guid = "test2_______"), "broken data")
, ("kw2", (SELECT id FROM moz_places WHERE guid = "test2_______"), NULL)
, ("kw3", (SELECT id FROM moz_places WHERE guid = "test1_______"), "zzzzzzzzzz")
`);
// Add bookmarks.
let now = Date.now() * 1000;
let index = 0;
await db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, keyword_id, guid)
VALUES (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark1___")
/* same uri, different keyword */
, (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw2"), "bookmark2___")
/* different uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = "test2_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark3___")
/* same uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark4___")
/* same uri, same keyword as 2 */
, (1, (SELECT id FROM moz_places WHERE guid = "test2_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw2"), "bookmark5___")
/* different uri, same keyword as 1 */
, (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
(SELECT id FROM moz_keywords WHERE keyword = "kw3"), "bookmark6___")
`);
// Add postData.
await db.execute(`INSERT INTO moz_anno_attributes (name)
VALUES ("bookmarkProperties/POSTData")
, ("someOtherAnno")`);
await db.execute(`INSERT INTO moz_items_annos(anno_attribute_id, item_id, content)
VALUES ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark3___"), "postData1")
, ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")
, ((SELECT id FROM moz_anno_attributes where name = "someOtherAnno"),
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "zzzzzzzzzz")
`);
await db.close();
});
add_task(async function database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_keywords() {
// When 2 urls have the same keyword, if one has postData it will be
// preferred.
let entry1 = await PlacesUtils.keywords.fetch("kw1");
Assert.equal(entry1.url.href, "http://test2.com/");
Assert.equal(entry1.postData, "postData1");
let entry2 = await PlacesUtils.keywords.fetch("kw2");
Assert.equal(entry2.url.href, "http://test2.com/");
Assert.equal(entry2.postData, "postData2");
let entry3 = await PlacesUtils.keywords.fetch("kw3");
Assert.equal(entry3.url.href, "http://test1.com/");
Assert.equal(entry3.postData, null);
});

View File

@ -39,15 +39,15 @@ add_task(async function database_is_valid() {
add_task(async function test_icons() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`SELECT url FROM moz_favicons`);
Assert.equal(rows.length, 0, "favicons table should be empty");
await Assert.rejects(db.execute(`SELECT url FROM moz_favicons`),
"The moz_favicons table should not exist");
for (let entry of gTestcases) {
do_print("");
do_print("Checking " + entry.icon_url + " - " + entry.page_url);
rows = await db.execute(`SELECT id, expire_ms, width FROM moz_icons
WHERE fixed_icon_url_hash = hash(fixup_url(:icon_url))
AND icon_url = :icon_url
`, { icon_url: entry.icon_url });
let rows = await db.execute(`SELECT id, expire_ms, width FROM moz_icons
WHERE fixed_icon_url_hash = hash(fixup_url(:icon_url))
AND icon_url = :icon_url
`, { icon_url: entry.icon_url });
Assert.equal(!!rows.length, entry.has_data, "icon exists");
if (!entry.has_data) {
// Icon not migrated.

View File

@ -2,39 +2,15 @@
head = head_migration.js
support-files =
places_v6.sqlite
places_v10.sqlite
places_v11.sqlite
places_v17.sqlite
places_v19.sqlite
places_v21.sqlite
places_v22.sqlite
places_v23.sqlite
places_v24.sqlite
places_v25.sqlite
places_v26.sqlite
places_v27.sqlite
places_v28.sqlite
places_v30.sqlite
places_outdated.sqlite
places_v31.sqlite
places_v32.sqlite
places_v33.sqlite
places_v34.sqlite
places_v35.sqlite
places_v36.sqlite
places_v37.sqlite
places_v38.sqlite
places_v39.sqlite
places_v40.sqlite
[test_current_from_downgraded.js]
[test_current_from_v6.js]
[test_current_from_v11.js]
[test_current_from_v19.js]
[test_current_from_v24.js]
[test_current_from_v25.js]
[test_current_from_v26.js]
[test_current_from_v27.js]
[test_current_from_outdated.js]
[test_current_from_v31.js]
[test_current_from_v34.js]
[test_current_from_v34_no_roots.js]