зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1336518 - Move Sync history queries into PlacesSyncUtils. r=kitcambridge
MozReview-Commit-ID: Lood8ivLeJf --HG-- extra : rebase_source : dcb8207378afc6ce2995acfe4f235f3ed728a188
This commit is contained in:
Родитель
9cb48aea09
Коммит
7a9b4d8b79
|
@ -9,7 +9,8 @@ var Ci = Components.interfaces;
|
||||||
var Cu = Components.utils;
|
var Cu = Components.utils;
|
||||||
var Cr = Components.results;
|
var Cr = Components.results;
|
||||||
|
|
||||||
const HISTORY_TTL = 5184000; // 60 days
|
const HISTORY_TTL = 5184000; // 60 days in milliseconds
|
||||||
|
const THIRTY_DAYS_IN_MS = 2592000000; // 30 days in milliseconds
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://services-common/async.js");
|
Cu.import("resource://services-common/async.js");
|
||||||
|
@ -21,6 +22,9 @@ Cu.import("resource://services-sync/util.js");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||||
"resource://gre/modules/PlacesUtils.jsm");
|
"resource://gre/modules/PlacesUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
|
||||||
|
"resource://gre/modules/PlacesSyncUtils.jsm");
|
||||||
|
|
||||||
this.HistoryRec = function HistoryRec(collection, id) {
|
this.HistoryRec = function HistoryRec(collection, id) {
|
||||||
CryptoWrapper.call(this, collection, id);
|
CryptoWrapper.call(this, collection, id);
|
||||||
}
|
}
|
||||||
|
@ -71,41 +75,8 @@ HistoryEngine.prototype = {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
|
let guidsToRemove = await PlacesSyncUtils.history.determineNonSyncableGuids(modifiedGUIDs);
|
||||||
.DBConnection;
|
this._tracker.removeChangedID(...guidsToRemove);
|
||||||
|
|
||||||
// Filter out hidden pages and `TRANSITION_FRAMED_LINK` visits. These are
|
|
||||||
// excluded when rendering the history menu, so we use the same constraints
|
|
||||||
// for Sync. We also don't want to sync `TRANSITION_EMBED` visits, but those
|
|
||||||
// aren't stored in the database.
|
|
||||||
for (let startIndex = 0;
|
|
||||||
startIndex < modifiedGUIDs.length;
|
|
||||||
startIndex += SQLITE_MAX_VARIABLE_NUMBER) {
|
|
||||||
|
|
||||||
let chunkLength = Math.min(SQLITE_MAX_VARIABLE_NUMBER,
|
|
||||||
modifiedGUIDs.length - startIndex);
|
|
||||||
|
|
||||||
let query = `
|
|
||||||
SELECT DISTINCT p.guid FROM moz_places p
|
|
||||||
JOIN moz_historyvisits v ON p.id = v.place_id
|
|
||||||
WHERE p.guid IN (${new Array(chunkLength).fill("?").join(",")}) AND
|
|
||||||
(p.hidden = 1 OR v.visit_type IN (0,
|
|
||||||
${PlacesUtils.history.TRANSITION_FRAMED_LINK}))
|
|
||||||
`;
|
|
||||||
|
|
||||||
let statement = db.createAsyncStatement(query);
|
|
||||||
try {
|
|
||||||
for (let i = 0; i < chunkLength; i++) {
|
|
||||||
statement.bindByIndex(i, modifiedGUIDs[startIndex + i]);
|
|
||||||
}
|
|
||||||
let results = Async.querySpinningly(statement, ["guid"]);
|
|
||||||
let guids = results.map(result => result.guid);
|
|
||||||
this._tracker.removeChangedID(...guids);
|
|
||||||
} finally {
|
|
||||||
statement.finalize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._tracker.changedIDs;
|
return this._tracker.changedIDs;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -146,112 +117,59 @@ HistoryStore.prototype = {
|
||||||
return this._stmts[query] = db.createAsyncStatement(query);
|
return this._stmts[query] = db.createAsyncStatement(query);
|
||||||
},
|
},
|
||||||
|
|
||||||
get _setGUIDStm() {
|
|
||||||
return this._getStmt(
|
|
||||||
"UPDATE moz_places " +
|
|
||||||
"SET guid = :guid " +
|
|
||||||
"WHERE url_hash = hash(:page_url) AND url = :page_url");
|
|
||||||
},
|
|
||||||
|
|
||||||
// Some helper functions to handle GUIDs
|
// Some helper functions to handle GUIDs
|
||||||
setGUID: function setGUID(uri, guid) {
|
async setGUID(uri, guid) {
|
||||||
uri = uri.spec ? uri.spec : uri;
|
|
||||||
|
|
||||||
if (!guid) {
|
if (!guid) {
|
||||||
guid = Utils.makeGUID();
|
guid = Utils.makeGUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
let stmt = this._setGUIDStm;
|
try {
|
||||||
stmt.params.guid = guid;
|
await PlacesSyncUtils.history.changeGuid(uri, guid);
|
||||||
stmt.params.page_url = uri;
|
} catch (e) {
|
||||||
Async.querySpinningly(stmt);
|
this._log.error("Error setting GUID ${guid} for URI ${uri}", guid, uri);
|
||||||
|
}
|
||||||
|
|
||||||
return guid;
|
return guid;
|
||||||
},
|
},
|
||||||
|
|
||||||
get _guidStm() {
|
async GUIDForUri(uri, create) {
|
||||||
return this._getStmt(
|
|
||||||
"SELECT guid " +
|
|
||||||
"FROM moz_places " +
|
|
||||||
"WHERE url_hash = hash(:page_url) AND url = :page_url");
|
|
||||||
},
|
|
||||||
_guidCols: ["guid"],
|
|
||||||
|
|
||||||
GUIDForUri: function GUIDForUri(uri, create) {
|
|
||||||
let stm = this._guidStm;
|
|
||||||
stm.params.page_url = uri.spec ? uri.spec : uri;
|
|
||||||
|
|
||||||
// Use the existing GUID if it exists
|
// Use the existing GUID if it exists
|
||||||
let result = Async.querySpinningly(stm, this._guidCols)[0];
|
let guid;
|
||||||
if (result && result.guid)
|
try {
|
||||||
return result.guid;
|
guid = await PlacesSyncUtils.history.fetchGuidForURL(uri);
|
||||||
|
} catch (e) {
|
||||||
|
this._log.error("Error fetching GUID for URL ${uri}", uri);
|
||||||
|
}
|
||||||
|
|
||||||
// Give the uri a GUID if it doesn't have one
|
// If the URI has an existing GUID, return it.
|
||||||
if (create)
|
if (guid) {
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the URI doesn't have a GUID and we were indicated to create one.
|
||||||
|
if (create) {
|
||||||
return this.setGUID(uri);
|
return this.setGUID(uri);
|
||||||
},
|
}
|
||||||
|
|
||||||
get _visitStm() {
|
// If the URI doesn't have a GUID and we didn't create one for it.
|
||||||
return this._getStmt(`/* do not warn (bug 599936) */
|
return null;
|
||||||
SELECT visit_type type, visit_date date
|
|
||||||
FROM moz_historyvisits
|
|
||||||
JOIN moz_places h ON h.id = place_id
|
|
||||||
WHERE url_hash = hash(:url) AND url = :url
|
|
||||||
ORDER BY date DESC LIMIT 20`);
|
|
||||||
},
|
|
||||||
_visitCols: ["date", "type"],
|
|
||||||
|
|
||||||
get _urlStm() {
|
|
||||||
return this._getStmt(
|
|
||||||
"SELECT url, title, frecency " +
|
|
||||||
"FROM moz_places " +
|
|
||||||
"WHERE guid = :guid");
|
|
||||||
},
|
|
||||||
_urlCols: ["url", "title", "frecency"],
|
|
||||||
|
|
||||||
get _allUrlStm() {
|
|
||||||
// Filter out hidden pages and framed link visits. See `pullNewChanges`
|
|
||||||
// for more info.
|
|
||||||
return this._getStmt(`
|
|
||||||
SELECT DISTINCT p.url
|
|
||||||
FROM moz_places p
|
|
||||||
JOIN moz_historyvisits v ON p.id = v.place_id
|
|
||||||
WHERE p.last_visit_date > :cutoff_date AND
|
|
||||||
p.hidden = 0 AND
|
|
||||||
v.visit_type NOT IN (0,
|
|
||||||
${PlacesUtils.history.TRANSITION_FRAMED_LINK})
|
|
||||||
ORDER BY frecency DESC
|
|
||||||
LIMIT :max_results`);
|
|
||||||
},
|
|
||||||
_allUrlCols: ["url"],
|
|
||||||
|
|
||||||
// See bug 320831 for why we use SQL here
|
|
||||||
_getVisits: function HistStore__getVisits(uri) {
|
|
||||||
this._visitStm.params.url = uri;
|
|
||||||
return Async.querySpinningly(this._visitStm, this._visitCols);
|
|
||||||
},
|
|
||||||
|
|
||||||
// See bug 468732 for why we use SQL here
|
|
||||||
_findURLByGUID: function HistStore__findURLByGUID(guid) {
|
|
||||||
this._urlStm.params.guid = guid;
|
|
||||||
return Async.querySpinningly(this._urlStm, this._urlCols)[0];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async changeItemID(oldID, newID) {
|
async changeItemID(oldID, newID) {
|
||||||
this.setGUID(this._findURLByGUID(oldID).url, newID);
|
this.setGUID(await PlacesSyncUtils.history.fetchURLInfoForGuid(oldID).url, newID);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async getAllIDs() {
|
async getAllIDs() {
|
||||||
// Only get places visited within the last 30 days (30*24*60*60*1000ms)
|
let urls = await PlacesSyncUtils.history.getAllURLs({ since: new Date((Date.now() - THIRTY_DAYS_IN_MS)), limit: MAX_HISTORY_UPLOAD });
|
||||||
this._allUrlStm.params.cutoff_date = (Date.now() - 2592000000) * 1000;
|
|
||||||
this._allUrlStm.params.max_results = MAX_HISTORY_UPLOAD;
|
|
||||||
|
|
||||||
let urls = Async.querySpinningly(this._allUrlStm, this._allUrlCols);
|
let urlsByGUID = {};
|
||||||
let self = this;
|
for (let url of urls) {
|
||||||
return urls.reduce(function(ids, item) {
|
let guid = await this.GUIDForUri(url, true);
|
||||||
ids[self.GUIDForUri(item.url, true)] = item.url;
|
urlsByGUID[guid] = url;
|
||||||
return ids;
|
}
|
||||||
}, {});
|
return urlsByGUID;
|
||||||
},
|
},
|
||||||
|
|
||||||
async applyIncomingBatch(records) {
|
async applyIncomingBatch(records) {
|
||||||
|
@ -274,7 +192,7 @@ HistoryStore.prototype = {
|
||||||
// No further processing needed. Remove it from the list.
|
// No further processing needed. Remove it from the list.
|
||||||
shouldApply = false;
|
shouldApply = false;
|
||||||
} else {
|
} else {
|
||||||
shouldApply = this._recordToPlaceInfo(record);
|
shouldApply = await this._recordToPlaceInfo(record);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (Async.isShutdownException(ex)) {
|
if (Async.isShutdownException(ex)) {
|
||||||
|
@ -315,7 +233,7 @@ HistoryStore.prototype = {
|
||||||
* returns true if the record is to be applied, false otherwise
|
* returns true if the record is to be applied, false otherwise
|
||||||
* (no visits to add, etc.),
|
* (no visits to add, etc.),
|
||||||
*/
|
*/
|
||||||
_recordToPlaceInfo: function _recordToPlaceInfo(record) {
|
async _recordToPlaceInfo(record) {
|
||||||
// Sort out invalid URIs and ones Places just simply doesn't want.
|
// Sort out invalid URIs and ones Places just simply doesn't want.
|
||||||
record.uri = Utils.makeURI(record.histUri);
|
record.uri = Utils.makeURI(record.histUri);
|
||||||
if (!record.uri) {
|
if (!record.uri) {
|
||||||
|
@ -339,7 +257,13 @@ HistoryStore.prototype = {
|
||||||
// the same timestamp and type as a local one won't get applied.
|
// the same timestamp and type as a local one won't get applied.
|
||||||
// To avoid creating new objects, we rewrite the query result so we
|
// To avoid creating new objects, we rewrite the query result so we
|
||||||
// can simply check for containment below.
|
// can simply check for containment below.
|
||||||
let curVisits = this._getVisits(record.histUri);
|
let curVisits = [];
|
||||||
|
try {
|
||||||
|
curVisits = await PlacesSyncUtils.history.fetchVisitsForURL(record.histUri);
|
||||||
|
} catch (e) {
|
||||||
|
this._log.error("Error while fetching visits for URL ${record.histUri}", record.histUri);
|
||||||
|
}
|
||||||
|
|
||||||
let i, k;
|
let i, k;
|
||||||
for (i = 0; i < curVisits.length; i++) {
|
for (i = 0; i < curVisits.length; i++) {
|
||||||
curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
|
curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
|
||||||
|
@ -402,17 +326,22 @@ HistoryStore.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async itemExists(id) {
|
async itemExists(id) {
|
||||||
return !!this._findURLByGUID(id);
|
return !!(await PlacesSyncUtils.history.fetchURLInfoForGuid(id));
|
||||||
},
|
},
|
||||||
|
|
||||||
async createRecord(id, collection) {
|
async createRecord(id, collection) {
|
||||||
let foo = this._findURLByGUID(id);
|
let foo = await PlacesSyncUtils.history.fetchURLInfoForGuid(id);
|
||||||
let record = new HistoryRec(collection, id);
|
let record = new HistoryRec(collection, id);
|
||||||
if (foo) {
|
if (foo) {
|
||||||
record.histUri = foo.url;
|
record.histUri = foo.url;
|
||||||
record.title = foo.title;
|
record.title = foo.title;
|
||||||
record.sortindex = foo.frecency;
|
record.sortindex = foo.frecency;
|
||||||
record.visits = this._getVisits(record.histUri);
|
try {
|
||||||
|
record.visits = await PlacesSyncUtils.history.fetchVisitsForURL(record.histUri);
|
||||||
|
} catch (e) {
|
||||||
|
this._log.error("Error while fetching visits for URL ${record.histUri}", record.histUri);
|
||||||
|
record.visits = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
record.deleted = true;
|
record.deleted = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ add_task(async function test_store_create() {
|
||||||
]);
|
]);
|
||||||
await onVisitObserved;
|
await onVisitObserved;
|
||||||
try {
|
try {
|
||||||
do_check_attribute_count(Async.promiseSpinningly(store.getAllIDs()), 2);
|
do_check_attribute_count(await store.getAllIDs(), 2);
|
||||||
let queryres = queryHistoryVisits(tburi);
|
let queryres = queryHistoryVisits(tburi);
|
||||||
do_check_eq(queryres.length, 1);
|
do_check_eq(queryres.length, 1);
|
||||||
do_check_eq(queryres[0].time, TIMESTAMP3);
|
do_check_eq(queryres[0].time, TIMESTAMP3);
|
||||||
|
|
|
@ -144,7 +144,7 @@ add_task(async function test_track_delete() {
|
||||||
// This isn't present because we weren't tracking when it was visited.
|
// This isn't present because we weren't tracking when it was visited.
|
||||||
await addVisit("track_delete");
|
await addVisit("track_delete");
|
||||||
let uri = Utils.makeURI("http://getfirefox.com/track_delete");
|
let uri = Utils.makeURI("http://getfirefox.com/track_delete");
|
||||||
let guid = engine._store.GUIDForUri(uri);
|
let guid = await engine._store.GUIDForUri(uri.spec);
|
||||||
await verifyTrackerEmpty();
|
await verifyTrackerEmpty();
|
||||||
|
|
||||||
await startTracking();
|
await startTracking();
|
||||||
|
@ -162,7 +162,7 @@ add_task(async function test_track_delete() {
|
||||||
add_task(async function test_dont_track_expiration() {
|
add_task(async function test_dont_track_expiration() {
|
||||||
_("Expirations are not tracked.");
|
_("Expirations are not tracked.");
|
||||||
let uriToRemove = await addVisit("to_remove");
|
let uriToRemove = await addVisit("to_remove");
|
||||||
let guidToRemove = engine._store.GUIDForUri(uriToRemove);
|
let guidToRemove = await engine._store.GUIDForUri(uriToRemove.spec);
|
||||||
|
|
||||||
await resetTracker();
|
await resetTracker();
|
||||||
await verifyTrackerEmpty();
|
await verifyTrackerEmpty();
|
||||||
|
@ -216,19 +216,19 @@ add_task(async function test_filter_hidden() {
|
||||||
|
|
||||||
_("Add visit; should be hidden by the redirect");
|
_("Add visit; should be hidden by the redirect");
|
||||||
let hiddenURI = await addVisit("hidden");
|
let hiddenURI = await addVisit("hidden");
|
||||||
let hiddenGUID = engine._store.GUIDForUri(hiddenURI);
|
let hiddenGUID = await engine._store.GUIDForUri(hiddenURI.spec);
|
||||||
_(`Hidden visit GUID: ${hiddenGUID}`);
|
_(`Hidden visit GUID: ${hiddenGUID}`);
|
||||||
|
|
||||||
_("Add redirect visit; should be tracked");
|
_("Add redirect visit; should be tracked");
|
||||||
let trackedURI = await addVisit("redirect", hiddenURI,
|
let trackedURI = await addVisit("redirect", hiddenURI.spec,
|
||||||
PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT);
|
PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT);
|
||||||
let trackedGUID = engine._store.GUIDForUri(trackedURI);
|
let trackedGUID = await engine._store.GUIDForUri(trackedURI.spec);
|
||||||
_(`Tracked visit GUID: ${trackedGUID}`);
|
_(`Tracked visit GUID: ${trackedGUID}`);
|
||||||
|
|
||||||
_("Add visit for framed link; should be ignored");
|
_("Add visit for framed link; should be ignored");
|
||||||
let embedURI = await addVisit("framed_link", null,
|
let embedURI = await addVisit("framed_link", null,
|
||||||
PlacesUtils.history.TRANSITION_FRAMED_LINK);
|
PlacesUtils.history.TRANSITION_FRAMED_LINK);
|
||||||
let embedGUID = engine._store.GUIDForUri(embedURI);
|
let embedGUID = await engine._store.GUIDForUri(embedURI.spec);
|
||||||
_(`Framed link visit GUID: ${embedGUID}`);
|
_(`Framed link visit GUID: ${embedGUID}`);
|
||||||
|
|
||||||
_("Run Places maintenance to mark redirect visit as hidden");
|
_("Run Places maintenance to mark redirect visit as hidden");
|
||||||
|
|
|
@ -29,6 +29,7 @@ var PlacesSyncUtils = {};
|
||||||
const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
|
const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
|
||||||
|
|
||||||
const MICROSECONDS_PER_SECOND = 1000000;
|
const MICROSECONDS_PER_SECOND = 1000000;
|
||||||
|
const SQLITE_MAX_VARIABLE_NUMBER = 999;
|
||||||
|
|
||||||
const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
|
const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
|
||||||
const ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE = "AllBookmarks";
|
const ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE = "AllBookmarks";
|
||||||
|
@ -59,7 +60,27 @@ XPCOMUtils.defineLazyGetter(this, "ROOTS", () =>
|
||||||
Object.keys(ROOT_SYNC_ID_TO_GUID)
|
Object.keys(ROOT_SYNC_ID_TO_GUID)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auxiliary generator function that yields an array in chunks
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param chunkLength
|
||||||
|
* @yields {Array} New Array with the next chunkLength elements of array. If the array has less than chunkLength elements, yields all of them
|
||||||
|
*/
|
||||||
|
function* chunkArray(array, chunkLength) {
|
||||||
|
let startIndex = 0;
|
||||||
|
while (startIndex < array.length) {
|
||||||
|
yield array.slice(startIndex, startIndex += chunkLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const HistorySyncUtils = PlacesSyncUtils.history = Object.freeze({
|
const HistorySyncUtils = PlacesSyncUtils.history = Object.freeze({
|
||||||
|
/**
|
||||||
|
* Fetches the frecency for the URL provided
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @returns {Number} The frecency of the given url
|
||||||
|
*/
|
||||||
async fetchURLFrecency(url) {
|
async fetchURLFrecency(url) {
|
||||||
let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
|
let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
|
||||||
|
|
||||||
|
@ -71,8 +92,156 @@ const HistorySyncUtils = PlacesSyncUtils.history = Object.freeze({
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
{ url: canonicalURL.href }
|
{ url: canonicalURL.href }
|
||||||
);
|
);
|
||||||
|
|
||||||
return rows.length ? rows[0].getResultByName("frecency") : -1;
|
return rows.length ? rows[0].getResultByName("frecency") : -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters syncable places from a collection of places guids.
|
||||||
|
*
|
||||||
|
* @param guids
|
||||||
|
*
|
||||||
|
* @returns {Array} new Array with the guids that aren't syncable
|
||||||
|
*/
|
||||||
|
async determineNonSyncableGuids(guids) {
|
||||||
|
// Filter out hidden pages and `TRANSITION_FRAMED_LINK` visits. These are
|
||||||
|
// excluded when rendering the history menu, so we use the same constraints
|
||||||
|
// for Sync. We also don't want to sync `TRANSITION_EMBED` visits, but those
|
||||||
|
// aren't stored in the database.
|
||||||
|
let db = await PlacesUtils.promiseDBConnection();
|
||||||
|
let nonSyncableGuids = [];
|
||||||
|
for (let chunk of chunkArray(guids, SQLITE_MAX_VARIABLE_NUMBER)) {
|
||||||
|
let rows = await db.execute(`
|
||||||
|
SELECT DISTINCT p.guid FROM moz_places p
|
||||||
|
JOIN moz_historyvisits v ON p.id = v.place_id
|
||||||
|
WHERE p.guid IN (${new Array(chunk.length).fill("?").join(",")}) AND
|
||||||
|
(p.hidden = 1 OR v.visit_type IN (0,
|
||||||
|
${PlacesUtils.history.TRANSITION_FRAMED_LINK}))
|
||||||
|
`, chunk);
|
||||||
|
nonSyncableGuids = nonSyncableGuids.concat(rows.map(row => row.getResultByName("guid")));
|
||||||
|
}
|
||||||
|
return nonSyncableGuids;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the guid of the given uri
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @param guid
|
||||||
|
*/
|
||||||
|
changeGuid(uri, guid) {
|
||||||
|
let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(uri);
|
||||||
|
let validatedGuid = PlacesUtils.BOOKMARK_VALIDATORS.guid(guid);
|
||||||
|
return PlacesUtils.withConnectionWrapper("HistorySyncUtils: changeGuid",
|
||||||
|
async function(db) {
|
||||||
|
await db.executeCached(`
|
||||||
|
UPDATE moz_places
|
||||||
|
SET guid = :guid
|
||||||
|
WHERE url_hash = hash(:page_url) AND url = :page_url`,
|
||||||
|
{guid: validatedGuid, page_url: canonicalURL.href});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the last 20 visits (date and type of it) corresponding to a given url
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @returns {Array} Each element of the Array is an object with members: date and type
|
||||||
|
*/
|
||||||
|
async fetchVisitsForURL(url) {
|
||||||
|
let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
|
||||||
|
let db = await PlacesUtils.promiseDBConnection();
|
||||||
|
let rows = await db.executeCached(`
|
||||||
|
SELECT visit_type type, visit_date date
|
||||||
|
FROM moz_historyvisits
|
||||||
|
JOIN moz_places h ON h.id = place_id
|
||||||
|
WHERE url_hash = hash(:url) AND url = :url
|
||||||
|
ORDER BY date DESC LIMIT 20`, { url: canonicalURL.href }
|
||||||
|
);
|
||||||
|
return rows.map(row => {
|
||||||
|
let visitDate = row.getResultByName("date");
|
||||||
|
let visitType = row.getResultByName("type");
|
||||||
|
return { date: visitDate, type: visitType };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the guid of a uri
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @returns {String} The guid of the given uri
|
||||||
|
*/
|
||||||
|
async fetchGuidForURL(url) {
|
||||||
|
let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
|
||||||
|
let db = await PlacesUtils.promiseDBConnection();
|
||||||
|
let rows = await db.executeCached(`
|
||||||
|
SELECT guid
|
||||||
|
FROM moz_places
|
||||||
|
WHERE url_hash = hash(:page_url) AND url = :page_url`,
|
||||||
|
{ page_url: canonicalURL.href }
|
||||||
|
);
|
||||||
|
if (rows.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rows[0].getResultByName("guid");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch information about a guid (url, title and frecency)
|
||||||
|
*
|
||||||
|
* @param guid
|
||||||
|
* @returns {Object} Object with three members: url, title and frecency of the given guid
|
||||||
|
*/
|
||||||
|
async fetchURLInfoForGuid(guid) {
|
||||||
|
let db = await PlacesUtils.promiseDBConnection();
|
||||||
|
let rows = await db.executeCached(`
|
||||||
|
SELECT url, title, frecency
|
||||||
|
FROM moz_places
|
||||||
|
WHERE guid = :guid`,
|
||||||
|
{ guid }
|
||||||
|
);
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: rows[0].getResultByName("url"),
|
||||||
|
title: rows[0].getResultByName("title"),
|
||||||
|
frecency: rows[0].getResultByName("frecency"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all URLs filtered by the limit and since members of the options object.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* Options object with two members, since and limit. Both of them must be provided
|
||||||
|
* @returns {Array} - Up to limit number of URLs starting from the date provided by since
|
||||||
|
*/
|
||||||
|
async getAllURLs(options) {
|
||||||
|
// Check that the limit property is finite number.
|
||||||
|
if (!Number.isFinite(options.limit)) {
|
||||||
|
throw new Error("The number provided in options.limit is not finite.");
|
||||||
|
}
|
||||||
|
// Check that the since property is of type Date.
|
||||||
|
if (!options.since || Object.prototype.toString.call(options.since) != "[object Date]") {
|
||||||
|
throw new Error("The property since of the options object must be of type Date.");
|
||||||
|
}
|
||||||
|
let db = await PlacesUtils.promiseDBConnection();
|
||||||
|
let sinceInMicroseconds = PlacesUtils.toPRTime(options.since);
|
||||||
|
let rows = await db.executeCached(`
|
||||||
|
SELECT DISTINCT p.url
|
||||||
|
FROM moz_places p
|
||||||
|
JOIN moz_historyvisits v ON p.id = v.place_id
|
||||||
|
WHERE p.last_visit_date > :cutoff_date AND
|
||||||
|
p.hidden = 0 AND
|
||||||
|
v.visit_type NOT IN (0,
|
||||||
|
${PlacesUtils.history.TRANSITION_FRAMED_LINK})
|
||||||
|
ORDER BY frecency DESC
|
||||||
|
LIMIT :max_results`,
|
||||||
|
{ cutoff_date: sinceInMicroseconds, max_results: options.limit }
|
||||||
|
);
|
||||||
|
return rows.map(row => row.getResultByName("url"));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
|
const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
|
||||||
|
|
|
@ -170,15 +170,188 @@ add_task(async function test_fetchURLFrecency() {
|
||||||
}
|
}
|
||||||
for (let url of arrayOfURLsToVisit) {
|
for (let url of arrayOfURLsToVisit) {
|
||||||
let frecency = await PlacesSyncUtils.history.fetchURLFrecency(url);
|
let frecency = await PlacesSyncUtils.history.fetchURLFrecency(url);
|
||||||
equal(typeof frecency, "number");
|
equal(typeof frecency, "number", "The frecency should be of type: number");
|
||||||
notEqual(frecency, -1);
|
notEqual(frecency, -1, "The frecency of this url should be different than -1");
|
||||||
}
|
}
|
||||||
// Do not add visits to the following URLs, and then check if frecency for those URLs is -1.
|
// Do not add visits to the following URLs, and then check if frecency for those URLs is -1.
|
||||||
let arrayOfURLsNotVisited = ["https://bugzilla.org", "https://example.org"];
|
let arrayOfURLsNotVisited = ["https://bugzilla.org", "https://example.org"];
|
||||||
for (let url of arrayOfURLsNotVisited) {
|
for (let url of arrayOfURLsNotVisited) {
|
||||||
let frecency = await PlacesSyncUtils.history.fetchURLFrecency(url);
|
let frecency = await PlacesSyncUtils.history.fetchURLFrecency(url);
|
||||||
equal(frecency, -1);
|
equal(frecency, -1, "The frecency of this url should be -1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_determineNonSyncableGuids() {
|
||||||
|
// Add visits to the following URLs with different transition types.
|
||||||
|
let arrayOfVisits = [{ uri: "https://www.mozilla.org/en-US/", transition: TRANSITION_TYPED },
|
||||||
|
{ uri: "http://getfirefox.com/", transition: TRANSITION_LINK },
|
||||||
|
{ uri: "http://getthunderbird.com/", transition: TRANSITION_FRAMED_LINK }];
|
||||||
|
for (let visit of arrayOfVisits) {
|
||||||
|
await PlacesTestUtils.addVisits(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the guid for each visit.
|
||||||
|
let guids = [];
|
||||||
|
let dictURLGuid = {};
|
||||||
|
for (let visit of arrayOfVisits) {
|
||||||
|
let guid = await PlacesSyncUtils.history.fetchGuidForURL(visit.uri);
|
||||||
|
guids.push(guid);
|
||||||
|
dictURLGuid[visit.uri] = guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the visits.
|
||||||
|
let filteredGuids = await PlacesSyncUtils.history.determineNonSyncableGuids(guids);
|
||||||
|
|
||||||
|
// Check if the filtered visits are of type TRANSITION_FRAMED_LINK.
|
||||||
|
for (let visit of arrayOfVisits) {
|
||||||
|
if (visit.transition === TRANSITION_FRAMED_LINK) {
|
||||||
|
ok(filteredGuids.includes(dictURLGuid[visit.uri]), "This url should be one of the filtered guids.");
|
||||||
|
} else {
|
||||||
|
ok(!filteredGuids.includes(dictURLGuid[visit.uri]), "This url should not be one of the filtered guids.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_changeGuid() {
|
||||||
|
// Add some visits of the following URLs.
|
||||||
|
let arrayOfURLsToVisit = ["https://www.mozilla.org/en-US/", "http://getfirefox.com/", "http://getthunderbird.com/"];
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
await PlacesTestUtils.addVisits(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
let originalGuid = await PlacesSyncUtils.history.fetchGuidForURL(url);
|
||||||
|
let newGuid = makeGuid();
|
||||||
|
|
||||||
|
// Change the original GUID for the new GUID.
|
||||||
|
await PlacesSyncUtils.history.changeGuid(url, newGuid);
|
||||||
|
|
||||||
|
// Fetch the GUID for this URL.
|
||||||
|
let newGuidFetched = await PlacesSyncUtils.history.fetchGuidForURL(url);
|
||||||
|
|
||||||
|
// Check that the URL has the new GUID as its GUID and not the original one.
|
||||||
|
equal(newGuid, newGuidFetched, "These should be equal since we changed the guid for the visit.");
|
||||||
|
notEqual(originalGuid, newGuidFetched, "These should be different since we changed the guid for the visit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_fetchVisitsForURL() {
|
||||||
|
// Get the date for this moment and a date for a minute ago.
|
||||||
|
let now = new Date();
|
||||||
|
let aMinuteAgo = new Date(now.getTime() - (1 * 60000));
|
||||||
|
|
||||||
|
// Add some visits of the following URLs, specifying the transition and the visit date.
|
||||||
|
let arrayOfVisits = [{ uri: "https://www.mozilla.org/en-US/", transition: TRANSITION_TYPED, visitDate: aMinuteAgo },
|
||||||
|
{ uri: "http://getfirefox.com/", transition: TRANSITION_LINK, visitDate: aMinuteAgo },
|
||||||
|
{ uri: "http://getthunderbird.com/", transition: TRANSITION_LINK, visitDate: aMinuteAgo }];
|
||||||
|
for (let elem of arrayOfVisits) {
|
||||||
|
await PlacesTestUtils.addVisits(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let elem of arrayOfVisits) {
|
||||||
|
// Fetch all the visits for this URL.
|
||||||
|
let visits = await PlacesSyncUtils.history.fetchVisitsForURL(elem.uri);
|
||||||
|
// Since the visit we added will be the last one in the collection of visits, we get the index of it.
|
||||||
|
let iLast = visits.length - 1;
|
||||||
|
|
||||||
|
// The date is saved in _micro_seconds, here we change it to milliseconds.
|
||||||
|
let dateInMilliseconds = visits[iLast].date * 0.001;
|
||||||
|
|
||||||
|
// Check that the info we provided for this URL is the same one retrieved.
|
||||||
|
equal(dateInMilliseconds, elem.visitDate.getTime(), "The date we provided should be the same we retrieved.");
|
||||||
|
equal(visits[iLast].type, elem.transition, "The transition type we provided should be the same we retrieved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_fetchGuidForURL() {
|
||||||
|
// Add some visits of the following URLs.
|
||||||
|
let arrayOfURLsToVisit = ["https://www.mozilla.org/en-US/", "http://getfirefox.com/", "http://getthunderbird.com/"];
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
await PlacesTestUtils.addVisits(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tries to test fetchGuidForURL in two ways:
|
||||||
|
// 1- By fetching the GUID, and then using that GUID to retrieve the info of the visit.
|
||||||
|
// It then compares the URL with the URL that is on the visits info.
|
||||||
|
// 2- By creating a new GUID, changing the GUID for the visit, fetching the GUID and comparing them.
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
let guid = await PlacesSyncUtils.history.fetchGuidForURL(url)
|
||||||
|
let info = await PlacesSyncUtils.history.fetchURLInfoForGuid(guid);
|
||||||
|
|
||||||
|
let newGuid = makeGuid();
|
||||||
|
await PlacesSyncUtils.history.changeGuid(url, newGuid);
|
||||||
|
let newGuid2 = await PlacesSyncUtils.history.fetchGuidForURL(url);
|
||||||
|
|
||||||
|
equal(url, info.url, "The url provided and the url retrieved should be the same.");
|
||||||
|
equal(newGuid, newGuid2, "The changed guid and the retrieved guid should be the same.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_fetchURLInfoForGuid() {
|
||||||
|
// Add some visits of the following URLs. specifying the title.
|
||||||
|
let visits = [{ uri: "https://www.mozilla.org/en-US/", title: "mozilla" },
|
||||||
|
{ uri: "http://getfirefox.com/", title: "firefox" },
|
||||||
|
{ uri: "http://getthunderbird.com/", title: "thunderbird" }];
|
||||||
|
for (let visit of visits) {
|
||||||
|
await PlacesTestUtils.addVisits(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let visit of visits) {
|
||||||
|
let guid = await PlacesSyncUtils.history.fetchGuidForURL(visit.uri);
|
||||||
|
let info = await PlacesSyncUtils.history.fetchURLInfoForGuid(guid);
|
||||||
|
|
||||||
|
// Compare the info returned by fetchURLInfoForGuid,
|
||||||
|
// URL and title should match while frecency must be different than -1.
|
||||||
|
equal(info.url, visit.uri, "The url provided should be the same as the url retrieved.");
|
||||||
|
equal(info.title, visit.title, "The title provided should be the same as the title retrieved.");
|
||||||
|
notEqual(info.frecency, -1, "The frecency of the visit should be different than -1.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a "fake" GUID and check that the result of fetchURLInfoForGuid is null.
|
||||||
|
let guid = makeGuid();
|
||||||
|
let info = await PlacesSyncUtils.history.fetchURLInfoForGuid(guid);
|
||||||
|
|
||||||
|
equal(info, null, "The information object of a non-existent guid should be null.");
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_getAllURLs() {
|
||||||
|
// Add some visits of the following URLs.
|
||||||
|
let arrayOfURLsToVisit = ["https://www.mozilla.org/en-US/", "http://getfirefox.com/", "http://getthunderbird.com/"];
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
await PlacesTestUtils.addVisits(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all URLs.
|
||||||
|
let allURLs = await PlacesSyncUtils.history.getAllURLs({ since: new Date(Date.now() - 2592000000), limit: 5000 });
|
||||||
|
|
||||||
|
// The amount of URLs must be the same in both collections.
|
||||||
|
equal(allURLs.length, arrayOfURLsToVisit.length, "The amount of urls retrived should match the amount of urls provided.");
|
||||||
|
|
||||||
|
// Check that the correct URLs were retrived.
|
||||||
|
for (let url of arrayOfURLsToVisit) {
|
||||||
|
ok(allURLs.includes(url), "The urls retrieved should match the ones used in this test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the visits added during this test.
|
||||||
|
await PlacesTestUtils.clearHistory();
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_order() {
|
add_task(async function test_order() {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче