Bug 1356567 - root icons should still create a page association if the domain differs. r=adw, kitcambridge
Root domain icons are no more associated with their pages, BUT if the page uses
a root domain icon from another domain, it should still get an association with it
or we couldn't relate the two.
This also fixes an overlooked problem in PlacesTestUtils where Date objects
cross a boundary and fail instanceof checks. This causes failures in the same
test that this patch is modifying.
To protect from future similar issues some protection has been added to updatedPlaces
so that it will crash in debug builds.
MozReview-Commit-ID: 3MTKhGj3ehj
--- a/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
+++ b/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
@@ -29,21 +29,21 @@ do_get_profile();
function run_test() {
PlacesProvider.links.init();
run_next_test();
}
// url prefix for test history population
const TEST_URL = "https://mozilla.com/";
// time when the test starts execution
-const TIME_NOW = (new Date()).getTime();
+const TIME_NOW = new Date();
// utility function to compute past timestap
function timeDaysAgo(numDays) {
- return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
+ return new Date(TIME_NOW - (numDays * 24 * 60 * 60 * 1000));
}
// utility function to make a visit for insetion into places db
function makeVisit(index, daysAgo, isTyped, domain = TEST_URL) {
let {
TRANSITION_TYPED,
TRANSITION_LINK
} = PlacesUtils.history;
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -357,17 +357,17 @@ HistoryStore.prototype = {
curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
}
// Walk through the visits, make sure we have sound data, and eliminate
// dupes. The latter is done by rewriting the array in-place.
for (i = 0, k = 0; i < record.visits.length; i++) {
let visit = record.visits[k] = record.visits[i];
- if (!visit.date || typeof visit.date != "number") {
+ if (!visit.date || typeof visit.date != "number" || !Number.isInteger(visit.date)) {
this._log.warn("Encountered record with invalid visit date: "
+ visit.date);
continue;
}
if (!visit.type ||
!Object.values(PlacesUtils.history.TRANSITIONS).includes(visit.type)) {
this._log.warn("Encountered record with invalid visit type: " +
--- a/services/sync/tests/unit/test_history_store.js
+++ b/services/sync/tests/unit/test_history_store.js
@@ -1,12 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://testing-common/PlacesTestUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/engines/history.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
const TIMESTAMP1 = (Date.now() - 103406528) * 1000;
const TIMESTAMP2 = (Date.now() - 6592903) * 1000;
@@ -83,33 +84,19 @@ function run_test() {
add_test(function test_store() {
_("Verify that we've got an empty store to work with.");
do_check_empty(store.getAllIDs());
_("Let's create an entry in the database.");
fxuri = Utils.makeURI("http://getfirefox.com/");
- let place = {
- uri: fxuri,
- title: "Get Firefox!",
- visits: [{
- visitDate: TIMESTAMP1,
- transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
- }]
- };
- PlacesUtils.asyncHistory.updatePlaces(place, {
- handleError: function handleError() {
- do_throw("Unexpected error in adding visit.");
- },
- handleResult: function handleResult() {},
- handleCompletion: onVisitAdded
- });
-
- function onVisitAdded() {
+ PlacesTestUtils.addVisits({ uri: fxuri, title: "Get Firefox!",
+ visitDate: TIMESTAMP1 })
+ .then(() => {
_("Verify that the entry exists.");
let ids = Object.keys(store.getAllIDs());
do_check_eq(ids.length, 1);
fxguid = ids[0];
do_check_true(store.itemExists(fxguid));
_("If we query a non-existent record, it's marked as deleted.");
let record = store.createRecord("non-existent");
@@ -136,17 +123,17 @@ add_test(function test_store() {
run_next_test();
}));
applyEnsureNoFailures([
{id: fxguid,
histUri: record.histUri,
title: "Hol Dir Firefox!",
visits: [record.visits[0], secondvisit]}
]);
- }
+ });
});
add_test(function test_store_create() {
_("Create a brand new record through the store.");
tbguid = Utils.makeGUID();
tburi = Utils.makeURI("http://getthunderbird.com");
onNextVisit(ensureThrows(function() {
do_check_attribute_count(store.getAllIDs(), 2);
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -64,17 +64,17 @@ FetchPageInfo(const RefPtr<Database>& aD
"LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
"AND dest.visit_type IN (%d, %d) "
"LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
"AND parent.visit_type IN (%d, %d) "
"WHERE dest.place_id = h.id "
"AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
"LIMIT 1 "
") "
- ") "
+ "), fixup_url(get_unreversed_host(h.rev_host)) AS host "
"FROM moz_places h "
"LEFT JOIN moz_pages_w_icons pi ON page_url_hash = hash(:page_url) AND page_url = :page_url "
"WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
);
@@ -106,16 +106,21 @@ FetchPageInfo(const RefPtr<Database>& aD
rv = stmt->GetIsNull(3, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
// The page could not be bookmarked.
if (!isNull) {
rv = stmt->GetUTF8String(3, _page.bookmarkedSpec);
NS_ENSURE_SUCCESS(rv, rv);
}
+ if (_page.host.IsEmpty()) {
+ rv = stmt->GetUTF8String(4, _page.host);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
if (!_page.canAddToHistory) {
// Either history is disabled or the scheme is not supported. In such a
// case we want to update the icon only if the page is bookmarked.
if (_page.bookmarkedSpec.IsEmpty()) {
// The page is not bookmarked. Since updating the icon with a disabled
// history would be a privacy leak, bail out as if the page did not exist.
return NS_ERROR_NOT_AVAILABLE;
@@ -831,17 +836,19 @@ AsyncAssociateIconToPage::Run()
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// Don't associate pages to root domain icons, since those will be returned
// regardless. This saves a lot of work and database space since we don't
// need to store urls and relations.
- if (!mIcon.rootIcon) {
+ // Though, this is possible only if both the page and the icon have the same
+ // host, otherwise we couldn't relate them.
+ if (!mIcon.rootIcon || !mIcon.host.Equals(mPage.host)) {
// The page may have associated payloads already, and those could have to be
// expired. For example at a certain point a page could decide to stop serving
// its usual 16px and 32px pngs, and use an svg instead.
// On the other side, we could also be in the process of adding more payloads
// to this page, and we should not expire the payloads we just added.
// For this, we use the expiration field as an indicator and remove relations
// based on it being elapsed. We don't remove orphan icons at this time since
// it would have a cost. The privacy hit is limited since history removal
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -87,16 +87,17 @@ struct IconData
: expiration(0)
, fetchMode(FETCH_NEVER)
, status(ICON_STATUS_UNKNOWN)
, rootIcon(0)
{
}
nsCString spec;
+ nsCString host;
PRTime expiration;
enum AsyncFaviconFetchMode fetchMode;
uint16_t status; // This is a bitset, see ICON_STATUS_* defines above.
uint8_t rootIcon;
nsTArray<IconPayload> payloads;
};
/**
@@ -110,18 +111,18 @@ struct PageData
, canAddToHistory(true)
{
guid.SetIsVoid(true);
}
int64_t id; // This is the moz_pages_w_icons id.
int64_t placeId; // This is the moz_places page id.
nsCString spec;
+ nsCString host;
nsCString bookmarkedSpec;
- nsString revHost;
bool canAddToHistory; // False for disabled history and unsupported schemas.
nsCString guid;
};
/**
* Info for a frame.
*/
struct FrameData
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -2972,16 +2972,29 @@ History::UpdatePlaces(JS::Handle<JS::Val
VisitData& data = *visitData.AppendElement(VisitData(uri));
data.title = title;
data.guid = guid;
// We must have a date and a transaction type!
rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
NS_ENSURE_SUCCESS(rv, rv);
+ // visitDate should be in microseconds. It's easy to do the wrong thing
+ // and pass milliseconds to updatePlaces, so we lazily check for that.
+ // While it's not easily distinguishable, since both are integers, we can
+ // check if the value is very far in the past, and assume it's probably
+ // a mistake.
+ if (data.visitTime < (PR_Now() / 1000)) {
+#ifdef DEBUG
+ nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
+ Unused << xpc->DebugDumpJSStack(false, false, false);
+ MOZ_CRASH("invalid time format passed to updatePlaces");
+#endif
+ return NS_ERROR_INVALID_ARG;
+ }
uint32_t transitionType = 0;
rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_RANGE(transitionType,
nsINavHistoryService::TRANSITION_LINK,
nsINavHistoryService::TRANSITION_RELOAD);
data.SetTransitionType(transitionType);
data.hidden = GetHiddenState(false, transitionType);
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -334,18 +334,21 @@ nsFaviconService::SetAndFetchFaviconForP
NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
bool loadPrivate = aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
// Build page data.
PageData page;
rv = aPageURI->GetSpec(page.spec);
NS_ENSURE_SUCCESS(rv, rv);
- // URIs can arguably miss a host.
- Unused << GetReversedHostname(aPageURI, page.revHost);
+ // URIs can arguably lack a host.
+ Unused << aPageURI->GetHost(page.host);
+ if (StringBeginsWith(page.host, NS_LITERAL_CSTRING("www."))) {
+ page.host.Cut(0, 4);
+ }
bool canAddToHistory;
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
NS_ENSURE_SUCCESS(rv, rv);
page.canAddToHistory = !!canAddToHistory && !loadPrivate;
// Build icon data.
@@ -354,16 +357,21 @@ nsFaviconService::SetAndFetchFaviconForP
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
if (iconKey) {
icon = iconKey->iconData;
mUnassociatedIcons.RemoveEntry(iconKey);
} else {
icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
rv = aFaviconURI->GetSpec(icon.spec);
NS_ENSURE_SUCCESS(rv, rv);
+ // URIs can arguably lack a host.
+ Unused << aFaviconURI->GetHost(icon.host);
+ if (StringBeginsWith(icon.host, NS_LITERAL_CSTRING("www."))) {
+ icon.host.Cut(0, 4);
+ }
nsAutoCString path;
rv = aFaviconURI->GetPath(path);
if (NS_SUCCEEDED(rv) && path.EqualsLiteral("/favicon.ico")) {
icon.rootIcon = 1;
}
}
// If the page url points to an image, the icon's url will be the same.
@@ -432,16 +440,21 @@ nsFaviconService::ReplaceFaviconData(nsI
iconData->fetchMode = FETCH_NEVER;
nsresult rv = aFaviconURI->GetSpec(iconData->spec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString path;
rv = aFaviconURI->GetPath(path);
if (NS_SUCCEEDED(rv) && path.EqualsLiteral("/favicon.ico")) {
iconData->rootIcon = 1;
}
+ // URIs can arguably lack a host.
+ Unused << aFaviconURI->GetHost(iconData->host);
+ if (StringBeginsWith(iconData->host, NS_LITERAL_CSTRING("www."))) {
+ iconData->host.Cut(0, 4);
+ }
IconPayload payload;
payload.mimeType = aMimeType;
payload.data.Assign(TO_CHARBUFFER(aData), aDataLen);
if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
payload.width = UINT16_MAX;
}
// There may already be a previous payload, so ensure to only have one.
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -58,17 +58,25 @@ this.PlacesTestUtils = Object.freeze({
info.title = (typeof place.title === "string") ? place.title : "test visit for " + info.url.spec ;
if (typeof place.referrer == "string") {
place.referrer = NetUtil.newURI(place.referrer);
} else if (place.referrer && place.referrer instanceof URL) {
place.referrer = NetUtil.newURI(place.referrer.href);
}
let visitDate = place.visitDate;
if (visitDate) {
- if (!(visitDate instanceof Date)) {
+ if (visitDate.constructor.name != "Date") {
+ // visitDate should be in microseconds. It's easy to do the wrong thing
+ // and pass milliseconds to updatePlaces, so we lazily check for that.
+ // While it's not easily distinguishable, since both are integers, we
+ // can check if the value is very far in the past, and assume it's
+ // probably a mistake.
+ if (visitDate <= Date.now()) {
+ throw new Error("AddVisits expects a Date object or _micro_seconds!");
+ }
visitDate = PlacesUtils.toDate(visitDate);
}
} else {
visitDate = new Date();
}
info.visits = [{
transition: place.transition,
date: visitDate,
--- a/toolkit/components/places/tests/favicons/test_root_icons.js
+++ b/toolkit/components/places/tests/favicons/test_root_icons.js
@@ -41,17 +41,17 @@ add_task(function* () {
rows = yield db.execute("SELECT * FROM moz_icons");
Assert.equal(rows.length, 0, "The icon should have been removed");
});
add_task(function* test_removePagesByTimeframe() {
// Add a visit in the past with no directly associated icon.
yield PlacesTestUtils.addVisits({uri: "http://www.places.test/old/", visitDate: new Date(Date.now() - 86400000)});
let pageURI = NetUtil.newURI("http://www.places.test/page/");
- yield PlacesTestUtils.addVisits(pageURI);
+ yield PlacesTestUtils.addVisits({uri: pageURI, visitDate: new Date(Date.now() - 7200000)});
let faviconURI = NetUtil.newURI("http://www.places.test/page/favicon.ico");
let rootIconURI = NetUtil.newURI("http://www.places.test/favicon.ico");
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI, SMALLSVG_DATA_URI.spec, 0, Services.scriptSecurityManager.getSystemPrincipal());
yield setFaviconForPage(pageURI, faviconURI);
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
rootIconURI, SMALLPNG_DATA_URI.spec, 0, Services.scriptSecurityManager.getSystemPrincipal());
yield setFaviconForPage(pageURI, rootIconURI);
@@ -60,17 +60,17 @@ add_task(function* test_removePagesByTim
Assert.equal(yield getFaviconUrlForPage(pageURI),
faviconURI.spec, "Should get the biggest icon");
Assert.equal(yield getFaviconUrlForPage(pageURI, 1),
rootIconURI.spec, "Should get the smallest icon");
Assert.equal(yield getFaviconUrlForPage("http://www.places.test/old/"),
rootIconURI.spec, "Should get the root icon");
PlacesUtils.history.removePagesByTimeframe(
- PlacesUtils.toPRTime(Date.now() - 20000),
+ PlacesUtils.toPRTime(Date.now() - 14400000),
PlacesUtils.toPRTime(new Date())
);
// Check database entries.
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute("SELECT * FROM moz_icons");
Assert.equal(rows.length, 1, "There should only be 1 icon entry");
Assert.equal(rows[0].getResultByName("root"), 1, "It should be marked as a root icon");
@@ -78,8 +78,20 @@ add_task(function* test_removePagesByTim
Assert.equal(rows.length, 0, "There should be no page entry");
rows = yield db.execute("SELECT * FROM moz_icons_to_pages");
Assert.equal(rows.length, 0, "There should be no relation entry");
PlacesUtils.history.removePagesByTimeframe(0, PlacesUtils.toPRTime(new Date()));
rows = yield db.execute("SELECT * FROM moz_icons");
Assert.equal(rows.length, 0, "There should be no icon entry");
});
+
+add_task(function* test_different_host() {
+ let pageURI = NetUtil.newURI("http://places.test/page/");
+ yield PlacesTestUtils.addVisits(pageURI);
+ let faviconURI = NetUtil.newURI("http://mozilla.test/favicon.ico");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI, SMALLPNG_DATA_URI.spec, 0, Services.scriptSecurityManager.getSystemPrincipal());
+ yield setFaviconForPage(pageURI, faviconURI);
+
+ Assert.equal(yield getFaviconUrlForPage(pageURI),
+ faviconURI.spec, "Should get the png icon");
+});
--- a/toolkit/components/places/tests/unit/test_486978_sort_by_date_queries.js
+++ b/toolkit/components/places/tests/unit/test_486978_sort_by_date_queries.js
@@ -21,30 +21,24 @@ var pages = [
"http://a.mozilla.org/3/",
"http://a.mozilla.org/4/",
"http://b.mozilla.org/5/",
"http://b.mozilla.org/6/",
"http://b.mozilla.org/7/",
"http://b.mozilla.org/8/",
];
-function run_test() {
- run_next_test();
-}
-
add_task(function* test_initialize() {
- var noon = new Date();
- noon.setHours(12);
-
// Add visits.
+ let now = new Date();
for (let pageIndex = 0; pageIndex < pages.length; ++pageIndex) {
let page = pages[pageIndex];
yield PlacesTestUtils.addVisits({
uri: uri(page),
- visitDate: noon - (pages.length - pageIndex) * 1000
+ visitDate: new Date(now - (pages.length - pageIndex))
});
}
});
/**
* Tests that sorting date query by none will sort by title asc.
*/
add_task(function() {