--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -213,28 +213,24 @@ const CustomizableWidgets = [
win.openUILink(item.getAttribute("targetURI"), aItemCommandEvent);
CustomizableUI.hidePanelForNode(item);
};
let fragment = doc.createDocumentFragment();
let row;
while ((row = aResultSet.getNextRow())) {
let uri = row.getResultByIndex(1);
let title = row.getResultByIndex(2);
- let icon = row.getResultByIndex(6);
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
item.setAttribute("label", title || uri);
item.setAttribute("targetURI", uri);
item.setAttribute("class", "subviewbutton");
item.addEventListener("command", onItemCommand);
item.addEventListener("click", onItemCommand);
- if (icon) {
- let iconURL = "moz-anno:favicon:" + icon;
- item.setAttribute("image", iconURL);
- }
+ item.setAttribute("image", "page-icon:" + uri);
fragment.appendChild(item);
}
items.appendChild(fragment);
},
handleError(aError) {
log.debug("History view tried to show but had an error: " + aError);
},
handleCompletion(aReason) {
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -503,24 +503,20 @@ PlacesViewBase.prototype = {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// There's no UI representation for the root node, thus there's nothing to
// be done when the icon changes.
if (elt == this._rootElt)
return;
// Here we need the <menu>.
- if (elt.localName == "menupopup")
+ if (elt.localName == "menupopup") {
elt = elt.parentNode;
-
- let icon = aPlacesNode.icon;
- if (!icon)
- elt.removeAttribute("image");
- else if (icon != elt.getAttribute("image"))
- elt.setAttribute("image", icon);
+ }
+ elt.setAttribute("image", aPlacesNode.icon);
},
nodeAnnotationChanged:
function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// All livemarks have a feedURI, so use it as our indicator of a livemark
// being modified.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9598,17 +9598,17 @@ public:
, mNewURI(aNewURI)
, mLoadingPrincipal(aLoadingPrincipal)
, mInPrivateBrowsing(aInPrivateBrowsing)
{
}
NS_IMETHOD
OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
- const uint8_t* aData, const nsACString& aMimeType) override
+ const uint8_t* aData, const nsACString& aMimeType, uint16_t aWidth) override
{
// Continue only if there is an associated favicon.
if (!aFaviconURI) {
return NS_OK;
}
MOZ_ASSERT(aDataLen == 0,
"We weren't expecting the callback to deliver data.");
@@ -9657,17 +9657,17 @@ nsDocShell::CopyFavicon(nsIURI* aOldURI,
#ifdef MOZ_PLACES
nsCOMPtr<mozIAsyncFavicons> favSvc =
do_GetService("@mozilla.org/browser/favicon-service;1");
if (favSvc) {
nsCOMPtr<nsIFaviconDataCallback> callback =
new nsCopyFaviconCallback(favSvc, aNewURI,
aLoadingPrincipal,
aInPrivateBrowsing);
- favSvc->GetFaviconURLForPage(aOldURI, callback);
+ favSvc->GetFaviconURLForPage(aOldURI, callback, 0);
}
#endif
}
class InternalLoadEvent : public Runnable
{
public:
InternalLoadEvent(nsDocShell* aDocShell, nsIURI* aURI,
--- a/testing/firefox-ui/tests/functional/locationbar/manifest.ini
+++ b/testing/firefox-ui/tests/functional/locationbar/manifest.ini
@@ -1,9 +1,8 @@
[DEFAULT]
tags = local
[test_access_locationbar.py]
disabled = Bug 1168727 - Timeout when opening auto-complete popup
[test_escape_autocomplete.py]
-[test_favicon_in_autocomplete.py]
[test_suggest_bookmarks.py]
deleted file mode 100644
--- a/testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from firefox_puppeteer import PuppeteerMixin
-from marionette_driver import Wait
-from marionette_harness import MarionetteTestCase
-
-
-class TestFaviconInAutocomplete(PuppeteerMixin, MarionetteTestCase):
-
- PREF_SUGGEST_SEARCHES = 'browser.urlbar.suggest.searches'
- PREF_SUGGEST_BOOKMARK = 'browser.urlbar.suggest.bookmark'
-
- def setUp(self):
- super(TestFaviconInAutocomplete, self).setUp()
-
- # Disable suggestions for searches and bookmarks to get results only for history
- self.marionette.set_pref(self.PREF_SUGGEST_SEARCHES, False)
- self.marionette.set_pref(self.PREF_SUGGEST_BOOKMARK, False)
-
- self.puppeteer.places.remove_all_history()
-
- self.test_urls = [self.marionette.absolute_url('layout/mozilla.html')]
-
- self.test_string = 'mozilla'
- self.test_favicon = 'mozilla_favicon.ico'
-
- self.autocomplete_results = self.browser.navbar.locationbar.autocomplete_results
-
- def tearDown(self):
- try:
- self.autocomplete_results.close(force=True)
- self.marionette.clear_pref(self.PREF_SUGGEST_SEARCHES)
- self.marionette.clear_pref(self.PREF_SUGGEST_BOOKMARK)
- finally:
- super(TestFaviconInAutocomplete, self).tearDown()
-
- def test_favicon_in_autocomplete(self):
- # Open the test page
- def load_urls():
- with self.marionette.using_context('content'):
- self.marionette.navigate(self.test_urls[0])
- self.puppeteer.places.wait_for_visited(self.test_urls, load_urls)
-
- locationbar = self.browser.navbar.locationbar
-
- # Clear the location bar, type the test string, check that autocomplete list opens
- locationbar.clear()
- locationbar.urlbar.send_keys(self.test_string)
- self.assertEqual(locationbar.value, self.test_string)
- Wait(self.marionette).until(lambda _: self.autocomplete_results.is_complete)
-
- result = self.autocomplete_results.visible_results[1]
-
- result_icon = self.marionette.execute_script("""
- return arguments[0].image;
- """, script_args=[result])
-
- self.assertIn(self.test_favicon, result_icon)
-
- self.autocomplete_results.close()
--- a/toolkit/components/alerts/nsAlertsService.cpp
+++ b/toolkit/components/alerts/nsAlertsService.cpp
@@ -44,17 +44,17 @@ public:
nsIObserver* aAlertListener)
: mBackend(aBackend)
, mAlert(aAlert)
, mAlertListener(aAlertListener)
{}
NS_IMETHOD
OnComplete(nsIURI *aIconURI, uint32_t aIconSize, const uint8_t *aIconData,
- const nsACString &aMimeType) override
+ const nsACString &aMimeType, uint16_t aWidth) override
{
nsresult rv = NS_ERROR_FAILURE;
if (aIconSize > 0) {
nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend));
if (alertsIconData) {
rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener,
aIconSize, aIconData);
}
@@ -106,19 +106,19 @@ ShowWithIconBackend(nsIAlertsService* aB
nsCOMPtr<mozIAsyncFavicons> favicons(do_GetService(
"@mozilla.org/browser/favicon-service;1"));
NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE);
nsCOMPtr<nsIFaviconDataCallback> callback =
new IconCallback(aBackend, aAlert, aAlertListener);
if (alertsIconData) {
- return favicons->GetFaviconDataForPage(uri, callback);
+ return favicons->GetFaviconDataForPage(uri, callback, 0);
}
- return favicons->GetFaviconURLForPage(uri, callback);
+ return favicons->GetFaviconURLForPage(uri, callback, 0);
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif // !MOZ_PLACES
}
nsresult
ShowWithBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
nsIObserver* aAlertListener, const nsAString& aPersistentData)
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -18,63 +18,65 @@
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "nsIPrivateBrowsingChannel.h"
#include "nsISupportsPriority.h"
#include "nsContentUtils.h"
#include <algorithm>
+#include <deque>
#include "mozilla/gfx/2D.h"
#include "imgIContainer.h"
#include "ImageOps.h"
-#include "imgLoader.h"
#include "imgIEncoder.h"
using namespace mozilla::places;
using namespace mozilla::storage;
namespace mozilla {
namespace places {
namespace {
/**
- * Fetches information on a page from the Places database.
+ * Fetches information about a page from the database.
*
- * @param aDBConn
+ * @param aDB
* Database connection to history tables.
* @param _page
* Page that should be fetched.
*/
nsresult
FetchPageInfo(const RefPtr<Database>& aDB,
PageData& _page)
{
MOZ_ASSERT(_page.spec.Length(), "Must have a non-empty spec!");
MOZ_ASSERT(!NS_IsMainThread());
// This query finds the bookmarked uri we want to set the icon for,
// walking up to two redirect levels.
nsCString query = nsPrintfCString(
- "SELECT h.id, h.favicon_id, h.guid, ( "
+ "SELECT h.id, h.guid, ( "
"SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id "
"UNION ALL " // Union not directly bookmarked pages.
"SELECT url FROM moz_places WHERE id = ( "
"SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id "
"FROM moz_historyvisits dest "
"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 "
") "
- ") FROM moz_places h WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
+ ") "
+ "FROM moz_places h "
+ "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
);
nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
NS_ENSURE_STATE(stmt);
@@ -89,31 +91,25 @@ FetchPageInfo(const RefPtr<Database>& aD
NS_ENSURE_SUCCESS(rv, rv);
if (!hasResult) {
// The page does not exist.
return NS_ERROR_NOT_AVAILABLE;
}
rv = stmt->GetInt64(0, &_page.id);
NS_ENSURE_SUCCESS(rv, rv);
- bool isNull;
- rv = stmt->GetIsNull(1, &isNull);
+ rv = stmt->GetUTF8String(1, _page.guid);
NS_ENSURE_SUCCESS(rv, rv);
- // favicon_id can be nullptr.
- if (!isNull) {
- rv = stmt->GetInt64(1, &_page.iconId);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- rv = stmt->GetUTF8String(2, _page.guid);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = stmt->GetIsNull(3, &isNull);
+ // Bookmarked url can be nullptr.
+ bool isNull;
+ rv = stmt->GetIsNull(2, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
// The page could not be bookmarked.
if (!isNull) {
- rv = stmt->GetUTF8String(3, _page.bookmarkedSpec);
+ rv = stmt->GetUTF8String(2, _page.bookmarkedSpec);
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()) {
@@ -131,146 +127,283 @@ FetchPageInfo(const RefPtr<Database>& aD
}
}
}
return NS_OK;
}
/**
- * Stores information on a icon in the database.
+ * Stores information about an icon in the database.
*
- * @param aDBConn
+ * @param aDB
* Database connection to history tables.
* @param aIcon
* Icon that should be stored.
+ * @param aMustReplace
+ * If set to true, the function will bail out with NS_ERROR_NOT_AVAILABLE
+ * if it can't find a previous stored icon to replace.
+ * @note Should be wrapped in a transaction.
*/
nsresult
SetIconInfo(const RefPtr<Database>& aDB,
- const IconData& aIcon)
+ IconData& aIcon,
+ bool aMustReplace = false)
{
MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aIcon.payloads.Length() > 0);
+ MOZ_ASSERT(!aIcon.spec.IsEmpty());
- nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
- "INSERT OR REPLACE INTO moz_favicons "
- "(id, url, data, mime_type, expiration) "
- "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), "
- ":icon_url, :data, :mime_type, :expiration) "
+ // There are multiple cases possible at this point:
+ // 1. We must insert some payloads and no payloads exist in the table. This
+ // would be a straight INSERT.
+ // 2. The table contains the same number of payloads we are inserting. This
+ // would be a straight UPDATE.
+ // 3. The table contains more payloads than we are inserting. This would be
+ // an UPDATE and a DELETE.
+ // 4. The table contains less payloads than we are inserting. This would be
+ // an UPDATE and an INSERT.
+ // We can't just remove all the old entries and insert the new ones, cause
+ // we'd lose the referential integrity with pages. For the same reason we
+ // cannot use INSERT OR REPLACE, since it's implemented as DELETE AND INSERT.
+ // Thus, we follow this strategy:
+ // * SELECT all existing icon ids
+ // * For each payload, either UPDATE OR INSERT reusing icon ids.
+ // * If any previous icon ids is leftover, DELETE it.
+
+ nsCOMPtr<mozIStorageStatement> selectStmt = aDB->GetStatement(
+ "SELECT id FROM moz_icons "
+ "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) "
+ "AND icon_url = :url "
);
- NS_ENSURE_STATE(stmt);
- mozStorageStatementScoper scoper(stmt);
- nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aIcon.spec);
+ NS_ENSURE_STATE(selectStmt);
+ mozStorageStatementScoper scoper(selectStmt);
+ nsresult rv = URIBinder::Bind(selectStmt, NS_LITERAL_CSTRING("url"), aIcon.spec);
NS_ENSURE_SUCCESS(rv, rv);
- rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
- TO_INTBUFFER(aIcon.data), aIcon.data.Length());
- NS_ENSURE_SUCCESS(rv, rv);
- rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), aIcon.mimeType);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aIcon.expiration);
- NS_ENSURE_SUCCESS(rv, rv);
+ std::deque<int64_t> ids;
+ bool hasResult = false;
+ while (NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult) {
+ int64_t id = selectStmt->AsInt64(0);
+ MOZ_ASSERT(id > 0);
+ ids.push_back(id);
+ }
+ if (aMustReplace && ids.empty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<mozIStorageStatement> insertStmt = aDB->GetStatement(
+ "INSERT INTO moz_icons "
+ "(icon_url, fixed_icon_url_hash, width, expire_ms, data) "
+ "VALUES (:url, hash(fixup_url(:url)), :width, :expire, :data) "
+ );
+ NS_ENSURE_STATE(insertStmt);
+ nsCOMPtr<mozIStorageStatement> updateStmt = aDB->GetStatement(
+ "UPDATE moz_icons SET width = :width, "
+ "expire_ms = :expire, "
+ "data = :data "
+ "WHERE id = :id "
+ );
+ NS_ENSURE_STATE(updateStmt);
- rv = stmt->Execute();
- NS_ENSURE_SUCCESS(rv, rv);
+ for (auto& payload : aIcon.payloads) {
+ // Sanity checks.
+ MOZ_ASSERT(payload.mimeType.EqualsLiteral(PNG_MIME_TYPE) ||
+ payload.mimeType.EqualsLiteral(SVG_MIME_TYPE),
+ "Only png and svg payloads are supported");
+ MOZ_ASSERT(!payload.mimeType.EqualsLiteral(SVG_MIME_TYPE) ||
+ payload.width == UINT16_MAX,
+ "SVG payloads should have max width");
+ MOZ_ASSERT(payload.width > 0, "Payload should have a width");
+#ifdef DEBUG
+ // Done to ensure we fetch the id. See the MOZ_ASSERT below.
+ payload.id = 0;
+#endif
+ if (!ids.empty()) {
+ // Pop the first existing id for reuse.
+ int64_t id = ids.front();
+ ids.pop_front();
+ mozStorageStatementScoper scoper(updateStmt);
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindInt32ByName(NS_LITERAL_CSTRING("width"),
+ payload.width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("expire"),
+ aIcon.expiration / 1000);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
+ TO_INTBUFFER(payload.data),
+ payload.data.Length());
+ rv = updateStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Set the new payload id.
+ payload.id = id;
+ } else {
+ // Insert a new entry.
+ mozStorageStatementScoper scoper(insertStmt);
+ rv = URIBinder::Bind(insertStmt, NS_LITERAL_CSTRING("url"), aIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = insertStmt->BindInt32ByName(NS_LITERAL_CSTRING("width"),
+ payload.width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("expire"),
+ aIcon.expiration / 1000);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = insertStmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
+ TO_INTBUFFER(payload.data),
+ payload.data.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = insertStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Set the new payload id.
+ payload.id = nsFaviconService::sLastInsertedIconId;
+ }
+ MOZ_ASSERT(payload.id > 0, "Payload should have an id");
+ }
+
+ if (!ids.empty()) {
+ // Remove any old leftover payload.
+ nsAutoCString sql("DELETE FROM moz_icons WHERE id IN (");
+ for (int64_t id : ids) {
+ sql.AppendInt(id);
+ sql.AppendLiteral(",");
+ }
+ sql.AppendLiteral(" 0)"); // Non-existing id to match the trailing comma.
+ nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(sql);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
return NS_OK;
}
/**
- * Fetches information on a icon from the Places database.
+ * Fetches information on a icon url from the database.
*
* @param aDBConn
* Database connection to history tables.
+ * @param aPreferredWidth
+ * The preferred size to fetch.
* @param _icon
* Icon that should be fetched.
*/
nsresult
FetchIconInfo(const RefPtr<Database>& aDB,
- IconData& _icon)
+ uint16_t aPreferredWidth,
+ IconData& _icon
+)
{
MOZ_ASSERT(_icon.spec.Length(), "Must have a non-empty spec!");
MOZ_ASSERT(!NS_IsMainThread());
if (_icon.status & ICON_STATUS_CACHED) {
+ // The icon data has already been set by ReplaceFaviconData.
return NS_OK;
}
nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
- "SELECT id, expiration, data, mime_type "
- "FROM moz_favicons WHERE url = :icon_url"
+ "/* do not warn (bug no: not worth having a compound index) */ "
+ "SELECT id, expire_ms, data, width "
+ "FROM moz_icons "
+ "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) "
+ "AND icon_url = :url "
+ "ORDER BY width ASC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
- DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"),
+ DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"),
_icon.spec);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- bool hasResult;
- rv = stmt->ExecuteStep(&hasResult);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- if (!hasResult) {
- // The icon does not exist yet, bail out.
- return NS_OK;
- }
-
- rv = stmt->GetInt64(0, &_icon.id);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
+ bool hasResult = false;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ IconPayload payload;
+ rv = stmt->GetInt64(0, &payload.id);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
- // Expiration can be nullptr.
- bool isNull;
- rv = stmt->GetIsNull(1, &isNull);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- if (!isNull) {
- rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_icon.expiration));
+ // Expiration can be nullptr.
+ bool isNull;
+ rv = stmt->GetIsNull(1, &isNull);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- }
+ if (!isNull) {
+ int64_t expire_ms;
+ rv = stmt->GetInt64(1, &expire_ms);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ _icon.expiration = expire_ms * 1000;
+ }
- // Data can be nullptr.
- rv = stmt->GetIsNull(2, &isNull);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- if (!isNull) {
uint8_t* data;
uint32_t dataLen = 0;
rv = stmt->GetBlob(2, &dataLen, &data);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- _icon.data.Adopt(TO_CHARBUFFER(data), dataLen);
- // Read mime only if we have data.
- rv = stmt->GetUTF8String(3, _icon.mimeType);
+
+ payload.data.Adopt(TO_CHARBUFFER(data), dataLen);
+ int32_t width;
+ rv = stmt->GetInt32(3, &width);
MOZ_ASSERT(NS_SUCCEEDED(rv));
+ payload.width = width;
+
+ if (payload.width == UINT16_MAX) {
+ payload.mimeType.AssignLiteral(SVG_MIME_TYPE);
+ } else {
+ payload.mimeType.AssignLiteral(PNG_MIME_TYPE);
+ }
+
+ if (aPreferredWidth == 0 || _icon.payloads.Length() == 0) {
+ _icon.payloads.AppendElement(payload);
+ } else if (payload.width >= aPreferredWidth) {
+ // Only retain the best matching payload.
+ _icon.payloads.ReplaceElementAt(0, payload);
+ } else {
+ break;
+ }
}
return NS_OK;
}
nsresult
-FetchIconURL(const RefPtr<Database>& aDB,
- const nsACString& aPageSpec,
- nsACString& aIconSpec)
+FetchIconPerSpec(const RefPtr<Database>& aDB,
+ const nsACString& aPageSpec,
+ IconData& aIconData,
+ uint16_t aPreferredWidth)
{
MOZ_ASSERT(!aPageSpec.IsEmpty(), "Page spec must not be empty.");
MOZ_ASSERT(!NS_IsMainThread());
- aIconSpec.Truncate();
-
nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
- "SELECT f.url "
- "FROM moz_places h "
- "JOIN moz_favicons f ON h.favicon_id = f.id "
- "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ "/* do not warn (bug no: not worth having a compound index) */ "
+ "SELECT width, icon_url "
+ "FROM moz_icons i "
+ "JOIN moz_icons_to_pages ON i.id = icon_id "
+ "JOIN moz_pages_w_icons p ON p.id = page_id "
+ "WHERE page_url_hash = hash(:url) AND page_url = :url "
+ "ORDER BY width DESC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
- nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"),
aPageSpec);
NS_ENSURE_SUCCESS(rv, rv);
+ // Return the biggest icon close to the preferred width. It may be bigger
+ // or smaller if the preferred width isn't found.
bool hasResult;
- if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
- rv = stmt->GetUTF8String(0, aIconSpec);
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ int32_t width;
+ rv = stmt->GetInt32(0, &width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (width < aPreferredWidth && !aIconData.spec.IsEmpty()) {
+ break;
+ }
+ rv = stmt->GetUTF8String(1, aIconData.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
/**
* Tries to compute the expiration time for a icon from the channel.
@@ -302,50 +435,16 @@ GetExpirationTimeFromChannel(nsIChannel*
}
}
}
// If we did not obtain a time from the cache, use the cap value.
return expiration < 0 ? PR_Now() + MAX_FAVICON_EXPIRATION
: expiration;
}
-/**
- * Checks the icon and evaluates if it needs to be optimized. In such a case it
- * will try to reduce its size through OptimizeFaviconImage method of the
- * favicons service.
- *
- * @param aIcon
- * The icon to be evaluated.
- * @param aFaviconSvc
- * Pointer to the favicons service.
- */
-nsresult
-OptimizeIconSize(IconData& aIcon,
- nsFaviconService* aFaviconSvc)
-{
- MOZ_ASSERT(NS_IsMainThread());
-
- // Even if the page provides a large image for the favicon (eg, a highres
- // image or a multiresolution .ico file), don't try to store more data than
- // needed.
- nsAutoCString newData, newMimeType;
- if (aIcon.data.Length() > MAX_FAVICON_FILESIZE) {
- nsresult rv = aFaviconSvc->OptimizeFaviconImage(TO_INTBUFFER(aIcon.data),
- aIcon.data.Length(),
- aIcon.mimeType,
- newData,
- newMimeType);
- if (NS_SUCCEEDED(rv) && newData.Length() < aIcon.data.Length()) {
- aIcon.data = newData;
- aIcon.mimeType = newMimeType;
- }
- }
- return NS_OK;
-}
-
} // namespace
////////////////////////////////////////////////////////////////////////////////
//// AsyncFetchAndSetIconForPage
NS_IMPL_ISUPPORTS_INHERITED(
AsyncFetchAndSetIconForPage
, Runnable
@@ -374,32 +473,31 @@ AsyncFetchAndSetIconForPage::AsyncFetchA
NS_IMETHODIMP
AsyncFetchAndSetIconForPage::Run()
{
MOZ_ASSERT(!NS_IsMainThread());
// Try to fetch the icon from the database.
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
- nsresult rv = FetchIconInfo(DB, mIcon);
+ nsresult rv = FetchIconInfo(DB, 0, mIcon);
NS_ENSURE_SUCCESS(rv, rv);
- bool isInvalidIcon = mIcon.data.IsEmpty() ||
+ bool isInvalidIcon = !mIcon.payloads.Length() ||
(mIcon.expiration && PR_Now() > mIcon.expiration);
bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS ||
(mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon);
if (!fetchIconFromNetwork) {
// There is already a valid icon or we don't want to fetch a new one,
// directly proceed with association.
RefPtr<AsyncAssociateIconToPage> event =
new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
- DB->DispatchToAsyncThread(event);
-
- return NS_OK;
+ // We're already on the async thread.
+ return event->Run();
}
// Fetch the icon from the network, the request starts from the main-thread.
// When done this will associate the icon to the page and notify.
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod(this, &AsyncFetchAndSetIconForPage::FetchFromNetwork);
return NS_DispatchToMainThread(event);
}
@@ -408,20 +506,20 @@ nsresult
AsyncFetchAndSetIconForPage::FetchFromNetwork() {
MOZ_ASSERT(NS_IsMainThread());
if (mCanceled) {
return NS_OK;
}
// Ensure data is cleared, since it's going to be overwritten.
- if (mIcon.data.Length() > 0) {
- mIcon.data.Truncate(0);
- mIcon.mimeType.Truncate(0);
- }
+ mIcon.payloads.Clear();
+
+ IconPayload payload;
+ mIcon.payloads.AppendElement(payload);
nsCOMPtr<nsIURI> iconURI;
nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel),
iconURI,
mLoadingPrincipal,
@@ -484,30 +582,32 @@ AsyncFetchAndSetIconForPage::OnStartRequ
NS_IMETHODIMP
AsyncFetchAndSetIconForPage::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInputStream,
uint64_t aOffset,
uint32_t aCount)
{
- const size_t kMaxFaviconDownloadSize = 1 * 1024 * 1024;
- if (mIcon.data.Length() + aCount > kMaxFaviconDownloadSize) {
- mIcon.data.Truncate();
+ MOZ_ASSERT(mIcon.payloads.Length() == 1);
+ // Limit downloads to 500KB.
+ const size_t kMaxDownloadSize = 500 * 1024;
+ if (mIcon.payloads[0].data.Length() + aCount > kMaxDownloadSize) {
+ mIcon.payloads.Clear();
return NS_ERROR_FILE_TOO_BIG;
}
nsAutoCString buffer;
nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) {
return rv;
}
- if (!mIcon.data.Append(buffer, fallible)) {
- mIcon.data.Truncate();
+ if (!mIcon.payloads[0].data.Append(buffer, fallible)) {
+ mIcon.payloads.Clear();
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
@@ -546,85 +646,88 @@ AsyncFetchAndSetIconForPage::OnStopReque
}
nsFaviconService* favicons = nsFaviconService::GetFaviconService();
NS_ENSURE_STATE(favicons);
nsresult rv;
// If fetching the icon failed, add it to the failed cache.
- if (NS_FAILED(aStatusCode) || mIcon.data.Length() == 0) {
+ if (NS_FAILED(aStatusCode) || mIcon.payloads.Length() == 0) {
nsCOMPtr<nsIURI> iconURI;
rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
NS_ENSURE_SUCCESS(rv, rv);
rv = favicons->AddFailedFavicon(iconURI);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
// aRequest should always QI to nsIChannel.
MOZ_ASSERT(channel);
+ MOZ_ASSERT(mIcon.payloads.Length() == 1);
+ IconPayload& payload = mIcon.payloads[0];
+
nsAutoCString contentType;
channel->GetContentType(contentType);
- // Bug 366324 - can't sniff SVG yet, so rely on server-specified type
- if (contentType.EqualsLiteral("image/svg+xml")) {
- mIcon.mimeType.AssignLiteral("image/svg+xml");
+ // Bug 366324 - We don't want to sniff for SVG, so rely on server-specified type.
+ if (contentType.EqualsLiteral(SVG_MIME_TYPE)) {
+ payload.mimeType.AssignLiteral(SVG_MIME_TYPE);
+ payload.width = UINT16_MAX;
} else {
NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
- TO_INTBUFFER(mIcon.data), mIcon.data.Length(),
- mIcon.mimeType);
+ TO_INTBUFFER(payload.data), payload.data.Length(),
+ payload.mimeType);
}
// If the icon does not have a valid MIME type, add it to the failed cache.
- if (mIcon.mimeType.IsEmpty()) {
+ if (payload.mimeType.IsEmpty()) {
nsCOMPtr<nsIURI> iconURI;
rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
NS_ENSURE_SUCCESS(rv, rv);
rv = favicons->AddFailedFavicon(iconURI);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
mIcon.expiration = GetExpirationTimeFromChannel(channel);
// Telemetry probes to measure the favicon file sizes for each different file type.
// This allow us to measure common file sizes while also observing each type popularity.
- if (mIcon.mimeType.EqualsLiteral("image/png")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_PNG_SIZES, mIcon.data.Length());
+ if (payload.mimeType.EqualsLiteral(PNG_MIME_TYPE)) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_PNG_SIZES, payload.data.Length());
}
- else if (mIcon.mimeType.EqualsLiteral("image/x-icon") ||
- mIcon.mimeType.EqualsLiteral("image/vnd.microsoft.icon")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_ICO_SIZES, mIcon.data.Length());
+ else if (payload.mimeType.EqualsLiteral("image/x-icon") ||
+ payload.mimeType.EqualsLiteral("image/vnd.microsoft.icon")) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_ICO_SIZES, payload.data.Length());
}
- else if (mIcon.mimeType.EqualsLiteral("image/jpeg") ||
- mIcon.mimeType.EqualsLiteral("image/pjpeg")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_JPEG_SIZES, mIcon.data.Length());
+ else if (payload.mimeType.EqualsLiteral("image/jpeg") ||
+ payload.mimeType.EqualsLiteral("image/pjpeg")) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_JPEG_SIZES, payload.data.Length());
}
- else if (mIcon.mimeType.EqualsLiteral("image/gif")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_GIF_SIZES, mIcon.data.Length());
+ else if (payload.mimeType.EqualsLiteral("image/gif")) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_GIF_SIZES, payload.data.Length());
}
- else if (mIcon.mimeType.EqualsLiteral("image/bmp") ||
- mIcon.mimeType.EqualsLiteral("image/x-windows-bmp")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_BMP_SIZES, mIcon.data.Length());
+ else if (payload.mimeType.EqualsLiteral("image/bmp") ||
+ payload.mimeType.EqualsLiteral("image/x-windows-bmp")) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_BMP_SIZES, payload.data.Length());
}
- else if (mIcon.mimeType.EqualsLiteral("image/svg+xml")) {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_SVG_SIZES, mIcon.data.Length());
+ else if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_SVG_SIZES, payload.data.Length());
}
else {
- mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_OTHER_SIZES, mIcon.data.Length());
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_OTHER_SIZES, payload.data.Length());
}
- rv = OptimizeIconSize(mIcon, favicons);
+ rv = favicons->OptimizeIconSizes(mIcon);
NS_ENSURE_SUCCESS(rv, rv);
- // If over the maximum size allowed, don't save data to the database to
- // avoid bloating it.
- if (mIcon.data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ // If there's not valid payload, don't store the icon into to the database.
+ if (mIcon.payloads.Length() == 0) {
return NS_OK;
}
mIcon.status = ICON_STATUS_CHANGED;
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
RefPtr<AsyncAssociateIconToPage> event =
@@ -640,16 +743,17 @@ AsyncFetchAndSetIconForPage::OnStopReque
AsyncAssociateIconToPage::AsyncAssociateIconToPage(
const IconData& aIcon
, const PageData& aPage
, const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback
) : mCallback(aCallback)
, mIcon(aIcon)
, mPage(aPage)
{
+ // May be created in both threads.
}
NS_IMETHODIMP
AsyncAssociateIconToPage::Run()
{
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<Database> DB = Database::GetDatabase();
@@ -661,68 +765,91 @@ AsyncAssociateIconToPage::Run()
if (!mPage.canAddToHistory) {
return NS_OK;
}
}
else {
NS_ENSURE_SUCCESS(rv, rv);
}
+ bool shouldUpdateIcon = mIcon.status & ICON_STATUS_CHANGED;
+ if (!shouldUpdateIcon) {
+ for (const auto& payload : mIcon.payloads) {
+ // If the entry is missing from the database, we should add it.
+ if (payload.id == 0) {
+ shouldUpdateIcon = true;
+ break;
+ }
+ }
+ }
+
mozStorageTransaction transaction(DB->MainConn(), false,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
- // If there is no entry for this icon, or the entry is obsolete, replace it.
- if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) {
+ if (shouldUpdateIcon) {
rv = SetIconInfo(DB, mIcon);
NS_ENSURE_SUCCESS(rv, rv);
- // Get the new icon id. Do this regardless mIcon.id, since other code
- // could have added a entry before us. Indeed we interrupted the thread
- // after the previous call to FetchIconInfo.
mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED;
- rv = FetchIconInfo(DB, mIcon);
- NS_ENSURE_SUCCESS(rv, rv);
}
// If the page does not have an id, don't try to insert a new one, cause we
// don't know where the page comes from. Not doing so we may end adding
// a page that otherwise we'd explicitly ignore, like a POST or an error page.
if (mPage.id == 0) {
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
- // Otherwise just associate the icon to the page, if needed.
- if (mPage.iconId != mIcon.id) {
+ // First we need to create the page entry.
+ {
nsCOMPtr<mozIStorageStatement> stmt;
- if (mPage.id) {
- stmt = DB->GetStatement(
- "UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id"
- );
- NS_ENSURE_STATE(stmt);
- rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- else {
- stmt = DB->GetStatement(
- "UPDATE moz_places SET favicon_id = :icon_id "
- "WHERE url_hash = hash(:page_url) AND url = :page_url"
- );
- NS_ENSURE_STATE(stmt);
- rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), mIcon.id);
+ stmt = DB->GetStatement(
+ "INSERT OR IGNORE INTO moz_pages_w_icons (id, page_url, page_url_hash) "
+ "VALUES (:page_id, :page_url, hash(:page_url)) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
NS_ENSURE_SUCCESS(rv, rv);
-
- mozStorageStatementScoper scoper(stmt);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
+ }
- mIcon.status |= ICON_STATUS_ASSOCIATED;
+ // Then we can create the relations.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ stmt = DB->GetStatement(
+ "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
+ "VALUES (:page_id, :icon_id) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (const auto& payload : mIcon.payloads) {
+ nsCOMPtr<mozIStorageBindingParams> params;
+ rv = paramsArray->NewBindingParams(getter_AddRefs(params));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = params->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = params->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), payload.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = paramsArray->AddParams(params);
+ NS_ENSURE_SUCCESS(rv, rv);
}
+ rv = stmt->BindParameters(paramsArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIcon.status |= ICON_STATUS_ASSOCIATED;
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// Finally, dispatch an event to the main thread to notify observers.
nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(mIcon, mPage, mCallback);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
@@ -730,117 +857,116 @@ AsyncAssociateIconToPage::Run()
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// AsyncGetFaviconURLForPage
AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
const nsACString& aPageSpec
+, uint16_t aPreferredWidth
, nsIFaviconDataCallback* aCallback
-) : mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+) : mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
+ , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
{
MOZ_ASSERT(NS_IsMainThread());
mPageSpec.Assign(aPageSpec);
}
NS_IMETHODIMP
AsyncGetFaviconURLForPage::Run()
{
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
- nsAutoCString iconSpec;
- nsresult rv = FetchIconURL(DB, mPageSpec, iconSpec);
+ IconData iconData;
+ nsresult rv = FetchIconPerSpec(DB, mPageSpec, iconData, mPreferredWidth);
NS_ENSURE_SUCCESS(rv, rv);
// Now notify our callback of the icon spec we retrieved, even if empty.
- IconData iconData;
- iconData.spec.Assign(iconSpec);
-
PageData pageData;
pageData.spec.Assign(mPageSpec);
nsCOMPtr<nsIRunnable> event =
new NotifyIconObservers(iconData, pageData, mCallback);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// AsyncGetFaviconDataForPage
AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage(
const nsACString& aPageSpec
+, uint16_t aPreferredWidth
, nsIFaviconDataCallback* aCallback
-) : mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+) : mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
+ , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
{
MOZ_ASSERT(NS_IsMainThread());
mPageSpec.Assign(aPageSpec);
}
NS_IMETHODIMP
AsyncGetFaviconDataForPage::Run()
{
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
- nsAutoCString iconSpec;
- nsresult rv = FetchIconURL(DB, mPageSpec, iconSpec);
+ IconData iconData;
+ nsresult rv = FetchIconPerSpec(DB, mPageSpec, iconData, mPreferredWidth);
NS_ENSURE_SUCCESS(rv, rv);
- IconData iconData;
- iconData.spec.Assign(iconSpec);
-
- PageData pageData;
- pageData.spec.Assign(mPageSpec);
-
- if (!iconSpec.IsEmpty()) {
- rv = FetchIconInfo(DB, iconData);
+ if (!iconData.spec.IsEmpty()) {
+ rv = FetchIconInfo(DB, mPreferredWidth, iconData);
if (NS_FAILED(rv)) {
iconData.spec.Truncate();
}
}
+ PageData pageData;
+ pageData.spec.Assign(mPageSpec);
+
nsCOMPtr<nsIRunnable> event =
new NotifyIconObservers(iconData, pageData, mCallback);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// AsyncReplaceFaviconData
AsyncReplaceFaviconData::AsyncReplaceFaviconData(const IconData &aIcon)
: mIcon(aIcon)
{
+ MOZ_ASSERT(NS_IsMainThread());
}
NS_IMETHODIMP
AsyncReplaceFaviconData::Run()
{
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
- IconData dbIcon;
- dbIcon.spec.Assign(mIcon.spec);
- nsresult rv = FetchIconInfo(DB, dbIcon);
- NS_ENSURE_SUCCESS(rv, rv);
- if (!dbIcon.id) {
+ mozStorageTransaction transaction(DB->MainConn(), false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+ nsresult rv = SetIconInfo(DB, mIcon, true);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ // There's no previous icon to replace, we don't need to do anything.
return NS_OK;
}
-
- rv = SetIconInfo(DB, mIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// We can invalidate the cache version since we now persist the icon.
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod(this, &AsyncReplaceFaviconData::RemoveIconDataCacheEntry);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
@@ -891,22 +1017,28 @@ NotifyIconObservers::Run()
// Notify observers only if something changed.
if (mIcon.status & ICON_STATUS_SAVED ||
mIcon.status & ICON_STATUS_ASSOCIATED) {
SendGlobalNotifications(iconURI);
}
}
}
- if (mCallback) {
- (void)mCallback->OnComplete(iconURI, mIcon.data.Length(),
- TO_INTBUFFER(mIcon.data), mIcon.mimeType);
+ if (!mCallback) {
+ return NS_OK;
}
- return NS_OK;
+ if (mIcon.payloads.Length() > 0) {
+ IconPayload& payload = mIcon.payloads[0];
+ return mCallback->OnComplete(iconURI, payload.data.Length(),
+ TO_INTBUFFER(payload.data), payload.mimeType,
+ payload.width);
+ }
+ return mCallback->OnComplete(iconURI, 0, TO_INTBUFFER(EmptyCString()),
+ EmptyCString(), 0);
}
void
NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI)
{
nsCOMPtr<nsIURI> pageURI;
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(pageURI), mPage.spec));
if (pageURI) {
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -10,16 +10,17 @@
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIStreamListener.h"
#include "mozIPlacesPendingOperation.h"
#include "nsThreadUtils.h"
#include "nsProxyRelease.h"
#include "imgITools.h"
#include "imgIContainer.h"
+#include "imgLoader.h"
class nsIPrincipal;
#include "Database.h"
#include "mozilla/storage.h"
#define ICON_STATUS_UNKNOWN 0
#define ICON_STATUS_CHANGED 1 << 0
@@ -54,56 +55,70 @@ namespace places {
*/
enum AsyncFaviconFetchMode {
FETCH_NEVER = 0
, FETCH_IF_MISSING
, FETCH_ALWAYS
};
/**
- * Data cache for a icon entry.
+ * Represents one of the payloads (frames) of an icon entry.
+ */
+struct IconPayload
+{
+ IconPayload()
+ : id(0)
+ , width(0)
+ {
+ data.SetIsVoid(true);
+ mimeType.SetIsVoid(true);
+ }
+
+ int64_t id;
+ uint16_t width;
+ nsCString data;
+ nsCString mimeType;
+};
+
+/**
+ * Represents an icon entry.
*/
struct IconData
{
IconData()
- : id(0)
- , expiration(0)
+ : expiration(0)
, fetchMode(FETCH_NEVER)
, status(ICON_STATUS_UNKNOWN)
{
}
- int64_t id;
nsCString spec;
- nsCString data;
- nsCString mimeType;
PRTime expiration;
enum AsyncFaviconFetchMode fetchMode;
uint16_t status; // This is a bitset, see ICON_STATUS_* defines above.
+ nsTArray<IconPayload> payloads;
};
/**
* Data cache for a page entry.
*/
struct PageData
{
PageData()
: id(0)
, canAddToHistory(true)
- , iconId(0)
{
guid.SetIsVoid(true);
}
int64_t id;
nsCString spec;
nsCString bookmarkedSpec;
nsString revHost;
bool canAddToHistory; // False for disabled history and unsupported schemas.
- int64_t iconId;
nsCString guid;
};
/**
* Async fetches icon from database or network, associates it with the required
* page and finally notifies the change.
*/
class AsyncFetchAndSetIconForPage final : public Runnable
@@ -194,21 +209,25 @@ public:
/**
* Constructor.
*
* @param aPageSpec
* URL of the page whose favicon's URL we're fetching
* @param aCallback
* function to be called once finished
+ * @param aPreferredWidth
+ * The preferred size for the icon
*/
AsyncGetFaviconURLForPage(const nsACString& aPageSpec,
+ uint16_t aPreferredWidth,
nsIFaviconDataCallback* aCallback);
private:
+ uint16_t mPreferredWidth;
nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
nsCString mPageSpec;
};
/**
* Asynchronously tries to get the URL and data of a page's favicon, then
* notifies the given observer.
@@ -218,23 +237,28 @@ class AsyncGetFaviconDataForPage final :
public:
NS_DECL_NSIRUNNABLE
/**
* Constructor.
*
* @param aPageSpec
* URL of the page whose favicon URL and data we're fetching
+ * @param aPreferredWidth
+ * The preferred size of the icon. We will try to return an icon close
+ * to this size.
* @param aCallback
* function to be called once finished
*/
AsyncGetFaviconDataForPage(const nsACString& aPageSpec,
+ uint16_t aPreferredWidth,
nsIFaviconDataCallback* aCallback);
private:
+ uint16_t mPreferredWidth;
nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
nsCString mPageSpec;
};
class AsyncReplaceFaviconData final : public Runnable
{
public:
NS_DECL_NSIRUNNABLE
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -155,19 +155,17 @@ this.History = Object.freeze({
* A promise resolved once the operation is complete.
* @resolves (PageInfo)
* A PageInfo object populated with data after the insert is complete.
* @rejects (Error)
* Rejects if the insert was unsuccessful.
*
* @throws (Error)
* If the `url` specified was for a protocol that should not be
- * stored (e.g. "chrome:", "mailbox:", "about:", "imap:", "news:",
- * "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
- * "javascript:", "blob:").
+ * stored (@see nsNavHistory::CanAddURI).
* @throws (Error)
* If `pageInfo` has an unexpected type.
* @throws (Error)
* If `pageInfo` does not have a `url`.
* @throws (Error)
* If `pageInfo` does not have a `visits` property or if the
* value of `visits` is ill-typed or is an empty array.
* @throws (Error)
@@ -211,19 +209,17 @@ this.History = Object.freeze({
* @return (Promise)
* A promise resolved once the operation is complete.
* @resolves (null)
* @rejects (Error)
* Rejects if all of the inserts were unsuccessful.
*
* @throws (Error)
* If the `url` specified was for a protocol that should not be
- * stored (e.g. "chrome:", "mailbox:", "about:", "imap:", "news:",
- * "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
- * "javascript:", "blob:").
+ * stored (@see nsNavHistory::CanAddURI).
* @throws (Error)
* If `pageInfos` has an unexpected type.
* @throws (Error)
* If a `pageInfo` does not have a `url`.
* @throws (Error)
* If a `PageInfo` does not have a `visits` property or if the
* value of `visits` is ill-typed or is an empty array.
* @throws (Error)
@@ -701,19 +697,20 @@ var cleanupPages = Task.async(function*(
// async race conditions.
yield db.execute(`DELETE FROM moz_places WHERE id IN ( ${ idsList } )
AND foreign_count = 0 AND last_visit_date ISNULL`);
// Hosts accumulated during the places delete are updated through a trigger
// (see nsPlacesTriggers.h).
yield db.executeCached(`DELETE FROM moz_updatehosts_temp`);
// Expire orphans.
- yield db.executeCached(`
- DELETE FROM moz_favicons WHERE NOT EXISTS
- (SELECT 1 FROM moz_places WHERE favicon_id = moz_favicons.id)`);
+ yield db.executeCached(`DELETE FROM moz_pages_w_icons
+ WHERE page_url_hash NOT IN (SELECT url_hash FROM moz_places)`);
+ yield db.executeCached(`DELETE FROM moz_icons
+ WHERE id NOT IN (SELECT icon_id FROM moz_icons_to_pages)`);
yield db.execute(`DELETE FROM moz_annos
WHERE place_id IN ( ${ idsList } )`);
yield db.execute(`DELETE FROM moz_inputhistory
WHERE place_id IN ( ${ idsList } )`);
}
});
/**
--- a/toolkit/components/places/PageIconProtocolHandler.js
+++ b/toolkit/components/places/PageIconProtocolHandler.js
@@ -36,16 +36,26 @@ function streamDefaultFavicon(uri, loadI
let defaultIconChannel = makeDefaultFaviconChannel(uri, loadInfo);
defaultIconChannel.asyncOpen2(listener);
} catch (ex) {
Cu.reportError(ex);
outputStream.close();
}
}
+function serveIcon(pipe, data, len) {
+ // Pass the icon data to the output stream.
+ let stream = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ stream.setOutputStream(pipe.outputStream);
+ stream.writeByteArray(data, len);
+ stream.close();
+ pipe.outputStream.close();
+}
+
function PageIconProtocolHandler() {
}
PageIconProtocolHandler.prototype = {
get scheme() {
return "page-icon";
},
@@ -78,35 +88,26 @@ PageIconProtocolHandler.prototype = {
let channel = Cc["@mozilla.org/network/input-stream-channel;1"]
.createInstance(Ci.nsIInputStreamChannel);
channel.QueryInterface(Ci.nsIChannel);
channel.setURI(uri);
channel.contentStream = pipe.inputStream;
channel.loadInfo = loadInfo;
let pageURI = NetUtil.newURI(uri.path);
- PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconuri, len, data, mime) => {
+ PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconURI, len, data, mimeType) => {
+ channel.contentType = mimeType;
if (len == 0) {
- channel.contentType = "image/png";
streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
- return;
- }
-
- try {
- channel.contentType = mime;
- // Pass the icon data to the output stream.
- let stream = Cc["@mozilla.org/binaryoutputstream;1"]
- .createInstance(Ci.nsIBinaryOutputStream);
- stream.setOutputStream(pipe.outputStream);
- stream.writeByteArray(data, len);
- stream.close();
- pipe.outputStream.close();
- } catch (ex) {
- channel.contentType = "image/png";
- streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+ } else {
+ try {
+ serveIcon(pipe, data, len);
+ } catch (ex) {
+ streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+ }
}
});
return channel;
} catch (ex) {
return makeDefaultFaviconChannel(uri, loadInfo);
}
},
--- a/toolkit/components/places/PlacesDBUtils.jsm
+++ b/toolkit/components/places/PlacesDBUtils.jsm
@@ -588,23 +588,27 @@ this.PlacesDBUtils = {
WHERE length(title) = 0 AND type = :folder_type
AND parent = :tags_folder`
);
fixEmptyNamedTags.params["empty_title"] = "(notitle)";
fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
cleanupStatements.push(fixEmptyNamedTags);
- // MOZ_FAVICONS
- // E.1 remove orphan icons
+ // MOZ_ICONS
+ // E.1 remove orphan icon entries.
+ let deleteOrphanIconPages = DBConn.createAsyncStatement(
+ `DELETE FROM moz_pages_w_icons WHERE page_url_hash NOT IN (
+ SELECT url_hash FROM moz_places
+ )`);
+ cleanupStatements.push(deleteOrphanIconPages);
+
let deleteOrphanIcons = DBConn.createAsyncStatement(
- `DELETE FROM moz_favicons WHERE id IN (
- SELECT id FROM moz_favicons f
- WHERE NOT EXISTS
- (SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1)
+ `DELETE FROM moz_icons WHERE id NOT IN (
+ SELECT icon_id FROM moz_icons_to_pages
)`);
cleanupStatements.push(deleteOrphanIcons);
// MOZ_HISTORYVISITS
// F.1 remove orphan visits
let deleteOrphanVisits = DBConn.createAsyncStatement(
`DELETE FROM moz_historyvisits WHERE id IN (
SELECT id FROM moz_historyvisits v
@@ -649,26 +653,16 @@ this.PlacesDBUtils = {
`DELETE FROM moz_keywords WHERE id IN (
SELECT id FROM moz_keywords k
WHERE NOT EXISTS
(SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
)`);
cleanupStatements.push(deleteUnusedKeywords);
// MOZ_PLACES
- // L.1 fix wrong favicon ids
- let fixInvalidFaviconIds = DBConn.createAsyncStatement(
- `UPDATE moz_places SET favicon_id = NULL WHERE id IN (
- SELECT id FROM moz_places h
- WHERE favicon_id NOT NULL
- AND NOT EXISTS
- (SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1)
- )`);
- cleanupStatements.push(fixInvalidFaviconIds);
-
// L.2 recalculate visit_count and last_visit_date
let fixVisitStats = DBConn.createAsyncStatement(
`UPDATE moz_places
SET visit_count = (SELECT count(*) FROM moz_historyvisits
WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8,9)),
last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits
WHERE place_id = moz_places.id)
WHERE id IN (
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1864,32 +1864,35 @@ this.PlacesUtils = {
UNION ALL
SELECT b2.fk, level + 1, b2.type, b2.id, b2.guid, b2.parent,
descendants.guid, b2.position, b2.title, b2.dateAdded,
b2.lastModified
FROM moz_bookmarks b2
JOIN descendants ON b2.parent = descendants.id AND b2.id <> :tags_folder)
SELECT d.level, d.id, d.guid, d.parent, d.parentGuid, d.type,
d.position AS [index], d.title, d.dateAdded, d.lastModified,
- h.url, f.url AS iconuri,
+ h.url, (SELECT icon_url FROM moz_icons i
+ JOIN moz_icons_to_pages ON icon_id = i.id
+ JOIN moz_pages_w_icons pi ON page_id = pi.id
+ WHERE pi.page_url_hash = hash(h.url) AND pi.page_url = h.url
+ ORDER BY width DESC LIMIT 1) AS iconuri,
(SELECT GROUP_CONCAT(t.title, ',')
FROM moz_bookmarks b2
JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder
WHERE b2.fk = h.id
) AS tags,
EXISTS (SELECT 1 FROM moz_items_annos
WHERE item_id = d.id LIMIT 1) AS has_annos,
(SELECT a.content FROM moz_annos a
JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
WHERE place_id = h.id AND n.name = :charset_anno
) AS charset
FROM descendants d
LEFT JOIN moz_bookmarks b3 ON b3.id = d.parent
LEFT JOIN moz_places h ON h.id = d.fk
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
ORDER BY d.level, d.parent, d.position`;
if (!aItemGuid)
aItemGuid = this.bookmarks.rootGuid;
let hasExcludeItemsCallback =
aOptions.hasOwnProperty("excludeItemsCallback");
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -89,25 +89,24 @@ const REGEXP_USER_CONTEXT_ID = /(?:^| )u
// Regex used to match one or more whitespace.
const REGEXP_SPACES = /\s+/;
// Sqlite result row index constants.
const QUERYINDEX_QUERYTYPE = 0;
const QUERYINDEX_URL = 1;
const QUERYINDEX_TITLE = 2;
-const QUERYINDEX_ICONURL = 3;
-const QUERYINDEX_BOOKMARKED = 4;
-const QUERYINDEX_BOOKMARKTITLE = 5;
-const QUERYINDEX_TAGS = 6;
-const QUERYINDEX_VISITCOUNT = 7;
-const QUERYINDEX_TYPED = 8;
-const QUERYINDEX_PLACEID = 9;
-const QUERYINDEX_SWITCHTAB = 10;
-const QUERYINDEX_FRECENCY = 11;
+const QUERYINDEX_BOOKMARKED = 3;
+const QUERYINDEX_BOOKMARKTITLE = 4;
+const QUERYINDEX_TAGS = 5;
+const QUERYINDEX_VISITCOUNT = 6;
+const QUERYINDEX_TYPED = 7;
+const QUERYINDEX_PLACEID = 8;
+const QUERYINDEX_SWITCHTAB = 9;
+const QUERYINDEX_FRECENCY = 10;
// This SQL query fragment provides the following:
// - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
// - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE)
// - the tags associated with a bookmarked entry (QUERYINDEX_TAGS)
const SQL_BOOKMARK_TAGS_FRAGMENT =
`EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
@@ -120,20 +119,19 @@ const SQL_BOOKMARK_TAGS_FRAGMENT =
) AS tags`;
// TODO bug 412736: in case of a frecency tie, we might break it with h.typed
// and h.visit_count. That is slower though, so not doing it yet...
// NB: as a slight performance optimization, we only evaluate the "btitle"
// and "tags" queries for bookmarked entries.
function defaultQuery(conditions = "") {
let query =
- `SELECT :query_type, h.url, h.title, f.url, ${SQL_BOOKMARK_TAGS_FRAGMENT},
+ `SELECT :query_type, h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT},
h.visit_count, h.typed, h.id, t.open_count, h.frecency
FROM moz_places h
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
LEFT JOIN moz_openpages_temp t
ON t.url = h.url
AND t.userContextId = :userContextId
WHERE h.frecency <> 0
AND AUTOCOMPLETE_MATCH(:searchString, h.url,
CASE WHEN bookmarked THEN
IFNULL(btitle, h.title)
ELSE h.title END,
@@ -145,61 +143,55 @@ function defaultQuery(conditions = "") {
:matchBehavior, :searchBehavior)
${conditions}
ORDER BY h.frecency DESC, h.id DESC
LIMIT :maxResults`;
return query;
}
const SQL_SWITCHTAB_QUERY =
- `SELECT :query_type, t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ `SELECT :query_type, t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL,
t.open_count, NULL
FROM moz_openpages_temp t
LEFT JOIN moz_places h ON h.url_hash = hash(t.url) AND h.url = t.url
WHERE h.id IS NULL
AND t.userContextId = :userContextId
AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
NULL, NULL, NULL, t.open_count,
:matchBehavior, :searchBehavior)
ORDER BY t.ROWID DESC
LIMIT :maxResults`;
const SQL_ADAPTIVE_QUERY =
`/* do not warn (bug 487789) */
- SELECT :query_type, h.url, h.title, f.url, ${SQL_BOOKMARK_TAGS_FRAGMENT},
+ SELECT :query_type, h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT},
h.visit_count, h.typed, h.id, t.open_count, h.frecency
FROM (
SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank,
place_id
FROM moz_inputhistory
WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
GROUP BY place_id
) AS i
JOIN moz_places h ON h.id = i.place_id
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
LEFT JOIN moz_openpages_temp t
ON t.url = h.url
AND t.userContextId = :userContextId
WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
IFNULL(btitle, h.title), tags,
h.visit_count, h.typed, bookmarked,
t.open_count,
:matchBehavior, :searchBehavior)
ORDER BY rank DESC, h.frecency DESC`;
function hostQuery(conditions = "") {
let query =
`/* do not warn (bug NA): not worth to index on (typed, frecency) */
- SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/',
- ( SELECT f.url FROM moz_favicons f
- JOIN moz_places h ON h.favicon_id = f.id
- WHERE rev_host = get_unreversed_host(host || '.') || '.'
- OR rev_host = get_unreversed_host(host || '.') || '.www.'
- ) AS favicon_url,
+ SELECT :query_type, host || '/', IFNULL(prefix, 'http://') || host || '/',
NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency
FROM moz_hosts
WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
AND frecency <> 0
${conditions}
ORDER BY frecency DESC
LIMIT 1`;
return query;
@@ -207,22 +199,17 @@ function hostQuery(conditions = "") {
const SQL_HOST_QUERY = hostQuery();
const SQL_TYPED_HOST_QUERY = hostQuery("AND typed = 1");
function bookmarkedHostQuery(conditions = "") {
let query =
`/* do not warn (bug NA): not worth to index on (typed, frecency) */
- SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/',
- ( SELECT f.url FROM moz_favicons f
- JOIN moz_places h ON h.favicon_id = f.id
- WHERE rev_host = get_unreversed_host(host || '.') || '.'
- OR rev_host = get_unreversed_host(host || '.') || '.www.'
- ) AS favicon_url,
+ SELECT :query_type, host || '/', IFNULL(prefix, 'http://') || host || '/',
( SELECT foreign_count > 0 FROM moz_places
WHERE rev_host = get_unreversed_host(host || '.') || '.'
OR rev_host = get_unreversed_host(host || '.') || '.www.'
) AS bookmarked, NULL, NULL, NULL, NULL, NULL, NULL, frecency
FROM moz_hosts
WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
AND bookmarked
AND frecency <> 0
@@ -233,21 +220,20 @@ function bookmarkedHostQuery(conditions
}
const SQL_BOOKMARKED_HOST_QUERY = bookmarkedHostQuery();
const SQL_BOOKMARKED_TYPED_HOST_QUERY = bookmarkedHostQuery("AND typed = 1");
function urlQuery(conditions = "") {
return `/* do not warn (bug no): cannot use an index to sort */
- SELECT :query_type, h.url, NULL, f.url AS favicon_url,
+ SELECT :query_type, h.url, NULL,
foreign_count > 0 AS bookmarked,
NULL, NULL, NULL, NULL, NULL, NULL, h.frecency
FROM moz_places h
- LEFT JOIN moz_favicons f ON h.favicon_id = f.id
WHERE (rev_host = :revHost OR rev_host = :revHost || "www.")
AND h.frecency <> 0
AND fixup_url(h.url) BETWEEN :searchString AND :searchString || X'FFFF'
${conditions}
ORDER BY h.frecency DESC, h.id DESC
LIMIT 1`;
}
@@ -1387,17 +1373,22 @@ Search.prototype = {
url,
input: this._originalSearchString,
postData,
});
let value = this._enableActions ? actionURL : url;
// The title will end up being "host: queryString"
let comment = entry.url.host;
- this._addMatch({ value, comment, style, frecency: FRECENCY_DEFAULT });
+ this._addMatch({
+ value,
+ comment,
+ icon: "page-icon:" + url,
+ style,
+ frecency: FRECENCY_DEFAULT });
return true;
},
*_matchSearchEngineUrl() {
if (!Prefs.autofillSearchEngines)
return false;
let match = yield PlacesSearchAutocompleteProvider.findMatchByToken(
@@ -1528,22 +1519,17 @@ Search.prototype = {
},
*_matchRemoteTabs() {
let matches = yield PlacesRemoteTabsAutocompleteProvider.getMatches(this._originalSearchString);
for (let {url, title, icon, deviceName} of matches) {
// It's rare that Sync supplies the icon for the page (but if it does, it
// is a string URL)
if (!icon) {
- try {
- let favicon = yield PlacesUtils.promiseFaviconLinkUrl(url);
- if (favicon) {
- icon = favicon.spec;
- }
- } catch (ex) {} // no favicon for this URL.
+ icon = "page-icon:" + url;
} else {
icon = PlacesUtils.favicons
.getFaviconLinkForIcon(NetUtil.newURI(icon)).spec;
}
let match = {
// We include the deviceName in the action URL so we can render it in
// the URLBar.
@@ -1616,26 +1602,19 @@ Search.prototype = {
input: this._originalSearchString,
});
let match = {
value,
comment: displayURL,
style: "action visiturl",
frecency: 0,
+ icon: "page-icon:" + escapedURL
};
- try {
- let favicon = yield PlacesUtils.promiseFaviconLinkUrl(uri);
- if (favicon)
- match.icon = favicon.spec;
- } catch (e) {
- // It's possible we don't have a favicon for this - and that's ok.
- }
-
this._addMatch(match);
return true;
},
_onResultRow(row) {
if (this._localMatchesCount == 0) {
TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT, this);
}
@@ -1779,46 +1758,43 @@ Search.prototype = {
this._localMatchesCount++;
}
return index;
},
_processHostRow(row) {
let match = {};
let strippedHost = row.getResultByIndex(QUERYINDEX_URL);
- let unstrippedHost = row.getResultByIndex(QUERYINDEX_TITLE);
+ let url = row.getResultByIndex(QUERYINDEX_TITLE);
+ let unstrippedHost = stripHttpAndTrim(url, false);
let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
- let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL);
// If the unfixup value doesn't preserve the user's input just
// ignore it and complete to the found host.
if (!unstrippedHost.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
unstrippedHost = null;
}
match.value = this._strippedPrefix + strippedHost;
match.finalCompleteValue = unstrippedHost;
- if (faviconUrl) {
- match.icon = PlacesUtils.favicons
- .getFaviconLinkForIcon(NetUtil.newURI(faviconUrl)).spec;
- }
+ match.icon = "page-icon:" + url;
+
// Although this has a frecency, this query is executed before any other
// queries that would result in frecency matches.
match.frecency = frecency;
match.style = "autofill";
return match;
},
_processUrlRow(row) {
let url = row.getResultByIndex(QUERYINDEX_URL);
let strippedUrl = stripPrefix(url);
let prefix = url.substr(0, url.length - strippedUrl.length);
let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
- let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL);
// We must complete the URL up to the next separator (which is /, ? or #).
let searchString = stripPrefix(this._trimmedOriginalSearchString);
let separatorIndex = strippedUrl.slice(searchString.length)
.search(/[\/\?\#]/);
if (separatorIndex != -1) {
separatorIndex += searchString.length;
if (strippedUrl[separatorIndex] == "/") {
@@ -1830,37 +1806,33 @@ Search.prototype = {
let match = {
value: this._strippedPrefix + strippedUrl,
// Although this has a frecency, this query is executed before any other
// queries that would result in frecency matches.
frecency,
style: "autofill"
};
- if (faviconUrl) {
- match.icon = PlacesUtils.favicons
- .getFaviconLinkForIcon(NetUtil.newURI(faviconUrl)).spec;
- }
-
// Complete to the found url only if its untrimmed value preserves the
// user's input.
if (url.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
match.finalCompleteValue = prefix + strippedUrl;
}
+ match.icon = "page-icon:" + (match.finalCompleteValue || match.value);
+
return match;
},
_processRow(row) {
let match = {};
match.placeId = row.getResultByIndex(QUERYINDEX_PLACEID);
let escapedURL = row.getResultByIndex(QUERYINDEX_URL);
let openPageCount = row.getResultByIndex(QUERYINDEX_SWITCHTAB) || 0;
let historyTitle = row.getResultByIndex(QUERYINDEX_TITLE) || "";
- let iconurl = row.getResultByIndex(QUERYINDEX_ICONURL) || "";
let bookmarked = row.getResultByIndex(QUERYINDEX_BOOKMARKED);
let bookmarkTitle = bookmarked ?
row.getResultByIndex(QUERYINDEX_BOOKMARKTITLE) : null;
let tags = row.getResultByIndex(QUERYINDEX_TAGS) || "";
let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
// If actions are enabled and the page is open, add only the switch-to-tab
// result. Otherwise, add the normal result.
@@ -1906,20 +1878,17 @@ Search.prototype = {
}
}
if (action)
match.style = "action " + action;
match.value = url;
match.comment = title;
- if (iconurl) {
- match.icon = PlacesUtils.favicons
- .getFaviconLinkForIcon(NetUtil.newURI(iconurl)).spec;
- }
+ match.icon = "page-icon:" + escapedURL;
match.frecency = frecency;
return match;
},
/**
* @return a string consisting of the search query to be used based on the
* previously set urlbar suggestion preferences.
--- a/toolkit/components/places/mozIAsyncFavicons.idl
+++ b/toolkit/components/places/mozIAsyncFavicons.idl
@@ -58,16 +58,17 @@ interface mozIAsyncFavicons : nsISupport
*/
mozIPlacesPendingOperation setAndFetchFaviconForPage(
in nsIURI aPageURI,
in nsIURI aFaviconURI,
in boolean aForceReload,
in unsigned long aFaviconLoadType,
[optional] in nsIFaviconDataCallback aCallback,
[optional] in nsIPrincipal aLoadingPrincipal);
+
/**
* Sets the data for a given favicon URI either by replacing existing data in
* the database or taking the place of otherwise fetched icon data when
* calling setAndFetchFaviconForPage later.
*
* Favicon data for favicon URIs that are not associated with a page URI via
* setAndFetchFaviconForPage will be stored in memory, but may be expired at
* any time, so you should make an effort to associate favicon URIs with page
@@ -138,37 +139,45 @@ interface mozIAsyncFavicons : nsISupport
* Retrieves the favicon URI associated to the given page, if any.
*
* @param aPageURI
* URI of the page whose favicon URI we're looking up.
* @param aCallback
* This callback is always invoked to notify the result of the lookup.
* The aURI parameter will be the favicon URI, or null when no favicon
* is associated with the page or an error occurred while fetching it.
+ * @param aPreferredWidth
+ * The preferred icon width, 0 for the biggest available.
*
* @note When the callback is invoked, aDataLen will be always 0, aData will
* be an empty array, and aMimeType will be an empty string, regardless
* of whether a favicon is associated with the page.
*
* @see nsIFaviconDataCallback in nsIFaviconService.idl.
*/
void getFaviconURLForPage(in nsIURI aPageURI,
- in nsIFaviconDataCallback aCallback);
+ in nsIFaviconDataCallback aCallback,
+ [optional] in unsigned short aPreferredWidth);
/**
* Retrieves the favicon URI and data associated to the given page, if any.
+ * If the page icon is not available, it will try to return the root domain
+ * icon data, when it's known.
*
* @param aPageURI
* URI of the page whose favicon URI and data we're looking up.
* @param aCallback
* This callback is always invoked to notify the result of the lookup. The aURI
* parameter will be the favicon URI, or null when no favicon is
* associated with the page or an error occurred while fetching it. If
* aURI is not null, the other parameters may contain the favicon data.
* However, if no favicon data is currently associated with the favicon
* URI, aDataLen will be 0, aData will be an empty array, and aMimeType
* will be an empty string.
+ * @param aPreferredWidth
+ * The preferred icon width, 0 for the biggest available.
*
* @see nsIFaviconDataCallback in nsIFaviconService.idl.
*/
void getFaviconDataForPage(in nsIURI aPageURI,
- in nsIFaviconDataCallback aCallback);
+ in nsIFaviconDataCallback aCallback,
+ [optional] in unsigned short aPreferredWidth);
};
--- a/toolkit/components/places/nsAnnoProtocolHandler.cpp
+++ b/toolkit/components/places/nsAnnoProtocolHandler.cpp
@@ -28,16 +28,17 @@
#include "nsInputStreamPump.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsStringStream.h"
#include "SimpleChannel.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/storage.h"
#include "Helpers.h"
+#include "FaviconHelpers.h"
using namespace mozilla;
using namespace mozilla::places;
////////////////////////////////////////////////////////////////////////////////
//// Global Functions
/**
@@ -60,119 +61,115 @@ GetDefaultIcon(nsILoadInfo *aLoadInfo, n
namespace {
/**
* An instance of this class is passed to the favicon service as the callback
* for getting favicon data from the database. We'll get this data back in
* HandleResult, and on HandleCompletion, we'll close our output stream which
* will close the original channel for the favicon request.
*
- * However, if an error occurs at any point, we do not set mReturnDefaultIcon to
- * false, so we will open up another channel to get the default favicon, and
- * pass that along to our output stream in HandleCompletion. If anything
- * happens at that point, the world must be against us, so we return nothing.
+ * However, if an error occurs at any point and we don't have mData, we will
+ * just fallback to the default favicon. If anything happens at that point, the
+ * world must be against us, so we can do nothing.
*/
class faviconAsyncLoader : public AsyncStatementCallback
{
public:
- faviconAsyncLoader(nsIChannel *aChannel, nsIStreamListener *aListener) :
- mChannel(aChannel)
+ faviconAsyncLoader(nsIChannel *aChannel, nsIStreamListener *aListener)
+ : mChannel(aChannel)
, mListener(aListener)
{
- NS_ASSERTION(aChannel,
- "Not providing a channel will result in crashes!");
- NS_ASSERTION(aListener,
- "Not providing a stream listener will result in crashes!");
+ MOZ_ASSERT(aChannel, "Not providing a channel will result in crashes!");
+ MOZ_ASSERT(aListener, "Not providing a stream listener will result in crashes!");
+ // Set the default content type.
+ Unused << mChannel->SetContentType(NS_LITERAL_CSTRING(PNG_MIME_TYPE));
}
//////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback
NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet) override
{
- // We will only get one row back in total, so we do not need to loop.
nsCOMPtr<mozIStorageRow> row;
- nsresult rv = aResultSet->GetNextRow(getter_AddRefs(row));
- NS_ENSURE_SUCCESS(rv, rv);
+ while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
+ // TODO: For now just return the biggest icon, that is the first one.
+ // Later this should allow to return a specific size.
+ if (!mData.IsEmpty()) {
+ return NS_OK;
+ }
- // We do not allow favicons without a MIME type, so we'll return the default
- // icon.
- nsAutoCString mimeType;
- (void)row->GetUTF8String(1, mimeType);
- NS_ENSURE_FALSE(mimeType.IsEmpty(), NS_OK);
+ int32_t width;
+ nsresult rv = row->GetInt32(1, &width);
+ NS_ENSURE_SUCCESS(rv, rv);
- // Set our mimeType now that we know it.
- rv = mChannel->SetContentType(mimeType);
- NS_ENSURE_SUCCESS(rv, rv);
+ // Eventually override the default mimeType for svg.
+ if (width == UINT16_MAX) {
+ rv = mChannel->SetContentType(NS_LITERAL_CSTRING(SVG_MIME_TYPE));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
- // Obtain the binary blob that contains our favicon data.
- uint8_t *favicon;
- uint32_t size = 0;
- rv = row->GetBlob(0, &size, &favicon);
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsCOMPtr<nsIInputStream> stream;
- rv = NS_NewByteInputStream(getter_AddRefs(stream),
- reinterpret_cast<char*>(favicon),
- size, NS_ASSIGNMENT_ADOPT);
- if (NS_FAILED(rv)) {
- free(favicon);
- return rv;
+ // Obtain the binary blob that contains our favicon data.
+ uint8_t *data;
+ uint32_t dataLen;
+ rv = row->GetBlob(0, &dataLen, &data);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mData.Adopt(TO_CHARBUFFER(data), dataLen);
}
- RefPtr<nsInputStreamPump> pump;
- rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream, -1, -1, 0, 0,
- true);
- NS_ENSURE_SUCCESS(rv, rv);
-
- MOZ_DIAGNOSTIC_ASSERT(mListener);
- NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
-
- rv = pump->AsyncRead(mListener, nullptr);
- NS_ENSURE_SUCCESS(rv, rv);
-
- mListener = nullptr;
return NS_OK;
}
NS_IMETHOD HandleCompletion(uint16_t aReason) override
{
- // If we've already written our icon data to the channel, there's nothing
- // more to do. If we didn't, then return the default icon instead.
- if (!mListener)
- return NS_OK;
+ MOZ_DIAGNOSTIC_ASSERT(mListener);
+ NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
+ nsresult rv;
+ // Ensure we'll break possible cycles with the listener.
auto cleanup = MakeScopeExit([&] () {
mListener = nullptr;
});
+ if (!mData.IsEmpty()) {
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), mData);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<nsInputStreamPump> pump;
+ rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream, -1, -1, 0, 0,
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ return pump->AsyncRead(mListener, nullptr);
+ }
+ }
+ }
+
+ // Fallback to the default favicon.
// we should pass the loadInfo of the original channel along
// to the new channel. Note that mChannel can not be null,
// constructor checks that.
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
nsCOMPtr<nsIChannel> newChannel;
- nsresult rv = GetDefaultIcon(loadInfo, getter_AddRefs(newChannel));
-
+ rv = GetDefaultIcon(loadInfo, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) {
mListener->OnStartRequest(mChannel, nullptr);
mListener->OnStopRequest(mChannel, nullptr, rv);
return rv;
}
-
- mChannel->SetContentType(NS_LITERAL_CSTRING("image/png"));
-
return newChannel->AsyncOpen2(mListener);
}
protected:
virtual ~faviconAsyncLoader() {}
private:
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIStreamListener> mListener;
+ nsCString mData;
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
//// nsAnnoProtocolHandler
NS_IMPL_ISUPPORTS(nsAnnoProtocolHandler, nsIProtocolHandler)
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -113,42 +113,41 @@ nsFaviconService::Init()
}
return NS_OK;
}
NS_IMETHODIMP
nsFaviconService::ExpireAllFavicons()
{
+ NS_ENSURE_STATE(mDB);
+
+ nsCOMPtr<mozIStorageAsyncStatement> removePagesStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_pages_w_icons"
+ );
+ NS_ENSURE_STATE(removePagesStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_icons"
+ );
+ NS_ENSURE_STATE(removeIconsStmt);
nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
- "UPDATE moz_places "
- "SET favicon_id = NULL "
- "WHERE favicon_id NOT NULL"
+ "DELETE FROM moz_icons_to_pages"
);
NS_ENSURE_STATE(unlinkIconsStmt);
- nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
- "DELETE FROM moz_favicons WHERE id NOT IN ("
- "SELECT favicon_id FROM moz_places WHERE favicon_id NOT NULL "
- ")"
- );
- NS_ENSURE_STATE(removeIconsStmt);
mozIStorageBaseStatement* stmts[] = {
- unlinkIconsStmt.get()
+ removePagesStmt.get()
, removeIconsStmt.get()
+ , unlinkIconsStmt.get()
};
nsCOMPtr<mozIStoragePendingStatement> ps;
RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
new ExpireFaviconsStatementCallbackNotifier();
- nsresult rv = mDB->MainConn()->ExecuteAsync(
- stmts, ArrayLength(stmts), callback, getter_AddRefs(ps)
- );
- NS_ENSURE_SUCCESS(rv, rv);
-
- return NS_OK;
+ return mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts),
+ callback, getter_AddRefs(ps));
}
////////////////////////////////////////////////////////////////////////////////
//// nsITimerCallback
NS_IMETHODIMP
nsFaviconService::Notify(nsITimer* timer)
{
@@ -219,20 +218,22 @@ nsFaviconService::SetAndFetchFaviconForP
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_ARG_POINTER(_canceler);
// If a favicon is in the failed cache, only load it during a forced reload.
bool previouslyFailed;
nsresult rv = IsFailedFavicon(aFaviconURI, &previouslyFailed);
NS_ENSURE_SUCCESS(rv, rv);
if (previouslyFailed) {
- if (aForceReload)
+ if (aForceReload) {
RemoveFailedFavicon(aFaviconURI);
- else
+ }
+ else {
return NS_OK;
+ }
}
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
if (!loadingPrincipal) {
// Let's default to the nullPrincipal if no loadingPrincipal is provided.
const char16_t* params[] = {
u"nsFaviconService::setAndFetchFaviconForPage()",
@@ -243,46 +244,47 @@ nsFaviconService::SetAndFetchFaviconForP
nullptr, // aDocument
nsContentUtils::eNECKO_PROPERTIES,
"APIDeprecationWarning",
params, ArrayLength(params));
loadingPrincipal = NullPrincipal::Create();
}
NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
- // Check if the icon already exists and fetch it from the network, if needed.
- // Finally associate the icon to the requested page if not yet associated.
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.
- (void)GetReversedHostname(aPageURI, page.revHost);
+ Unused << GetReversedHostname(aPageURI, page.revHost);
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.
IconData icon;
+ // If we have an in-memory icon payload, it overwrites the actual request.
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);
}
// If the page url points to an image, the icon's url will be the same.
- // In future evaluate to store a resample of the image. For now avoid that
- // for database size concerns.
+ // TODO (Bug 403651): store a resample of the image. For now avoid that
+ // for database size and UX concerns.
// Don't store favicons for error pages too.
if (icon.spec.Equals(page.spec) ||
icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
return NS_OK;
}
RefPtr<AsyncFetchAndSetIconForPage> event =
new AsyncFetchAndSetIconForPage(icon, page, loadPrivate,
@@ -305,18 +307,21 @@ nsFaviconService::ReplaceFaviconData(nsI
const uint8_t* aData,
uint32_t aDataLen,
const nsACString& aMimeType,
PRTime aExpiration)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_ARG(aData);
- NS_ENSURE_TRUE(aDataLen > 0, NS_ERROR_INVALID_ARG);
- NS_ENSURE_TRUE(aMimeType.Length() > 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG(aDataLen > 0);
+ NS_ENSURE_ARG(aMimeType.Length() > 0);
+ NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(PromiseFlatCString(aMimeType).get(),
+ AcceptedMimeTypes::IMAGES));
+
if (aExpiration == 0) {
aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
}
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
if (!iconKey) {
return NS_ERROR_OUT_OF_MEMORY;
}
@@ -334,32 +339,32 @@ nsFaviconService::ReplaceFaviconData(nsI
IconData* iconData = &(iconKey->iconData);
iconData->expiration = aExpiration;
iconData->status = ICON_STATUS_CACHED;
iconData->fetchMode = FETCH_NEVER;
nsresult rv = aFaviconURI->GetSpec(iconData->spec);
NS_ENSURE_SUCCESS(rv, rv);
- // If the page provided a large image for the favicon (eg, a highres image
- // or a multiresolution .ico file), we don't want to store more data than
- // needed.
- if (aDataLen > MAX_FAVICON_FILESIZE) {
- rv = OptimizeFaviconImage(aData, aDataLen, aMimeType, iconData->data, iconData->mimeType);
- NS_ENSURE_SUCCESS(rv, rv);
+ IconPayload payload;
+ payload.mimeType = aMimeType;
+ payload.data.Assign(TO_CHARBUFFER(aData), aDataLen);
+ // There may already be a previous payload, so ensure to only have one.
+ iconData->payloads.Clear();
+ iconData->payloads.AppendElement(payload);
- if (iconData->data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
- // We cannot optimize this favicon size and we are over the maximum size
- // allowed, so we will not save data to the db to avoid bloating it.
- mUnassociatedIcons.RemoveEntry(aFaviconURI);
- return NS_ERROR_FAILURE;
- }
- } else {
- iconData->mimeType.Assign(aMimeType);
- iconData->data.Assign(TO_CHARBUFFER(aData), aDataLen);
+ rv = OptimizeIconSizes(*iconData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there's not valid payload, don't store the icon into to the database.
+ if ((*iconData).payloads.Length() == 0) {
+ // We cannot optimize this favicon size and we are over the maximum size
+ // allowed, so we will not save data to the db to avoid bloating it.
+ mUnassociatedIcons.RemoveEntry(aFaviconURI);
+ return NS_ERROR_FAILURE;
}
// If the database contains an icon at the given url, we will update the
// database immediately so that the associated pages are kept in sync.
// Otherwise, do nothing and let the icon be picked up from the memory hash.
RefPtr<AsyncReplaceFaviconData> event = new AsyncReplaceFaviconData(*iconData);
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
@@ -459,50 +464,52 @@ nsFaviconService::ReplaceFaviconDataFrom
free(buffer);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsFaviconService::GetFaviconURLForPage(nsIURI *aPageURI,
- nsIFaviconDataCallback* aCallback)
+ nsIFaviconDataCallback* aCallback,
+ uint16_t aPreferredWidth)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aPageURI);
NS_ENSURE_ARG(aCallback);
nsAutoCString pageSpec;
nsresult rv = aPageURI->GetSpec(pageSpec);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<AsyncGetFaviconURLForPage> event =
- new AsyncGetFaviconURLForPage(pageSpec, aCallback);
+ new AsyncGetFaviconURLForPage(pageSpec, aPreferredWidth, aCallback);
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
DB->DispatchToAsyncThread(event);
return NS_OK;
}
NS_IMETHODIMP
nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
- nsIFaviconDataCallback* aCallback)
+ nsIFaviconDataCallback* aCallback,
+ uint16_t aPreferredWidth)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aPageURI);
NS_ENSURE_ARG(aCallback);
nsAutoCString pageSpec;
nsresult rv = aPageURI->GetSpec(pageSpec);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<AsyncGetFaviconDataForPage> event =
- new AsyncGetFaviconDataForPage(pageSpec, aCallback);
+ new AsyncGetFaviconDataForPage(pageSpec, aPreferredWidth, aCallback);
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
DB->DispatchToAsyncThread(event);
return NS_OK;
}
nsresult
@@ -582,23 +589,17 @@ nsFaviconService::IsFailedFavicon(nsIURI
// This computes a favicon URL with string input and using the cached
// default one to minimize parsing.
nsresult
nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
nsIURI** aOutput)
{
if (aSpec.IsEmpty()) {
- // default icon for empty strings
- if (! mDefaultIcon) {
- nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
- NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
- NS_ENSURE_SUCCESS(rv, rv);
- }
- return mDefaultIcon->Clone(aOutput);
+ return GetDefaultFavicon(aOutput);
}
if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
// pass through for chrome URLs, since they can be referenced without
// this service
return NS_NewURI(aOutput, aSpec);
}
@@ -622,74 +623,123 @@ nsFaviconService::GetFaviconSpecForIconS
} else if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
aOutput = aSpec;
} else {
aOutput.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
aOutput += aSpec;
}
}
-
-// nsFaviconService::OptimizeFaviconImage
-//
-// Given a blob of data (a image file already read into a buffer), optimize
-// its size by recompressing it as a 16x16 PNG.
+/**
+ * Checks the icon and evaluates if it needs to be optimized.
+ *
+ * @param aIcon
+ * The icon to be evaluated.
+ */
nsresult
-nsFaviconService::OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
- const nsACString& aMimeType,
- nsACString& aNewData,
- nsACString& aNewMimeType)
+nsFaviconService::OptimizeIconSizes(IconData& aIcon)
{
- nsresult rv;
+ // TODO (bug 1346139): move optimization to the async thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ // There should only be a single payload at this point, it may have to be
+ // split though, if it's an ico file.
+ MOZ_ASSERT(aIcon.payloads.Length() == 1);
+
+ // Even if the page provides a large image for the favicon (eg, a highres
+ // image or a multiresolution .ico file), don't try to store more data than
+ // needed.
+ IconPayload payload = aIcon.payloads[0];
+ if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
+ // Nothing to optimize, but check the payload size.
+ if (payload.data.Length() >= nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ aIcon.payloads.Clear();
+ }
+ return NS_OK;
+ }
+
+ // Make space for the optimized payloads.
+ aIcon.payloads.Clear();
nsCOMPtr<nsIInputStream> stream;
- rv = NS_NewByteInputStream(getter_AddRefs(stream),
- reinterpret_cast<const char*>(aData), aDataLen,
- NS_ASSIGNMENT_DEPEND);
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ payload.data.get(),
+ payload.data.Length(),
+ NS_ASSIGNMENT_DEPEND);
NS_ENSURE_SUCCESS(rv, rv);
// decode image
nsCOMPtr<imgIContainer> container;
- rv = GetImgTools()->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
+ rv = GetImgTools()->DecodeImageData(stream, payload.mimeType,
+ getter_AddRefs(container));
NS_ENSURE_SUCCESS(rv, rv);
- aNewMimeType.AssignLiteral(PNG_MIME_TYPE);
+ IconPayload newPayload;
+ newPayload.mimeType = NS_LITERAL_CSTRING(PNG_MIME_TYPE);
+ // TODO: for ico files we should extract every single payload.
+ int32_t width;
+ rv = container->GetWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t height;
+ rv = container->GetHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // For non-square images, pick the largest side.
+ int32_t originalSize = std::max(width, height);
+ newPayload.width = originalSize;
+ for (uint16_t size : sFaviconSizes) {
+ if (size <= originalSize) {
+ newPayload.width = size;
+ break;
+ }
+ }
- // scale and recompress
- nsCOMPtr<nsIInputStream> iconStream;
- rv = GetImgTools()->EncodeScaledImage(container, aNewMimeType,
- DEFAULT_FAVICON_SIZE,
- DEFAULT_FAVICON_SIZE,
- EmptyString(),
- getter_AddRefs(iconStream));
- NS_ENSURE_SUCCESS(rv, rv);
+ // If the original payload is png and the size is the same, no reason to
+ // rescale the image.
+ if (newPayload.mimeType.Equals(payload.mimeType) &&
+ newPayload.width == originalSize) {
+ newPayload.data = payload.data;
+ } else {
+ // scale and recompress
+ nsCOMPtr<nsIInputStream> iconStream;
+ rv = GetImgTools()->EncodeScaledImage(container,
+ newPayload.mimeType,
+ newPayload.width,
+ newPayload.width,
+ EmptyString(),
+ getter_AddRefs(iconStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Read the stream into the new buffer.
+ rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
- // Read the stream into a new buffer.
- rv = NS_ConsumeStream(iconStream, UINT32_MAX, aNewData);
- NS_ENSURE_SUCCESS(rv, rv);
+ if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ aIcon.payloads.AppendElement(newPayload);
+ }
return NS_OK;
}
nsresult
nsFaviconService::GetFaviconDataAsync(nsIURI* aFaviconURI,
mozIStorageStatementCallback *aCallback)
{
- NS_ASSERTION(aCallback, "Doesn't make sense to call this without a callback");
+ MOZ_ASSERT(aCallback, "Doesn't make sense to call this without a callback");
+
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
- "SELECT f.data, f.mime_type FROM moz_favicons f WHERE url = :icon_url"
+ "SELECT data, width FROM moz_icons "
+ "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) AND icon_url = :url "
+ "ORDER BY width DESC"
);
NS_ENSURE_STATE(stmt);
// Ignore the ref part of the URI before querying the database because
// we may have added a media fragment for rendering purposes.
-
nsAutoCString faviconURI;
aFaviconURI->GetSpecIgnoringRef(faviconURI);
- nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), faviconURI);
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), faviconURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
}
void // static
nsFaviconService::ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn)
--- a/toolkit/components/places/nsFaviconService.h
+++ b/toolkit/components/places/nsFaviconService.h
@@ -23,22 +23,16 @@
#include "mozilla/Attributes.h"
#include "FaviconHelpers.h"
// The target dimension in pixels for favicons we store, in reverse order.
static uint16_t sFaviconSizes[8] = {
256, 192, 144, 96, 64, 48, 32, 16
};
-// Default size when preferred size is unknown, doubled for hi-dpi.
-#define DEFAULT_FAVICON_SIZE 32
-
-// Favicons bigger than this (in bytes) will not be stored in the database. We
-// expect that most 32x32 PNG favicons will be no larger due to compression.
-#define MAX_FAVICON_FILESIZE 3072 /* 3 KiB */
// forward class definitions
class mozIStorageStatementCallback;
class UnassociatedIconHashKey : public nsURIHashKey
{
public:
explicit UnassociatedIconHashKey(const nsIURI* aURI)
@@ -90,19 +84,17 @@ public:
* Fetch and migrate favicons from an unsupported payload to a supported one.
*/
static void ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn);
// addition to API for strings to prevent excessive parsing of URIs
nsresult GetFaviconLinkForIconString(const nsCString& aIcon, nsIURI** aOutput);
void GetFaviconSpecForIconString(const nsCString& aIcon, nsACString& aOutput);
- nsresult OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
- const nsACString& aMimeType,
- nsACString& aNewData, nsACString& aNewMimeType);
+ nsresult OptimizeIconSizes(mozilla::places::IconData& aIcon);
/**
* Obtains the favicon data asynchronously.
*
* @param aFaviconURI
* The URI representing the favicon we are looking for.
* @param aCallback
* The callback where results or errors will be dispatch to. In the
--- a/toolkit/components/places/nsIFaviconService.idl
+++ b/toolkit/components/places/nsIFaviconService.idl
@@ -14,17 +14,17 @@ interface nsIFaviconService : nsISupport
const unsigned long FAVICON_LOAD_PRIVATE = 1;
// The favicon is being loaded from a non-private browsing window
const unsigned long FAVICON_LOAD_NON_PRIVATE = 2;
/**
* The limit in bytes of the size of favicons in memory and passed via the
* favicon protocol.
*/
- const unsigned long MAX_FAVICON_BUFFER_SIZE = 10240;
+ const unsigned long MAX_FAVICON_BUFFER_SIZE = 35840;
/**
* For a given icon URI, this will return a URI that will result in the image.
* In most cases, this is an annotation URI. For chrome URIs, this will do
* nothing but returning the input URI.
*
* No validity checking is done. If you pass an icon URI that we've never
* seen, you'll get back a URI that references an invalid icon. The moz-anno
@@ -116,25 +116,29 @@ interface nsIFaviconDataCallback : nsISu
* favicon URI, or the callback is notifying a failure.
* @param aDataLen
* Size of the icon data in bytes. Notice that a value of 0 does not
* necessarily mean that we don't have an icon.
* @param aData
* Icon data, or an empty array if aDataLen is 0.
* @param aMimeType
* Mime type of the icon, or an empty string if aDataLen is 0.
+ * @param aWidth
+ * Width of the icon. 0 if the width is unknown or if the icon is
+ * vectorial.
*
* @note If you want to open a network channel to access the favicon, it's
* recommended that you call the getFaviconLinkForIcon method to convert
* the "favicon URI" into a "favicon link URI".
*/
void onComplete(in nsIURI aFaviconURI,
in unsigned long aDataLen,
[const,array,size_is(aDataLen)] in octet aData,
- in AUTF8String aMimeType);
+ in AUTF8String aMimeType,
+ in unsigned short aWidth);
};
%{C++
/**
* Notification sent when all favicons are expired.
*/
#define NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID "places-favicons-expired"
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -1103,22 +1103,21 @@ nsNavBookmarks::GetDescendantChildren(in
// Collect children informations.
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match
// kGetInfoIndex_* order, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
- "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
"b.guid, b.position, b.type, b.fk, b.syncStatus "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE b.parent = :parent "
"ORDER BY b.position ASC"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2142,22 +2141,21 @@ nsNavBookmarks::QueryFolderChildren(
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match those returned
// by mDBGetURLPageInfo, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
- "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
"b.guid, b.position, b.type, b.fk "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE b.parent = :parent "
"ORDER BY b.position ASC"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2280,22 +2278,21 @@ nsNavBookmarks::QueryFolderChildrenAsync
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match those returned
// by mDBGetURLPageInfo, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
"SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
- "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
"b.guid, b.position, b.type, b.fk "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE b.parent = :parent "
"ORDER BY b.position ASC"
);
NS_ENSURE_STATE(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -1275,17 +1275,17 @@ nsNavHistory::ExecuteQueries(nsINavHisto
// This is a perf hack to generate an empty query that skips filtering.
options->SetExcludeItems(true);
}
}
if (!rootNode) {
// Either this is not a folder shortcut, or is a broken one. In both cases
// just generate a query node.
- rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), EmptyCString(),
+ rootNode = new nsNavHistoryQueryResultNode(EmptyCString(),
queries, options);
}
// Create the result that will hold nodes. Inject batching status into it.
RefPtr<nsNavHistoryResult> result;
rv = nsNavHistoryResult::NewHistoryResult(aQueries, aQueryCount, options,
rootNode, isBatching(),
getter_AddRefs(result));
@@ -1512,21 +1512,20 @@ PlacesSQLQueryBuilder::SelectAsURI()
case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
GetTagsSqlFragment(history->GetTagsFolder(),
NS_LITERAL_CSTRING("h.id"),
mHasSearchTerms,
tagsSqlFragment);
mQueryString = NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
- "h.last_visit_date, f.url, null, null, null, null, ") +
+ "h.last_visit_date, null, null, null, null, null, ") +
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null "
"FROM moz_places h "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
// WHERE 1 is a no-op since additonal conditions will start with AND.
"WHERE 1 "
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
"{ADDITIONAL_CONDITIONS} ");
break;
case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
@@ -1538,48 +1537,46 @@ PlacesSQLQueryBuilder::SelectAsURI()
GetTagsSqlFragment(history->GetTagsFolder(),
NS_LITERAL_CSTRING("b2.fk"),
mHasSearchTerms,
tagsSqlFragment);
mQueryString = NS_LITERAL_CSTRING(
"SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, "
- "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, "
+ "h.rev_host, h.visit_count, h.last_visit_date, null, b2.id, "
"b2.dateAdded, b2.lastModified, b2.parent, ") +
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null, b2.guid, b2.position, b2.type, b2.fk "
"FROM moz_bookmarks b2 "
"JOIN (SELECT b.fk "
"FROM moz_bookmarks b "
// ADDITIONAL_CONDITIONS will filter on parent.
"WHERE b.type = 1 {ADDITIONAL_CONDITIONS} "
") AS seed ON b2.fk = seed.fk "
"JOIN moz_places h ON h.id = b2.fk "
- "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE NOT EXISTS ( "
"SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") +
nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
NS_LITERAL_CSTRING(") "
"ORDER BY b2.fk DESC, b2.lastModified DESC");
}
else {
GetTagsSqlFragment(history->GetTagsFolder(),
NS_LITERAL_CSTRING("b.fk"),
mHasSearchTerms,
tagsSqlFragment);
mQueryString = NS_LITERAL_CSTRING(
"SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, "
- "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
+ "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
"b.dateAdded, b.lastModified, b.parent, ") +
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid,"
"null, null, null, b.guid, b.position, b.type, b.fk "
"FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
- "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE NOT EXISTS "
"(SELECT id FROM moz_bookmarks "
"WHERE id = b.parent AND parent = ") +
nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
NS_LITERAL_CSTRING(") "
"{ADDITIONAL_CONDITIONS}");
}
break;
@@ -1597,22 +1594,21 @@ PlacesSQLQueryBuilder::SelectAsVisit()
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
nsAutoCString tagsSqlFragment;
GetTagsSqlFragment(history->GetTagsFolder(),
NS_LITERAL_CSTRING("h.id"),
mHasSearchTerms,
tagsSqlFragment);
mQueryString = NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
- "v.visit_date, f.url, null, null, null, null, ") +
+ "v.visit_date, null, null, null, null, null, ") +
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"v.id, v.from_visit, v.visit_type "
"FROM moz_places h "
"JOIN moz_historyvisits v ON h.id = v.place_id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
// WHERE 1 is a no-op since additonal conditions will start with AND.
"WHERE 1 "
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
"{ADDITIONAL_CONDITIONS} ");
return NS_OK;
}
@@ -2126,21 +2122,20 @@ nsNavHistory::ConstructQueryString(
if (IsOptimizableHistoryQuery(aQueries, aOptions,
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) ||
IsOptimizableHistoryQuery(aQueries, aOptions,
nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
// Generate an optimized query for the history menu and most visited
// smart bookmark.
queryString = NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, "
- "f.url, null, null, null, null, ") +
+ "null, null, null, null, null, ") +
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null "
"FROM moz_places h "
- "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE h.hidden = 0 "
"AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id "
"AND visit_type NOT IN ") +
nsPrintfCString("(0,%d,%d) ",
nsINavHistoryService::TRANSITION_EMBED,
nsINavHistoryService::TRANSITION_FRAMED_LINK) +
NS_LITERAL_CSTRING("LIMIT 1) "
"{QUERY_OPTIONS} "
@@ -3792,21 +3787,16 @@ nsNavHistory::RowToResult(mozIStorageVal
// title
nsAutoCString title;
rv = aRow->GetUTF8String(kGetInfoIndex_Title, title);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount);
PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate);
- // favicon
- nsAutoCString favicon;
- rv = aRow->GetUTF8String(kGetInfoIndex_FaviconURL, favicon);
- NS_ENSURE_SUCCESS(rv, rv);
-
// itemId
int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId);
int64_t parentId = -1;
if (itemId == 0) {
// This is not a bookmark. For non-bookmarks we use a -1 itemId value.
// Notice ids in sqlite tables start from 1, so itemId cannot ever be 0.
itemId = -1;
}
@@ -3837,17 +3827,17 @@ nsNavHistory::RowToResult(mozIStorageVal
nsAutoCString guid;
if (itemId != -1) {
rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
NS_ENSURE_SUCCESS(rv, rv);
}
RefPtr<nsNavHistoryResultNode> resultNode;
- rv = QueryRowToResult(itemId, guid, url, title, accessCount, time, favicon,
+ rv = QueryRowToResult(itemId, guid, url, title, accessCount, time,
getter_AddRefs(resultNode));
NS_ENSURE_SUCCESS(rv, rv);
if (itemId != -1 ||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
// RESULTS_AS_TAG_QUERY has date columns
resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
@@ -3859,17 +3849,17 @@ nsNavHistory::RowToResult(mozIStorageVal
}
}
resultNode.forget(aResult);
return rv;
} else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI ||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
RefPtr<nsNavHistoryResultNode> resultNode =
- new nsNavHistoryResultNode(url, title, accessCount, time, favicon);
+ new nsNavHistoryResultNode(url, title, accessCount, time);
if (itemId != -1) {
resultNode->mItemId = itemId;
resultNode->mFolderId = parentId;
resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
@@ -3891,17 +3881,17 @@ nsNavHistory::RowToResult(mozIStorageVal
NS_ENSURE_SUCCESS(rv, rv);
resultNode.forget(aResult);
return NS_OK;
}
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
RefPtr<nsNavHistoryResultNode> resultNode =
- new nsNavHistoryResultNode(url, title, accessCount, time, favicon);
+ new nsNavHistoryResultNode(url, title, accessCount, time);
nsAutoString tags;
rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
if (!tags.IsVoid())
resultNode->mTags.Assign(tags);
rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
NS_ENSURE_SUCCESS(rv, rv);
@@ -3932,18 +3922,18 @@ nsNavHistory::RowToResult(mozIStorageVal
// Called by RowToResult when the URI is a place: URI to generate the proper
// folder or query node.
nsresult
nsNavHistory::QueryRowToResult(int64_t itemId,
const nsACString& aBookmarkGuid,
const nsACString& aURI,
const nsACString& aTitle,
- uint32_t aAccessCount, PRTime aTime,
- const nsACString& aFavicon,
+ uint32_t aAccessCount,
+ PRTime aTime,
nsNavHistoryResultNode** aNode)
{
MOZ_ASSERT((itemId != -1 && !aBookmarkGuid.IsEmpty()) ||
(itemId == -1 && aBookmarkGuid.IsEmpty()));
nsCOMArray<nsNavHistoryQuery> queries;
nsCOMPtr<nsNavHistoryQueryOptions> options;
nsresult rv = QueryStringToQueryArray(aURI, &queries,
@@ -3976,28 +3966,27 @@ nsNavHistory::QueryRowToResult(int64_t i
// concrete folder title).
if (!aTitle.IsVoid()) {
resultNode->mTitle = aTitle;
}
}
}
else {
// This is a regular query.
- resultNode = new nsNavHistoryQueryResultNode(aTitle, EmptyCString(),
- aTime, queries, options);
+ resultNode = new nsNavHistoryQueryResultNode(aTitle, aTime, queries, options);
resultNode->mItemId = itemId;
}
}
if (NS_FAILED(rv)) {
NS_WARNING("Generating a generic empty node for a broken query!");
// This is a broken query, that either did not parse or points to not
// existing data. We don't want to return failure since that will kill the
// whole result. Instead make a generic empty query node.
- resultNode = new nsNavHistoryQueryResultNode(aTitle, aFavicon, aURI);
+ resultNode = new nsNavHistoryQueryResultNode(aTitle, aURI);
resultNode->mItemId = itemId;
// This is a perf hack to generate an empty query that skips filtering.
resultNode->GetAsQuery()->Options()->SetExcludeItems(true);
}
resultNode.forget(aNode);
return NS_OK;
}
@@ -4021,37 +4010,35 @@ nsNavHistory::VisitIdToResultNode(int64_
switch (aOptions->ResultType())
{
case nsNavHistoryQueryOptions::RESULTS_AS_VISIT:
case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT:
// visit query - want exact visit time
// Should match kGetInfoIndex_* (see GetQueryResults)
statement = mDB->GetStatement(NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
- "v.visit_date, f.url, null, null, null, null, "
+ "v.visit_date, null, null, null, null, null, "
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"v.id, v.from_visit, v.visit_type "
"FROM moz_places h "
"JOIN moz_historyvisits v ON h.id = v.place_id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE v.id = :visit_id ")
);
break;
case nsNavHistoryQueryOptions::RESULTS_AS_URI:
// URL results - want last visit time
// Should match kGetInfoIndex_* (see GetQueryResults)
statement = mDB->GetStatement(NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
- "h.last_visit_date, f.url, null, null, null, null, "
+ "h.last_visit_date, null, null, null, null, null, "
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null "
"FROM moz_places h "
"JOIN moz_historyvisits v ON h.id = v.place_id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE v.id = :visit_id ")
);
break;
default:
// Query base types like RESULTS_AS_*_QUERY handle additions
// by registering their own observers when they are expanded.
return NS_OK;
@@ -4082,23 +4069,22 @@ nsNavHistory::BookmarkIdToResultNode(int
nsNavHistoryResultNode** aResult)
{
nsAutoCString tagsFragment;
GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
true, tagsFragment);
// Should match kGetInfoIndex_*
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
"SELECT b.fk, h.url, COALESCE(b.title, h.title), "
- "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
+ "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
"b.dateAdded, b.lastModified, b.parent, "
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null, b.guid, b.position, b.type, b.fk "
"FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE b.id = :item_id ")
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
aBookmarkId);
NS_ENSURE_SUCCESS(rv, rv);
@@ -4123,23 +4109,22 @@ nsNavHistory::URIToResultNode(nsIURI* aU
nsNavHistoryResultNode** aResult)
{
nsAutoCString tagsFragment;
GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
true, tagsFragment);
// Should match kGetInfoIndex_*
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
"SELECT h.id, :page_url, COALESCE(b.title, h.title), "
- "h.rev_host, h.visit_count, h.last_visit_date, f.url, "
+ "h.rev_host, h.visit_count, h.last_visit_date, null, "
"b.id, b.dateAdded, b.lastModified, b.parent, "
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null, b.guid, b.position, b.type, b.fk "
"FROM moz_places h "
"LEFT JOIN moz_bookmarks b ON b.fk = h.id "
- "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
"WHERE h.url_hash = hash(:page_url) AND h.url = :page_url ")
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -253,18 +253,18 @@ public:
// The row must contain the full set of columns.
nsresult RowToResult(mozIStorageValueArray* aRow,
nsNavHistoryQueryOptions* aOptions,
nsNavHistoryResultNode** aResult);
nsresult QueryRowToResult(int64_t aItemId,
const nsACString& aBookmarkGuid,
const nsACString& aURI,
const nsACString& aTitle,
- uint32_t aAccessCount, PRTime aTime,
- const nsACString& aFavicon,
+ uint32_t aAccessCount,
+ PRTime aTime,
nsNavHistoryResultNode** aNode);
nsresult VisitIdToResultNode(int64_t visitId,
nsNavHistoryQueryOptions* aOptions,
nsNavHistoryResultNode** aResult);
nsresult BookmarkIdToResultNode(int64_t aBookmarkId,
nsNavHistoryQueryOptions* aOptions,
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -83,24 +83,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
nsNavHistoryResultNode::nsNavHistoryResultNode(
const nsACString& aURI, const nsACString& aTitle, uint32_t aAccessCount,
- PRTime aTime, const nsACString& aIconURI) :
+ PRTime aTime) :
mParent(nullptr),
mURI(aURI),
mTitle(aTitle),
mAreTagsSorted(false),
mAccessCount(aAccessCount),
mTime(aTime),
- mFaviconURI(aIconURI),
mBookmarkIndex(-1),
mItemId(-1),
mFolderId(-1),
mVisitId(-1),
mFromVisitId(-1),
mDateAdded(0),
mLastModified(0),
mIndentLevel(-1),
@@ -110,24 +109,23 @@ nsNavHistoryResultNode::nsNavHistoryResu
{
mTags.SetIsVoid(true);
}
NS_IMETHODIMP
nsNavHistoryResultNode::GetIcon(nsACString& aIcon)
{
- if (mFaviconURI.IsEmpty()) {
- aIcon.Truncate();
+ if (this->IsContainer() || mURI.IsEmpty()) {
return NS_OK;
}
- nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
- NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
- faviconService->GetFaviconSpecForIconString(mFaviconURI, aIcon);
+ aIcon.AppendLiteral("page-icon:");
+ aIcon.Append(mURI);
+
return NS_OK;
}
NS_IMETHODIMP
nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent)
{
NS_IF_ADDREF(*aParent = mParent);
@@ -340,34 +338,32 @@ NS_IMPL_ADDREF_INHERITED(nsNavHistoryCon
NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode)
NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
- const nsACString& aURI, const nsACString& aTitle,
- const nsACString& aIconURI, uint32_t aContainerType,
+ const nsACString& aURI, const nsACString& aTitle, uint32_t aContainerType,
nsNavHistoryQueryOptions* aOptions) :
- nsNavHistoryResultNode(aURI, aTitle, 0, 0, aIconURI),
+ nsNavHistoryResultNode(aURI, aTitle, 0, 0),
mResult(nullptr),
mContainerType(aContainerType),
mExpanded(false),
mOptions(aOptions),
mAsyncCanceledState(NOT_CANCELED)
{
}
nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
const nsACString& aURI, const nsACString& aTitle,
- PRTime aTime,
- const nsACString& aIconURI, uint32_t aContainerType,
+ PRTime aTime, uint32_t aContainerType,
nsNavHistoryQueryOptions* aOptions) :
- nsNavHistoryResultNode(aURI, aTitle, 0, aTime, aIconURI),
+ nsNavHistoryResultNode(aURI, aTitle, 0, aTime),
mResult(nullptr),
mContainerType(aContainerType),
mExpanded(false),
mOptions(aOptions),
mAsyncCanceledState(NOT_CANCELED)
{
}
@@ -1758,33 +1754,31 @@ nsNavHistoryContainerResultNode::FindNod
* a message without doing a requery. For complex changes or complex queries,
* we give up and requery.
*/
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
nsNavHistoryContainerResultNode,
nsINavHistoryQueryResultNode)
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
- const nsACString& aTitle, const nsACString& aIconURI,
- const nsACString& aQueryURI) :
- nsNavHistoryContainerResultNode(aQueryURI, aTitle, aIconURI,
+ const nsACString& aTitle, const nsACString& aQueryURI) :
+ nsNavHistoryContainerResultNode(aQueryURI, aTitle,
nsNavHistoryResultNode::RESULT_TYPE_QUERY,
nullptr),
mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
mHasSearchTerms(false),
mContentsValid(false),
mBatchChanges(0)
{
}
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
- const nsACString& aTitle, const nsACString& aIconURI,
- const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ const nsACString& aTitle, const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsNavHistoryQueryOptions* aOptions) :
- nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aIconURI,
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle,
nsNavHistoryResultNode::RESULT_TYPE_QUERY,
aOptions),
mQueries(aQueries),
mContentsValid(false),
mBatchChanges(0),
mTransitions(mQueries[0]->Transitions())
{
NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
@@ -1803,21 +1797,21 @@ nsNavHistoryQueryResultNode::nsNavHistor
uint32_t transition = mTransitions.SafeElementAt(j, 0);
if (transition && !queryTransitions.Contains(transition))
mTransitions.RemoveElement(transition);
}
}
}
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
- const nsACString& aTitle, const nsACString& aIconURI,
+ const nsACString& aTitle,
PRTime aTime,
const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsNavHistoryQueryOptions* aOptions) :
- nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime, aIconURI,
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime,
nsNavHistoryResultNode::RESULT_TYPE_QUERY,
aOptions),
mQueries(aQueries),
mContentsValid(false),
mBatchChanges(0),
mTransitions(mQueries[0]->Transitions())
{
NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
@@ -2710,22 +2704,19 @@ nsNavHistoryQueryResultNode::OnClearHist
{
nsresult rv = Refresh();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
static nsresult setFaviconCallback(nsNavHistoryResultNode* aNode,
- const void* aClosure,
+ const void * aClosure,
const nsNavHistoryResult* aResult)
{
- const nsCString* newFavicon = static_cast<const nsCString*>(aClosure);
- aNode->mFaviconURI = *newFavicon;
-
if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
return NS_OK;
}
NS_IMETHODIMP
@@ -2735,23 +2726,22 @@ nsNavHistoryQueryResultNode::OnPageChang
const nsACString& aGUID)
{
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
switch (aChangedAttribute) {
case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
- NS_ConvertUTF16toUTF8 newFavicon(aNewValue);
bool onlyOneEntry = (mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_URI ||
mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
- &newFavicon);
+ nullptr);
break;
}
default:
NS_WARNING("Unknown page changed notification");
}
return NS_OK;
}
@@ -3026,17 +3016,17 @@ nsNavHistoryQueryResultNode::OnItemMoved
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryFolderResultNode,
nsNavHistoryContainerResultNode,
nsINavHistoryQueryResultNode,
mozIStorageStatementCallback)
nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
int64_t aFolderId) :
- nsNavHistoryContainerResultNode(EmptyCString(), aTitle, EmptyCString(),
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle,
nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
aOptions),
mContentsValid(false),
mTargetFolderItemId(aFolderId),
mIsRegisteredFolderObserver(false)
{
mItemId = aFolderId;
}
@@ -3761,17 +3751,16 @@ nsNavHistoryResultNode::OnItemChanged(in
else if (aProperty.EqualsLiteral("uri")) {
// clear the tags string as well
mTags.SetIsVoid(true);
mURI = aNewValue;
if (shouldNotify)
NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, mURI));
}
else if (aProperty.EqualsLiteral("favicon")) {
- mFaviconURI = aNewValue;
if (shouldNotify)
NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
}
else if (aProperty.EqualsLiteral("cleartime")) {
mTime = 0;
if (shouldNotify) {
NOTIFY_RESULT_OBSERVERS(result,
NodeHistoryDetailsChanged(this, 0, mAccessCount));
@@ -4002,17 +3991,17 @@ nsNavHistoryFolderResultNode::OnItemMove
}
/**
* Separator nodes do not hold any data.
*/
nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode()
: nsNavHistoryResultNode(EmptyCString(), EmptyCString(),
- 0, 0, EmptyCString())
+ 0, 0)
{
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsNavHistoryResult)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNavHistoryResult)
tmp->StopObserving();
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -252,18 +252,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
{ return nsNavHistoryResultNode::GetFromVisitId(aFromVisitId); } \
NS_IMETHOD GetVisitType(uint32_t* aVisitType) override \
{ return nsNavHistoryResultNode::GetVisitType(aVisitType); }
class nsNavHistoryResultNode : public nsINavHistoryResultNode
{
public:
nsNavHistoryResultNode(const nsACString& aURI, const nsACString& aTitle,
- uint32_t aAccessCount, PRTime aTime,
- const nsACString& aIconURI);
+ uint32_t aAccessCount, PRTime aTime);
NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULTNODE_IID)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(nsNavHistoryResultNode)
NS_IMPLEMENT_SIMPLE_RESULTNODE
NS_IMETHOD GetIcon(nsACString& aIcon) override;
@@ -362,17 +361,16 @@ public:
RefPtr<nsNavHistoryContainerResultNode> mParent;
nsCString mURI; // not necessarily valid for containers, call GetUri
nsCString mTitle;
nsString mTags;
bool mAreTagsSorted;
uint32_t mAccessCount;
int64_t mTime;
- nsCString mFaviconURI;
int32_t mBookmarkIndex;
int64_t mItemId;
int64_t mFolderId;
int64_t mVisitId;
int64_t mFromVisitId;
PRTime mDateAdded;
PRTime mLastModified;
@@ -431,23 +429,20 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
{ 0x6e3bf8d3, 0x22aa, 0x4065, { 0x86, 0xbc, 0x37, 0x46, 0xb5, 0xb3, 0x2c, 0xe8 } }
class nsNavHistoryContainerResultNode : public nsNavHistoryResultNode,
public nsINavHistoryContainerResultNode
{
public:
nsNavHistoryContainerResultNode(
const nsACString& aURI, const nsACString& aTitle,
- const nsACString& aIconURI, uint32_t aContainerType,
- nsNavHistoryQueryOptions* aOptions);
+ uint32_t aContainerType, nsNavHistoryQueryOptions* aOptions);
nsNavHistoryContainerResultNode(
const nsACString& aURI, const nsACString& aTitle,
- PRTime aTime,
- const nsACString& aIconURI, uint32_t aContainerType,
- nsNavHistoryQueryOptions* aOptions);
+ PRTime aTime, uint32_t aContainerType, nsNavHistoryQueryOptions* aOptions);
virtual nsresult Refresh();
NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYCONTAINERRESULTNODE_IID)
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
NS_FORWARD_COMMON_RESULTNODE_TO_BASE
@@ -620,24 +615,21 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
// bookmark notifications.
class nsNavHistoryQueryResultNode final : public nsNavHistoryContainerResultNode,
public nsINavHistoryQueryResultNode,
public nsINavBookmarkObserver
{
public:
nsNavHistoryQueryResultNode(const nsACString& aTitle,
- const nsACString& aIconURI,
const nsACString& aQueryURI);
nsNavHistoryQueryResultNode(const nsACString& aTitle,
- const nsACString& aIconURI,
const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsNavHistoryQueryOptions* aOptions);
nsNavHistoryQueryResultNode(const nsACString& aTitle,
- const nsACString& aIconURI,
PRTime aTime,
const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsNavHistoryQueryOptions* aOptions);
NS_DECL_ISUPPORTS_INHERITED
NS_FORWARD_COMMON_RESULTNODE_TO_BASE
NS_IMETHOD GetType(uint32_t* type) override
{ *type = nsNavHistoryResultNode::RESULT_TYPE_QUERY; return NS_OK; }
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -264,25 +264,34 @@ const EXPIRATION_QUERIES = {
// (see nsPlacesTriggers.h).
QUERY_UPDATE_HOSTS: {
sql: `DELETE FROM moz_updatehosts_temp`,
actions: ACTION.CLEAR_HISTORY | ACTION.TIMED | ACTION.TIMED_OVERLIMIT |
ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
ACTION.DEBUG
},
+ // Expire orphan pages from the icons database.
+ QUERY_EXPIRE_FAVICONS_PAGES: {
+ sql: `DELETE FROM moz_pages_w_icons
+ WHERE page_url_hash NOT IN (
+ SELECT url_hash FROM moz_places
+ )`,
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
// Expire orphan icons from the database.
QUERY_EXPIRE_FAVICONS: {
- sql: `DELETE FROM moz_favicons WHERE id IN (
- SELECT f.id FROM moz_favicons f
- LEFT JOIN moz_places h ON f.id = h.favicon_id
- WHERE h.favicon_id IS NULL
- LIMIT :limit_favicons
+ sql: `DELETE FROM moz_icons
+ WHERE id NOT IN (
+ SELECT icon_id FROM moz_icons_to_pages
)`,
- actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
ACTION.DEBUG
},
// Expire orphan page annotations from the database.
QUERY_EXPIRE_ANNOS: {
sql: `DELETE FROM moz_annos WHERE id in (
SELECT a.id FROM moz_annos a
@@ -991,19 +1000,16 @@ nsPlacesExpiration.prototype = {
aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit;
break;
case "QUERY_FIND_URIS_TO_EXPIRE":
params.limit_uris = baseLimit;
break;
case "QUERY_SILENT_EXPIRE_ORPHAN_URIS":
params.limit_uris = baseLimit;
break;
- case "QUERY_EXPIRE_FAVICONS":
- params.limit_favicons = baseLimit;
- break;
case "QUERY_EXPIRE_ANNOS":
// Each page may have multiple annos.
params.limit_annos = baseLimit * EXPIRE_AGGRESSIVITY_MULTIPLIER;
break;
case "QUERY_EXPIRE_ANNOS_WITH_POLICY":
case "QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY":
let microNow = Date.now() * 1000;
ANNOS_EXPIRE_POLICIES.forEach(function(policy) {
--- a/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
+++ b/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
@@ -33,29 +33,29 @@ function test() {
// This function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
function checkFavIconsDBCount(aCallback) {
- let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
+ let stmt = DBConn().createAsyncStatement("SELECT icon_url FROM moz_icons");
stmt.executeAsync({
handleResult: function final_handleResult(aResultSet) {
while (aResultSet.getNextRow()) {
favIconsResultCount++;
}
},
handleError: function final_handleError(aError) {
throw ("Unexpected error (" + aError.result + "): " + aError.message);
},
handleCompletion: function final_handleCompletion(aReason) {
// begin testing
- info("Previous records in moz_favicons: " + favIconsResultCount);
+ info("Previous records in moz_icons: " + favIconsResultCount);
if (aCallback) {
aCallback();
}
}
});
stmt.finalize();
}
@@ -168,21 +168,21 @@ function test() {
function testFinalVerification(aWindow, aCallback) {
// Only the last test should raise the onPageChanged notification,
// executing the waitForFaviconChanged callback.
waitForFaviconChanged(lastPageURI, favIcon32URI, aWindow,
function final_callback() {
// Check that only one record corresponding to the last favicon is present.
let resultCount = 0;
- let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
+ let stmt = DBConn().createAsyncStatement("SELECT icon_url FROM moz_icons");
stmt.executeAsync({
handleResult: function final_handleResult(aResultSet) {
- // If the moz_favicons DB had been previously loaded (before our
+ // If the moz_icons DB had been previously loaded (before our
// test began), we should focus only in the URI we are testing and
// skip the URIs not related to our test.
if (favIconsResultCount > 0) {
for (let row; (row = aResultSet.getNextRow()); ) {
if (favIcon32URI.spec === row.getResultByIndex(0)) {
is(favIcon32URI.spec, row.getResultByIndex(0),
"Check equal favicons");
resultCount++;
--- a/toolkit/components/places/tests/chrome/chrome.ini
+++ b/toolkit/components/places/tests/chrome/chrome.ini
@@ -1,12 +1,13 @@
[DEFAULT]
+support-files = head.js
[test_303567.xul]
[test_341972a.xul]
[test_341972b.xul]
[test_342484.xul]
[test_371798.xul]
[test_381357.xul]
[test_favicon_annotations.xul]
[test_reloadLivemarks.xul]
[test_browser_disableglobalhistory.xul]
-support-files = browser_disableglobalhistory.xul
\ No newline at end of file
+support-files = browser_disableglobalhistory.xul
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/chrome/head.js
@@ -0,0 +1,10 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
--- a/toolkit/components/places/tests/chrome/test_favicon_annotations.xul
+++ b/toolkit/components/places/tests/chrome/test_favicon_annotations.xul
@@ -12,153 +12,127 @@
onload="test();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="head.js" />
<script type="application/javascript">
<![CDATA[
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
-let fs = Cc["@mozilla.org/browser/favicon-service;1"].
- getService(Ci.nsIFaviconService);
-
-// Test descriptions that will be printed in the case of failure.
-let testDescriptions = [
- "moz-anno URI with no data in the database loads default icon",
- "URI added to the database is properly loaded",
-];
+let tests = [
+ {
+ desc: "moz-anno URI with no data in the database loads default icon",
+ url: "http://mozilla.org/2009/made-up-favicon/places-rocks/",
+ expectedIcon: PlacesUtils.favicons.defaultFavicon.spec,
+ },
+ {
+ desc: "URI added to the database is properly loaded",
+ url: "http://mozilla.org/should-be-barney/",
+ expectedIcon: "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%04gAMA%00%00%AF%C87%05%8A%E9%00%00%00%19tEXtSoftware%00Adobe%20ImageReadyq%C9e%3C%00%00%01%D6IDATx%DAb%FC%FF%FF%3F%03%25%00%20%80%98%909%EF%DF%BFg%EF%EC%EC%FC%AD%AC%AC%FC%DF%95%91%F1%BF%89%89%C9%7F%20%FF%D7%EA%D5%AB%B7%DF%BBwO%16%9B%01%00%01%C4%00r%01%08%9F9s%C6%CD%D8%D8%F8%BF%0B%03%C3%FF3%40%BC%0A%88%EF%02q%1A%10%BB%40%F1%AAU%ABv%C1%D4%C30%40%00%81%89%993g%3E%06%1A%F6%3F%14%AA%11D%97%03%F1%7Fc%08%0D%E2%2B))%FD%17%04%89%A1%19%00%10%40%0C%D00%F8%0F3%00%C8%F8%BF%1B%E4%0Ac%88a%E5%60%17%19%FF%0F%0D%0D%05%1B%02v%D9%DD%BB%0A0%03%00%02%08%AC%B9%A3%A3%E3%17%03%D4v%90%01%EF%18%106%C3%0Cz%07%C5%BB%A1%DE%82y%07%20%80%A0%A6%08B%FCn%0C1%60%26%D4%20d%C3VA%C3%06%26%BE%0A%EA-%80%00%82%B9%E0%F7L4%0D%EF%90%F8%C6%60%2F%0A%82%BD%01%13%07%0700%D0%01%02%88%11%E4%02P%B41%DC%BB%C7%D0%014%0D%E8l%06W%20%06%BA%88%A1%1C%1AS%15%40%7C%16%CA6.%2Fgx%BFg%0F%83%CB%D9%B3%0C%7B%80%7C%80%00%02%BB%00%E8%9F%ED%20%1B%3A%A0%A6%9F%81%DA%DC%01%C5%B0%80%ED%80%FA%BF%BC%BC%FC%3F%83%12%90%9D%96%F6%1F%20%80%18%DE%BD%7B%C7%0E%8E%05AD%20%FEGr%A6%A0%A0%E0%7F%25P%80%02%9D%0F%D28%13%18%23%C6%C0%B0%02E%3D%C8%F5%00%01%04%8F%05P%A8%BA%40my%87%E4%12c%A8%8D%20%8B%D0%D3%00%08%03%04%10%9C%01R%E4%82d%3B%C8%A0%99%C6%90%90%C6%A5%19%84%01%02%08%9E%17%80%C9x%F7%7B%A0%DBVC%F9%A0%C0%5C%7D%16%2C%CE%00%F4%C6O%5C%99%09%20%800L%04y%A5%03%1A%95%A0%80%05%05%14.%DBA%18%20%80%18)%CD%CE%00%01%06%00%0C'%94%C7%C0k%C9%2C%00%00%00%00IEND%AEB%60%82",
+ },
-// URIs to load (will be compared with expectedURIs of the same index).
-let testURIs = [
- "http://mozilla.org/2009/made-up-favicon/places-rocks/",
- "http://mozilla.org/should-be-barney/",
];
-// URIs to load for expected results.
-let expectedURIs = [
- fs.defaultFavicon.spec,
- "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%04gAMA%00%00%AF%C87%05%8A%E9%00%00%00%19tEXtSoftware%00Adobe%20ImageReadyq%C9e%3C%00%00%01%D6IDATx%DAb%FC%FF%FF%3F%03%25%00%20%80%98%909%EF%DF%BFg%EF%EC%EC%FC%AD%AC%AC%FC%DF%95%91%F1%BF%89%89%C9%7F%20%FF%D7%EA%D5%AB%B7%DF%BBwO%16%9B%01%00%01%C4%00r%01%08%9F9s%C6%CD%D8%D8%F8%BF%0B%03%C3%FF3%40%BC%0A%88%EF%02q%1A%10%BB%40%F1%AAU%ABv%C1%D4%C30%40%00%81%89%993g%3E%06%1A%F6%3F%14%AA%11D%97%03%F1%7Fc%08%0D%E2%2B))%FD%17%04%89%A1%19%00%10%40%0C%D00%F8%0F3%00%C8%F8%BF%1B%E4%0Ac%88a%E5%60%17%19%FF%0F%0D%0D%05%1B%02v%D9%DD%BB%0A0%03%00%02%08%AC%B9%A3%A3%E3%17%03%D4v%90%01%EF%18%106%C3%0Cz%07%C5%BB%A1%DE%82y%07%20%80%A0%A6%08B%FCn%0C1%60%26%D4%20d%C3VA%C3%06%26%BE%0A%EA-%80%00%82%B9%E0%F7L4%0D%EF%90%F8%C6%60%2F%0A%82%BD%01%13%07%0700%D0%01%02%88%11%E4%02P%B41%DC%BB%C7%D0%014%0D%E8l%06W%20%06%BA%88%A1%1C%1AS%15%40%7C%16%CA6.%2Fgx%BFg%0F%83%CB%D9%B3%0C%7B%80%7C%80%00%02%BB%00%E8%9F%ED%20%1B%3A%A0%A6%9F%81%DA%DC%01%C5%B0%80%ED%80%FA%BF%BC%BC%FC%3F%83%12%90%9D%96%F6%1F%20%80%18%DE%BD%7B%C7%0E%8E%05AD%20%FEGr%A6%A0%A0%E0%7F%25P%80%02%9D%0F%D28%13%18%23%C6%C0%B0%02E%3D%C8%F5%00%01%04%8F%05P%A8%BA%40my%87%E4%12c%A8%8D%20%8B%D0%D3%00%08%03%04%10%9C%01R%E4%82d%3B%C8%A0%99%C6%90%90%C6%A5%19%84%01%02%08%9E%17%80%C9x%F7%7B%A0%DBVC%F9%A0%C0%5C%7D%16%2C%CE%00%F4%C6O%5C%99%09%20%800L%04y%A5%03%1A%95%A0%80%05%05%14.%DBA%18%20%80%18)%CD%CE%00%01%06%00%0C'%94%C7%C0k%C9%2C%00%00%00%00IEND%AEB%60%82",
-];
-
-
/**
* The event listener placed on our test windows used to determine when it is
* safe to compare the two windows.
*/
let _results = [];
function loadEventHandler()
{
_results.push(snapshotWindow(window));
-
loadNextTest();
}
/**
* This runs the comparison.
*/
function compareResults(aIndex, aImage1, aImage2)
{
let [correct, data1, data2] = compareSnapshots(aImage1, aImage2, true);
SimpleTest.ok(correct,
- "Test '" + testDescriptions[aIndex] + "' matches expectations. " +
+ "Test '" + tests[aIndex].desc + "' matches expectations. " +
"Data from window 1 is '" + data1 + "'. " +
"Data from window 2 is '" + data2 + "'");
}
/**
* Loads the next set of URIs to compare against.
*/
let _counter = -1;
function loadNextTest()
{
_counter++;
// If we have no more tests, finish.
- if (_counter / 2 == testDescriptions.length) {
+ if (_counter / 2 == tests.length) {
for (let i = 0; i < _results.length; i = i + 2)
compareResults(i / 2, _results[i], _results[i + 1]);
SimpleTest.finish();
return;
}
let nextURI = function() {
let index = Math.floor(_counter / 2);
if ((_counter % 2) == 0)
- return "moz-anno:favicon:" + testURIs[index];
- return expectedURIs[index];
+ return "moz-anno:favicon:" + tests[index].url;
+ return tests[index].expectedIcon;
}
let img = document.getElementById("favicon");
img.setAttribute("src", nextURI());
}
function test()
{
SimpleTest.waitForExplicitFinish();
- let db = Cc["@mozilla.org/browser/nav-history-service;1"].
- getService(Ci.nsPIPlacesDatabase).
- DBConnection;
+ Task.spawn(function* () {
+ yield PlacesTestUtils.clearHistory();
- // Empty any old favicons
- db.executeSimpleSQL("DELETE FROM moz_favicons");
-
- let ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
- let uri = function(aSpec) {
- return ios.newURI(aSpec);
- };
+ info("Inserting new visit");
+ yield PlacesUtils.history.insert({
+ url: "http://example.com/favicon_annotations",
+ visits: [{
+ transition: PlacesUtils.history.TRANSITIONS.TYPED
+ }]
+ });
- let pageURI = uri("http://example.com/favicon_annotations");
- let history = Cc["@mozilla.org/browser/history;1"]
- .getService(Ci.mozIAsyncHistory);
- history.updatePlaces(
- {
- uri: pageURI,
- visits: [{ transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED,
- visitDate: Date.now() * 1000
- }],
- },
- {
- handleError: function UP_handleError() {
- ok(false, "Unexpected error in adding visit.");
- },
- handleResult: function () {},
- handleCompletion: function UP_handleCompletion() {
- // Set the favicon data. Note that the "moz-anno:" protocol requires
- // the favicon to be stored in the database, but the
- // replaceFaviconDataFromDataURL function will not save the favicon
- // unless it is associated with a page. Thus, we must associate the
- // icon with a page explicitly in order for it to be visible through
- // the protocol.
- var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
- .createInstance(Ci.nsIPrincipal);
+ // Set the favicon data. Note that the "moz-anno:" protocol requires
+ // the favicon to be stored in the database, but the
+ // replaceFaviconDataFromDataURL function will not save the favicon
+ // unless it is associated with a page. Thus, we must associate the
+ // icon with a page explicitly in order for it to be visible through
+ // the protocol.
+ info("Replace favicon data");
+ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ NetUtil.newURI(tests[1].url),
+ tests[1].expectedIcon,
+ (Date.now() + 86400) * 1000,
+ systemPrincipal);
+ info("Set favicon data");
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI("http://example.com/favicon_annotations"),
+ NetUtil.newURI(tests[1].url),
+ true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ systemPrincipal);
- fs.replaceFaviconDataFromDataURL(uri(testURIs[1]), expectedURIs[1],
- (Date.now() + 60 * 60 * 24 * 1000) * 1000,
- systemPrincipal);
-
- fs.setAndFetchFaviconForPage(pageURI, uri(testURIs[1]), true,
- fs.FAVICON_LOAD_NON_PRIVATE,
- null, systemPrincipal);
-
- // And start our test process.
- loadNextTest();
- }
- }
- );
-
-
+ // And start our test process.
+ loadNextTest();
+ }.bind(this));
}
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<img id="favicon" onload="loadEventHandler();"/>
<p id="display"></p>
new file mode 100644
index 0000000000000000000000000000000000000000..e9e520cb6cf6546bd1ec021ee99741681579889a
GIT binary patch
literal 330
zc$@)B0k!^#P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003INkl<ZIE}TE
zv2g=23<NJgh*$-%1=PY8*aA~U?BTNyR0s?BW>mpC;QyPF?2!T+31>LG#o<8dudB62
zN{Nt&2wH3O-gzs$-|s{OA+bp;`&py`lYR%S>kTub?7|{(l_17hBvMMa`_BL~I|1yS
zw-~Ui#HJwUOiGDG;zitA!^~KA0Q&BHfSBNA9tXhP$Luj4&4L4VWOCds`&g%g=k7Bg
zB;1{x^P`gp1e|MICc*Uycx<B9N{KkbmYLCe$K6lSHVNmuBbQn$3Q<ZK4$KU9f0=!g
zxLhuTM9!I7D~o~%v932Bw$OWjvA#{V-~Pl}EAgCs>`8x8y?0^{XYQVJ{tPgKj_}-l
c1ofZw1@I&!G(_A|qyPW_07*qoM6N<$f*u2q)Bpeg
index 9932c18fb87eb07c97aa59a7711f60d98db3cc57..c9db4979a6eb73c5b7313a7655e8ca93e00ce05d
GIT binary patch
literal 79
zc%17D@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=#5`RbLpWw8CnO~N=x>w!cbFlM
cF@=X=(P`<Y(Sn;-0#z`0y85}Sb4q9e0Iwbt^8f$<
index 9f16bef4338110c5775c8e51693c5e5e3e2432d0..0d6c1b14a0306baa609f5d3545b4118a637e4e8c
GIT binary patch
literal 22020
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
HY8Ux`W`BUp
index ed158d16111577ea1503d54d08768a4e93c0dbe0..2756cf0cb3ccf1a2f2371bceb8621550b34346b4
GIT binary patch
literal 10698
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	jHNR7uF-XXIUY%abEIzAmEl_Np;ev&J*
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
D&U^_D
index 585c9e897e69cd3e49629be7d8c6a45d25c98ee2..5ce32d6340794cc25e02d8b304401a8fb2ba0c49
GIT binary patch
literal 851
zc$}rv?`slq0LSqQ@fKslJ+u+=eK4OC4Sz)JM^H_hdg9DfY`a9u4J#41*_ciSk&l9?
z;e#Ia=%|Oqegy4DAj}7Di0p@nb7HnO@qvh&CUX;`Pk%rJ_2BN_ci-1>ci+2vetSUK
z+T7kuQB<p1rMQFb%<ttajdi~N`yxg0+SQ7iM(>+qLUKuL6ZY@1v^q-y9f6i$i&Q0D
zBkO<P5x%Y01+Vss?`8Mush1t+YJr0@YiyZ1xa_0Kudh))&4s6*MS>e&gH6P}&YSu4
z&%+KrpHFi}qj8upB9RC=H1wD-xm*r8IQWP#i9`a)vQENOt5ur&va|D$FtJz+xw{Kn
zEEbWe6l^AwL8jBNjYfm!u5@+5hQncGPY>+={ywt18#W$~BYS&cE0qe(eOz3GjYgx$
z<z?7Hp@3Xkf=#E>$dwh?dc96_PrP2(NF;)snu5*e^GKf$HknK!{eIZv<71lpQLDjj
zY-}J;Phm@?67u8(Hk-{ND-}CoLZJ}N-4*^+SglqZJ36{g7`NMvESDb;MyJyu4-ajG
znVg)&1w0;{WHy_TV`H!mhXXl24y#luk)xxqe!m|#zOVqxvMh3L4%X>(BIoB}H5v_a
zb{5v-@nAw^GFXGbfRspJ?RGm-DutEH<w&s@c6xdm6CIDkS}Ydi))uVG<w9<6!)mo!
z<mM*q#KZ(%puqrZGMSKiJ?zNH2p*`Ez0T8L?CiYS_HK2xkEag~e-r%oH}!|%7mcQ=
Wyt>VJo>t*!q13kq6d(F*&wc@wNoyzo
index e07dabc79378158a2d707d5e41140206593cef94..348bfcf55e548fb317f12c24c11c8d806d26792d
GIT binary patch
literal 877
zc%17D@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$farf!V~<#WAE}&f80d+Uk4^?S<1F
zCpoftvPr5)rc5Yl=9$D}<Y9C|;iN*d3YZfEmAwlS@R;t{c3+`3|L6YJ=zq0s&qZzK
zF9L!;r|bUwm^f+Dq$yLUE?u-pDKs>6<;s<wv9YmIQc{<$T$wU$+O#cOw=P}0Sb6T;
zxi8<n^UKfA|MmN~v4zEqhYuh2^!26H)Ywd(JbC84dFS4|$$9qt`Q{xvW;}iRbj`YT
zX|=Vs`|JLmx^^wBtgLL)_U+S~o10~1WuHEIqLQATZfS3S{^`@yb?es`S69#e{rk6#
zo!zsaKP}g<U;p#-bALNKyC4mbPhY=IegFQw2v=)OUEQ<4e{F&GS5{V@ynH!WPEM|<
zynOoX*|TF}W1l{Hq_n^O|GAehGyD4ciz_N-{Qdj)&AWGL)z#KO6*q6(IC1M%R9INp
zrY&2hw6?aMIeRuWEln*rIC$oa87FSsh;VarGcqxm(%9Je<?Gj_%a^MI!{F!7&*`^r
z-@bh7)~sjGo|%}LE!(%xZtK>qE7z^_OHEBZb^3I0RMf0Tj~><k`nI2ei9?}blo%w$
dg8$+R_D?SI6mVz`kOpQW22WQ%mvv4FO#txFDWL!W
--- a/toolkit/components/places/tests/favicons/test_expireAllFavicons.js
+++ b/toolkit/components/places/tests/favicons/test_expireAllFavicons.js
@@ -7,27 +7,27 @@
const TEST_PAGE_URI = NetUtil.newURI("http://example.com/");
const BOOKMARKED_PAGE_URI = NetUtil.newURI("http://example.com/bookmarked");
add_task(function* test_expireAllFavicons() {
// Add a visited page.
yield PlacesTestUtils.addVisits({ uri: TEST_PAGE_URI, transition: TRANSITION_TYPED });
// Set a favicon for our test page.
- yield promiseSetIconForPage(TEST_PAGE_URI, SMALLPNG_DATA_URI);
+ yield setFaviconForPage(TEST_PAGE_URI, SMALLPNG_DATA_URI);
// Add a page with a bookmark.
yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: BOOKMARKED_PAGE_URI,
title: "Test bookmark"
});
// Set a favicon for our bookmark.
- yield promiseSetIconForPage(BOOKMARKED_PAGE_URI, SMALLPNG_DATA_URI);
+ yield setFaviconForPage(BOOKMARKED_PAGE_URI, SMALLPNG_DATA_URI);
// Start expiration only after data has been saved in the database.
let promise = promiseTopicObserved(PlacesUtils.TOPIC_FAVICONS_EXPIRED);
PlacesUtils.favicons.expireAllFavicons();
yield promise;
// Check that the favicons for the pages we added were removed.
yield promiseFaviconMissingForPage(TEST_PAGE_URI);
--- a/toolkit/components/places/tests/favicons/test_favicons_conversions.js
+++ b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
@@ -76,19 +76,19 @@ add_test(function test_storing_a_normal_
// 32x32 png, 344 bytes.
// optimized: no
checkFaviconDataConversion("favicon-normal32.png", "image/png", 344,
false, false, run_next_test);
});
add_test(function test_storing_a_big_16x16_icon() {
// in: 16x16 ico, 1406 bytes.
- // optimized: no
+ // optimized: yes
checkFaviconDataConversion("favicon-big16.ico", "image/x-icon", 1406,
- false, false, run_next_test);
+ true, false, run_next_test);
});
add_test(function test_storing_an_oversize_4x4_icon() {
// in: 4x4 jpg, 4751 bytes.
// optimized: yes
checkFaviconDataConversion("favicon-big4.jpg", "image/jpeg", 4751,
true, false, run_next_test);
});
--- a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
+++ b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
@@ -69,16 +69,17 @@ add_task(function* test_replaceFaviconDa
iconsvc.replaceFaviconData(favicon.uri, favicon.data, favicon.data.length,
favicon.mimetype);
let deferSetAndFetchFavicon = Promise.defer();
iconsvc.setAndFetchFaviconForPage(pageURI, favicon.uri, true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_validHistoryURI_check(aURI, aDataLen, aData, aMimeType) {
+dump("GOT " + aMimeType + "\n");
checkCallbackSucceeded(aMimeType, aData, favicon.mimetype, favicon.data);
checkFaviconDataForPage(
pageURI, favicon.mimetype, favicon.data,
function test_replaceFaviconData_validHistoryURI_callback() {
favicon.file.remove(false);
deferSetAndFetchFavicon.resolve();
});
}, systemPrincipal);
@@ -185,51 +186,32 @@ add_task(function* test_replaceFaviconDa
}, systemPrincipal);
yield deferSetAndFetchFavicon.promise;
yield PlacesTestUtils.clearHistory();
});
add_task(function* test_replaceFaviconData_badInputs() {
do_print("test replaceFaviconData to throw on bad inputs");
-
- let favicon = createFavicon("favicon8.png");
-
- let ex = null;
- try {
- iconsvc.replaceFaviconData(
- favicon.uri, favicon.data, favicon.data.length, "");
- } catch (e) {
- ex = e;
- } finally {
- do_check_true(!!ex);
- }
+ let icon = createFavicon("favicon8.png");
- ex = null;
- try {
- iconsvc.replaceFaviconData(
- null, favicon.data, favicon.data.length, favicon.mimeType);
- } catch (e) {
- ex = e;
- } finally {
- do_check_true(!!ex);
- }
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, icon.data, icon.data.length, ""),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, icon.data, icon.data.length, "not-an-image"),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(null, icon.data, icon.data.length, icon.mimetype),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, null, 0, icon.mimetype),
+ /NS_ERROR_ILLEGAL_VALUE/);
- ex = null;
- try {
- iconsvc.replaceFaviconData(
- favicon.uri, null, 0, favicon.mimeType);
- } catch (e) {
- ex = e;
- } finally {
- do_check_true(!!ex);
- }
-
- favicon.file.remove(false);
-
+ icon.file.remove(false);
yield PlacesTestUtils.clearHistory();
});
add_task(function* test_replaceFaviconData_twiceReplace() {
do_print("test replaceFaviconData on multiple replacements");
let pageURI = uri("http://test5.bar/");
yield PlacesTestUtils.addVisits(pageURI);
rename from toolkit/components/places/tests/unit/test_svg_favicon.js
rename to toolkit/components/places/tests/favicons/test_svg_favicon.js
--- a/toolkit/components/places/tests/unit/test_svg_favicon.js
+++ b/toolkit/components/places/tests/favicons/test_svg_favicon.js
@@ -4,20 +4,21 @@ add_task(function* () {
// First, add a history entry or else Places can't save a favicon.
yield PlacesTestUtils.addVisits({
uri: PAGEURI,
transition: TRANSITION_LINK,
visitDate: Date.now() * 1000
});
yield new Promise(resolve => {
- function onSetComplete(aURI, aDataLen, aData, aMimeType) {
+ function onSetComplete(aURI, aDataLen, aData, aMimeType, aWidth) {
equal(aURI.spec, SMALLSVG_DATA_URI.spec, "setFavicon aURI check");
equal(aDataLen, 263, "setFavicon aDataLen check");
equal(aMimeType, "image/svg+xml", "setFavicon aMimeType check");
+ dump(aWidth);
resolve();
}
PlacesUtils.favicons.setAndFetchFaviconForPage(PAGEURI, SMALLSVG_DATA_URI,
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
onSetComplete,
Services.scriptSecurityManager.getSystemPrincipal());
--- a/toolkit/components/places/tests/favicons/xpcshell.ini
+++ b/toolkit/components/places/tests/favicons/xpcshell.ini
@@ -1,14 +1,15 @@
[DEFAULT]
head = head_favicons.js
skip-if = toolkit == 'android'
support-files =
expected-favicon-big32.jpg.png
expected-favicon-big4.jpg.png
+ expected-favicon-big16.ico.png
expected-favicon-big48.ico.png
expected-favicon-big64.png.png
expected-favicon-scale160x3.jpg.png
expected-favicon-scale3x160.jpg.png
favicon-big16.ico
favicon-big32.jpg
favicon-big4.jpg
favicon-big48.ico
@@ -22,8 +23,9 @@ support-files =
[test_favicons_conversions.js]
[test_getFaviconDataForPage.js]
[test_getFaviconURLForPage.js]
[test_moz-anno_favicon_mime_type.js]
[test_page-icon_protocol.js]
[test_query_result_favicon_changed_on_child.js]
[test_replaceFaviconData.js]
[test_replaceFaviconDataFromDataURL.js]
+[test_svg_favicon.js]
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -814,33 +814,16 @@ function promiseIsURIVisited(aURI) {
PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) {
deferred.resolve(aIsVisited);
});
return deferred.promise;
}
-/**
- * Asynchronously set the favicon associated with a page.
- * @param aPageURI
- * The page's URI
- * @param aIconURI
- * The URI of the favicon to be set.
- */
-function promiseSetIconForPage(aPageURI, aIconURI) {
- let deferred = Promise.defer();
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- aPageURI, aIconURI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
- () => { deferred.resolve(); },
- Services.scriptSecurityManager.getSystemPrincipal());
- return deferred.promise;
-}
-
function checkBookmarkObject(info) {
do_check_valid_places_guid(info.guid);
do_check_valid_places_guid(info.parentGuid);
Assert.ok(typeof info.index == "number", "index should be a number");
Assert.ok(info.dateAdded.constructor.name == "Date", "dateAdded should be a Date");
Assert.ok(info.lastModified.constructor.name == "Date", "lastModified should be a Date");
Assert.ok(info.lastModified >= info.dateAdded, "lastModified should never be smaller than dateAdded");
Assert.ok(typeof info.type == "number", "type should be a number");
@@ -868,8 +851,56 @@ function compareAscending(a, b) {
return -1;
}
return 0;
}
function sortBy(array, prop) {
return array.sort((a, b) => compareAscending(a[prop], b[prop]));
}
+
+/**
+ * Asynchronously set the favicon associated with a page.
+ * @param page
+ * The page's URL
+ * @param icon
+ * The URL of the favicon to be set.
+ */
+function setFaviconForPage(page, icon) {
+ let pageURI = page instanceof Ci.nsIURI ? page
+ : NetUtil.newURI(new URL(page).href);
+ let iconURI = icon instanceof Ci.nsIURI ? icon
+ : NetUtil.newURI(new URL(icon).href);
+ return new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI, iconURI, true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ resolve,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+}
+
+/**
+ * Asynchronously compares contents from 2 favicon urls.
+ */
+function* compareFavicons(icon1, icon2, msg) {
+ icon1 = new URL(icon1 instanceof Ci.nsIURI ? icon1.spec : icon1);
+ icon2 = new URL(icon2 instanceof Ci.nsIURI ? icon2.spec : icon2);
+
+ function getIconData(icon) {
+ new Promise((resolve, reject) => {
+ NetUtil.asyncFetch({
+ uri: icon.href, loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON
+ }, function(inputStream, status) {
+ if (!Components.isSuccessCode(status))
+ reject();
+ let size = inputStream.available();
+ resolve(NetUtil.readInputStreamToString(inputStream, size));
+ });
+ });
+ }
+
+ let data1 = yield getIconData(icon1);
+ let data2 = yield getIconData(icon2);
+ Assert.deepEqual(data1, data2, msg);
+}
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -347,13 +347,16 @@ add_task(function* test_orphans() {
PlacesUtils.favicons.setAndFetchFaviconForPage(
uri, SMALLPNG_DATA_URI, true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null, Services.scriptSecurityManager.getSystemPrincipal());
PlacesUtils.annotations.setPageAnnotation(uri, "test", "restval", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
yield PlacesUtils.history.remove(uri);
Assert.ok(!(yield PlacesTestUtils.isPageInDB(uri)), "Page should have been removed");
+
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(`SELECT (SELECT count(*) FROM moz_annos) +
- (SELECT count(*) FROM moz_favicons) AS count`);
+ (SELECT count(*) FROM moz_icons) +
+ (SELECT count(*) FROM moz_pages_w_icons) +
+ (SELECT count(*) FROM moz_icons_to_pages) AS count`);
Assert.equal(rows[0].getResultByName("count"), 0, "Should not find orphans");
});
--- a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
@@ -335,11 +335,13 @@ add_task(function* test_orphans() {
PlacesUtils.annotations.setPageAnnotation(uri, "test", "restval", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
yield PlacesUtils.history.removeVisitsByFilter({ beginDate: new Date(1999, 9, 9, 9, 9),
endDate: new Date() });
Assert.ok(!(yield PlacesTestUtils.isPageInDB(uri)), "Page should have been removed");
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(`SELECT (SELECT count(*) FROM moz_annos) +
- (SELECT count(*) FROM moz_favicons) AS count`);
+ (SELECT count(*) FROM moz_icons) +
+ (SELECT count(*) FROM moz_pages_w_icons) +
+ (SELECT count(*) FROM moz_icons_to_pages) AS count`);
Assert.equal(rows[0].getResultByName("count"), 0, "Should not find orphans");
});
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -19,20 +19,16 @@ Cu.import("resource://testing-common/htt
let uri = Services.io.newFileURI(commonFile);
Services.scriptloader.loadSubScript(uri.spec, this);
}
// Put any other stuff relative to this test folder below.
const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 ";
-function run_test() {
- run_next_test();
-}
-
function* cleanup() {
Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled");
Services.prefs.clearUserPref("browser.urlbar.autoFill");
Services.prefs.clearUserPref("browser.urlbar.autoFill.typed");
Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines");
let suggestPrefs = [
"history",
"bookmark",
@@ -106,17 +102,17 @@ AutoCompleteInput.prototype = {
onTextEntered: () => false,
onTextReverted: () => false,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput])
}
// A helper for check_autocomplete to check a specific match against data from
// the controller.
-function _check_autocomplete_matches(match, result) {
+function* _check_autocomplete_matches(match, result) {
let { uri, title, tags, style } = match;
if (tags)
title += " \u2013 " + tags.sort().join(", ");
if (style)
style = style.sort();
else
style = ["favicon"];
@@ -128,18 +124,19 @@ function _check_autocomplete_matches(mat
let actualStyle = result.style.split(/\s+/).sort();
if (style)
Assert.equal(actualStyle.toString(), style.toString(), "Match should have expected style");
if (uri.spec.startsWith("moz-action:")) {
Assert.ok(actualStyle.includes("action"), "moz-action results should always have 'action' in their style");
}
- if (match.icon)
- Assert.equal(result.image, match.icon, "Match should have expected image");
+ if (match.icon) {
+ yield compareFavicons(result.image, match.icon, "Match should have the expected icon");
+ }
return true;
}
function* check_autocomplete(test) {
// At this point frecency could still be updating due to latest pages
// updates.
// This is not a problem in real life, but autocomplete tests should
@@ -202,17 +199,17 @@ function* check_autocomplete(test) {
do_print("Checking first match is first autocomplete entry")
let result = {
value: controller.getValueAt(0),
comment: controller.getCommentAt(0),
style: controller.getStyleAt(0),
image: controller.getImageAt(0),
}
do_print(`First match is "${result.value}", "${result.comment}"`);
- Assert.ok(_check_autocomplete_matches(matches[0], result), "first item is correct");
+ Assert.ok(yield _check_autocomplete_matches(matches[0], result), "first item is correct");
do_print("Checking rest of the matches");
}
for (let i = firstIndexToCheck; i < controller.matchCount; i++) {
let result = {
value: controller.getValueAt(i),
comment: controller.getCommentAt(i),
style: controller.getStyleAt(i),
@@ -221,17 +218,17 @@ function* check_autocomplete(test) {
do_print(`Found value: "${result.value}", comment: "${result.comment}", style: "${result.style}" in results...`);
let lowerBound = test.checkSorting ? i : firstIndexToCheck;
let upperBound = test.checkSorting ? i + 1 : matches.length;
let found = false;
for (let j = lowerBound; j < upperBound; ++j) {
// Skip processed expected results
if (matches[j] == undefined)
continue;
- if (_check_autocomplete_matches(matches[j], result)) {
+ if (yield _check_autocomplete_matches(matches[j], result)) {
do_print("Got a match at index " + j + "!");
// Make it undefined so we don't process it again
matches[j] = undefined;
found = true;
break;
}
}
@@ -427,29 +424,16 @@ function makeExtensionMatch(extra = {})
content: extra.content,
keyword: extra.keyword,
}),
title: extra.description,
style,
};
}
-function setFaviconForHref(href, iconHref) {
- return new Promise(resolve => {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- NetUtil.newURI(href),
- NetUtil.newURI(iconHref),
- true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
- resolve,
- Services.scriptSecurityManager.getSystemPrincipal()
- );
- });
-}
-
function makeTestServer(port = -1) {
let httpServer = new HttpServer();
httpServer.start(port);
do_register_cleanup(() => httpServer.stop(() => {}));
return httpServer;
}
function* addTestEngine(basename, httpServer = undefined) {
--- a/toolkit/components/places/tests/unifiedcomplete/test_autofill_default_behavior.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_autofill_default_behavior.js
@@ -19,19 +19,19 @@ add_task(function* test_default_behavior
{ uri: uri2, title: "visited" },
{ uri: uri4, title: "tpbk", transition: TRANSITION_TYPED },
{ uri: uri6, title: "secure", transition: TRANSITION_TYPED },
]);
yield addBookmark( { uri: uri3, title: "bookmarked" } );
yield addBookmark( { uri: uri4, title: "tpbk" } );
yield addBookmark( { uri: uri5, title: "title", tags: ["foo"] } );
- yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/info.svg");
- yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png");
- yield setFaviconForHref(uri6.spec, "chrome://global/skin/icons/question-16.png");
+ yield setFaviconForPage(uri1, "chrome://global/skin/icons/info.svg");
+ yield setFaviconForPage(uri3, "chrome://global/skin/icons/error-16.png");
+ yield setFaviconForPage(uri6, "chrome://global/skin/icons/question-16.png");
// RESTRICT TO HISTORY.
Services.prefs.setBoolPref("browser.urlbar.suggest.history", true);
Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false);
Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
do_print("Restrict history, common visit, should not autoFill");
yield check_autocomplete({
@@ -218,18 +218,18 @@ add_task(function* test_default_behavior
yield PlacesTestUtils.addVisits([
{ uri: uri1, title: "typed", transition: TRANSITION_TYPED },
{ uri: uri2, title: "visited" },
{ uri: uri4, title: "tpbk", transition: TRANSITION_TYPED },
]);
yield addBookmark( { uri: uri3, title: "bookmarked" } );
yield addBookmark( { uri: uri4, title: "tpbk" } );
- yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/info.svg");
- yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png");
+ yield setFaviconForPage(uri1, "chrome://global/skin/icons/info.svg");
+ yield setFaviconForPage(uri3, "chrome://global/skin/icons/error-16.png");
// RESTRICT TO HISTORY.
Services.prefs.setBoolPref("browser.urlbar.suggest.history", true);
Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false);
Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true);
Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false);
--- a/toolkit/components/places/tests/unifiedcomplete/test_typed.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_typed.js
@@ -6,31 +6,31 @@
// ensure autocomplete is able to dinamically switch behavior.
const FAVICON_HREF = NetUtil.newURI(do_get_file("../favicons/favicon-normal16.png")).spec;
add_task(function* test_domain() {
do_print("Searching for domain should autoFill it");
Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false);
yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/"));
- yield setFaviconForHref("http://mozilla.org/link/", FAVICON_HREF);
+ yield setFaviconForPage("http://mozilla.org/link/", FAVICON_HREF);
yield check_autocomplete({
search: "moz",
autofilled: "mozilla.org/",
completed: "mozilla.org/",
icon: "moz-anno:favicon:" + FAVICON_HREF
});
yield cleanup();
});
add_task(function* test_url() {
do_print("Searching for url should autoFill it");
Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false);
yield PlacesTestUtils.addVisits(NetUtil.newURI("http://mozilla.org/link/"));
- yield setFaviconForHref("http://mozilla.org/link/", FAVICON_HREF);
+ yield setFaviconForPage("http://mozilla.org/link/", FAVICON_HREF);
yield check_autocomplete({
search: "mozilla.org/li",
autofilled: "mozilla.org/link/",
completed: "http://mozilla.org/link/",
icon: "moz-anno:favicon:" + FAVICON_HREF
});
yield cleanup();
});
--- a/toolkit/components/places/tests/unit/test_history_clear.js
+++ b/toolkit/components/places/tests/unit/test_history_clear.js
@@ -133,18 +133,27 @@ add_task(function* test_history_clear()
`SELECT h.id FROM moz_places h WHERE
url_hash NOT BETWEEN hash('place', 'prefix_lo') AND hash('place', 'prefix_hi')
AND NOT EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1`);
do_check_false(stmt.executeStep());
stmt.finalize();
// Check that we only have favicons for retained places
stmt = mDBConn.createStatement(
- `SELECT f.id FROM moz_favicons f WHERE NOT EXISTS
- (SELECT id FROM moz_places WHERE favicon_id = f.id) LIMIT 1`);
+ `SELECT 1
+ FROM moz_pages_w_icons
+ LEFT JOIN moz_places h ON url_hash = page_url_hash AND url = page_url
+ WHERE h.id ISNULL`);
+ do_check_false(stmt.executeStep());
+ stmt.finalize();
+ stmt = mDBConn.createStatement(
+ `SELECT 1
+ FROM moz_icons WHERE id NOT IN (
+ SELECT icon_id FROM moz_icons_to_pages
+ )`);
do_check_false(stmt.executeStep());
stmt.finalize();
// Check that we only have annotations for retained places
stmt = mDBConn.createStatement(
`SELECT a.id FROM moz_annos a WHERE NOT EXISTS
(SELECT id FROM moz_places WHERE id = a.place_id) LIMIT 1`);
do_check_false(stmt.executeStep());
--- a/toolkit/components/places/tests/unit/test_preventive_maintenance.js
+++ b/toolkit/components/places/tests/unit/test_preventive_maintenance.js
@@ -31,28 +31,44 @@ var defaultBookmarksMaxId = 0;
function cleanDatabase() {
mDBConn.executeSimpleSQL("DELETE FROM moz_places");
mDBConn.executeSimpleSQL("DELETE FROM moz_historyvisits");
mDBConn.executeSimpleSQL("DELETE FROM moz_anno_attributes");
mDBConn.executeSimpleSQL("DELETE FROM moz_annos");
mDBConn.executeSimpleSQL("DELETE FROM moz_items_annos");
mDBConn.executeSimpleSQL("DELETE FROM moz_inputhistory");
mDBConn.executeSimpleSQL("DELETE FROM moz_keywords");
- mDBConn.executeSimpleSQL("DELETE FROM moz_favicons");
+ mDBConn.executeSimpleSQL("DELETE FROM moz_icons");
+ mDBConn.executeSimpleSQL("DELETE FROM moz_pages_w_icons");
mDBConn.executeSimpleSQL("DELETE FROM moz_bookmarks WHERE id > " + defaultBookmarksMaxId);
}
function addPlace(aUrl, aFavicon) {
+ let href = new URL(aUrl || "http://www.mozilla.org").href;
let stmt = mDBConn.createStatement(
- "INSERT INTO moz_places (url, url_hash, favicon_id) VALUES (:url, hash(:url), :favicon)");
- stmt.params["url"] = aUrl || "http://www.mozilla.org";
- stmt.params["favicon"] = aFavicon || null;
+ "INSERT INTO moz_places (url, url_hash) VALUES (:url, hash(:url))");
+ stmt.params["url"] = href;
stmt.execute();
stmt.finalize();
- return mDBConn.lastInsertRowID;
+ let id = mDBConn.lastInsertRowID;
+ if (aFavicon) {
+ stmt = mDBConn.createStatement(
+ "INSERT INTO moz_pages_w_icons (page_url, page_url_hash) VALUES (:url, hash(:url))");
+ stmt.params["url"] = href;
+ stmt.execute();
+ stmt.finalize();
+ stmt = mDBConn.createStatement(
+ "INSERT INTO moz_icons_to_pages (page_id, icon_id) " +
+ "VALUES ((SELECT id FROM moz_pages_w_icons WHERE page_url_hash = hash(:url)), :favicon)");
+ stmt.params["url"] = href;
+ stmt.params["favicon"] = aFavicon;
+ stmt.execute();
+ stmt.finalize();
+ }
+ return id;
}
function addBookmark(aPlaceId, aType, aParent, aKeywordId, aFolderType, aTitle) {
let stmt = mDBConn.createStatement(
`INSERT INTO moz_bookmarks (fk, type, parent, keyword_id, folder_type,
title, guid)
VALUES (:place_id, :type, :parent, :keyword_id, :folder_type, :title,
GENERATE_GUID())`);
@@ -784,45 +800,57 @@ tests.push({
stmt.finalize();
}
});
// ------------------------------------------------------------------------------
tests.push({
name: "E.1",
- desc: "Remove orphan icons",
+ desc: "Remove orphan icon entries",
_placeId: null,
setup() {
// Insert favicon entries
- let stmt = mDBConn.createStatement("INSERT INTO moz_favicons (id, url) VALUES(:favicon_id, :url)");
+ let stmt = mDBConn.createStatement("INSERT INTO moz_icons (id, icon_url, fixed_icon_url_hash) VALUES(:favicon_id, :url, hash(fixup_url(:url)))");
stmt.params["favicon_id"] = 1;
stmt.params["url"] = "http://www1.mozilla.org/favicon.ico";
stmt.execute();
stmt.reset();
stmt.params["favicon_id"] = 2;
stmt.params["url"] = "http://www2.mozilla.org/favicon.ico";
stmt.execute();
stmt.finalize();
+ // Insert orphan page.
+ stmt = mDBConn.createStatement("INSERT INTO moz_pages_w_icons (id, page_url, page_url_hash) VALUES(:page_id, :url, hash(:url))");
+ stmt.params["page_id"] = 99;
+ stmt.params["url"] = "http://w99.mozilla.org/";
+ stmt.execute();
+ stmt.finalize();
+
// Insert a place using the existing favicon entry
this._placeId = addPlace("http://www.mozilla.org", 1);
},
check() {
// Check that used icon is still there
- let stmt = mDBConn.createStatement("SELECT id FROM moz_favicons WHERE id = :favicon_id");
+ let stmt = mDBConn.createStatement("SELECT id FROM moz_icons WHERE id = :favicon_id");
stmt.params["favicon_id"] = 1;
do_check_true(stmt.executeStep());
stmt.reset();
// Check that unused icon has been removed
stmt.params["favicon_id"] = 2;
do_check_false(stmt.executeStep());
stmt.finalize();
+ // Check that the orphan page is gone.
+ stmt = mDBConn.createStatement("SELECT id FROM moz_pages_w_icons WHERE id = :page_id");
+ stmt.params["page_id"] = 99;
+ do_check_false(stmt.executeStep());
+ stmt.finalize();
}
});
// ------------------------------------------------------------------------------
tests.push({
name: "F.1",
desc: "Remove orphan visits",
@@ -1019,60 +1047,16 @@ tests.push({
let stmt = mDBConn.createStatement("SELECT id FROM moz_keywords WHERE keyword = :keyword");
// Check that "unused" keyword has gone
stmt.params["keyword"] = "unused";
do_check_false(stmt.executeStep());
stmt.finalize();
}
});
-
-// ------------------------------------------------------------------------------
-
-tests.push({
- name: "L.1",
- desc: "Fix wrong favicon ids",
-
- _validIconPlaceId: null,
- _invalidIconPlaceId: null,
-
- setup() {
- // Insert a favicon entry
- let stmt = mDBConn.createStatement("INSERT INTO moz_favicons (id, url) VALUES(1, :url)");
- stmt.params["url"] = "http://www.mozilla.org/favicon.ico";
- stmt.execute();
- stmt.finalize();
- // Insert a place using the existing favicon entry
- this._validIconPlaceId = addPlace("http://www1.mozilla.org", 1);
-
- // Insert a place using a nonexistent favicon entry
- this._invalidIconPlaceId = addPlace("http://www2.mozilla.org", 1337);
- },
-
- check() {
- // Check that bogus favicon is not there
- let stmt = mDBConn.createStatement("SELECT id FROM moz_places WHERE favicon_id = :favicon_id");
- stmt.params["favicon_id"] = 1337;
- do_check_false(stmt.executeStep());
- stmt.reset();
- // Check that valid favicon is still there
- stmt.params["favicon_id"] = 1;
- do_check_true(stmt.executeStep());
- stmt.finalize();
- // Check that place entries are there
- stmt = mDBConn.createStatement("SELECT id FROM moz_places WHERE id = :place_id");
- stmt.params["place_id"] = this._validIconPlaceId;
- do_check_true(stmt.executeStep());
- stmt.reset();
- stmt.params["place_id"] = this._invalidIconPlaceId;
- do_check_true(stmt.executeStep());
- stmt.finalize();
- }
-});
-
// ------------------------------------------------------------------------------
tests.push({
name: "L.2",
desc: "Recalculate visit_count and last_visit_date",
*setup() {
function setVisitCount(aURL, aValue) {
@@ -1180,18 +1164,16 @@ tests.push({
let url = row.getResultByIndex(0);
do_check_true(/redirecting/.test(url));
this._count++;
}
},
handleError(aError) {
},
handleCompletion(aReason) {
- dump_table("moz_places");
- dump_table("moz_historyvisits");
do_check_eq(aReason, Ci.mozIStorageStatementCallback.REASON_FINISHED);
do_check_eq(this._count, 2);
resolve();
}
});
stmt.finalize();
});
}
--- a/toolkit/components/places/tests/unit/test_promiseBookmarksTree.js
+++ b/toolkit/components/places/tests/unit/test_promiseBookmarksTree.js
@@ -129,18 +129,17 @@ function* compareToNode(aItem, aNode, aI
compare_prop("uri");
// node.tags's format is "a, b" whilst promiseBoookmarksTree is "a,b"
if (aNode.tags === null)
check_unset("tags");
else
compare_prop_to_value("tags", aNode.tags.replace(/, /g, ","), false);
if (aNode.icon) {
- let nodeIconData = aNode.icon.replace("moz-anno:favicon:", "");
- compare_prop_to_value("iconuri", nodeIconData);
+ compareFavicons(aNode.icon, aItem.iconuri);
} else {
check_unset(aItem.iconuri);
}
check_unset(...FOLDER_ONLY_PROPS);
let itemURI = uri(aNode.uri);
compare_prop_to_value("charset",
@@ -218,17 +217,17 @@ add_task(function* () {
yield new_bookmark({ title: null,
parentGuid: folderGuid,
keyword: "test_keyword",
tags: ["TestTagA", "TestTagB"],
annotations: [{ name: "TestAnnoA", value: "TestVal2"}]});
let urlWithCharsetAndFavicon = uri("http://charset.and.favicon");
yield new_bookmark({ parentGuid: folderGuid, url: urlWithCharsetAndFavicon });
yield PlacesUtils.setCharsetForURI(urlWithCharsetAndFavicon, "UTF-8");
- yield promiseSetIconForPage(urlWithCharsetAndFavicon, SMALLPNG_DATA_URI);
+ yield setFaviconForPage(urlWithCharsetAndFavicon, SMALLPNG_DATA_URI);
// Test the default places root without specifying it.
yield test_promiseBookmarksTreeAgainstResult();
// Do specify it
yield test_promiseBookmarksTreeAgainstResult(PlacesUtils.bookmarks.rootGuid);
// Exclude the bookmarks menu.
// The calllback should be four times - once for the toolbar, once for
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -116,17 +116,16 @@ skip-if = true
[test_preventive_maintenance.js]
[test_preventive_maintenance_checkAndFixDatabase.js]
[test_preventive_maintenance_runTasks.js]
[test_promiseBookmarksTree.js]
[test_resolveNullBookmarkTitles.js]
[test_result_sort.js]
[test_resultsAsVisit_details.js]
[test_sql_guid_functions.js]
-[test_svg_favicon.js]
[test_sync_utils.js]
[test_tag_autocomplete_search.js]
[test_tagging.js]
[test_telemetry.js]
[test_update_frecency_after_delete.js]
[test_utils_backups_create.js]
[test_utils_getURLsForContainerNode.js]
[test_utils_setAnnotationsFor.js]
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -1224,17 +1224,18 @@ nsresult AsyncFaviconDataReady::OnFavico
return channel->AsyncOpen2(listener);
}
NS_IMETHODIMP
AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI,
uint32_t aDataLen,
const uint8_t *aData,
- const nsACString &aMimeType)
+ const nsACString &aMimeType,
+ uint16_t aWidth)
{
if (!aDataLen || !aData) {
if (mURLShortcut) {
OnFaviconDataNotAvailable();
}
return NS_OK;
}
@@ -1652,17 +1653,17 @@ nsresult
do_GetService("@mozilla.org/browser/favicon-service;1"));
NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE);
nsCOMPtr<nsIFaviconDataCallback> callback =
new mozilla::widget::AsyncFaviconDataReady(aFaviconPageURI,
aIOThread,
aURLShortcut);
- favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback);
+ favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0);
#endif
return NS_OK;
}
// Obtains the jump list 'ICO cache timeout in seconds' pref
int32_t FaviconHelper::GetICOCacheSecondsTimeout() {
// Only obtain the setting at most once from the pref service.