Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "FaviconHelpers.h"
8 :
9 : #include "nsICacheEntry.h"
10 : #include "nsICachingChannel.h"
11 : #include "nsIAsyncVerifyRedirectCallback.h"
12 : #include "nsIPrincipal.h"
13 :
14 : #include "nsNavHistory.h"
15 : #include "nsFaviconService.h"
16 : #include "mozilla/storage.h"
17 : #include "mozilla/Telemetry.h"
18 : #include "nsNetUtil.h"
19 : #include "nsPrintfCString.h"
20 : #include "nsStreamUtils.h"
21 : #include "nsStringStream.h"
22 : #include "nsIPrivateBrowsingChannel.h"
23 : #include "nsISupportsPriority.h"
24 : #include "nsContentUtils.h"
25 : #include <algorithm>
26 : #include <deque>
27 : #include "mozilla/gfx/2D.h"
28 : #include "imgIContainer.h"
29 : #include "ImageOps.h"
30 : #include "imgIEncoder.h"
31 :
32 : using namespace mozilla::places;
33 : using namespace mozilla::storage;
34 :
35 : namespace mozilla {
36 : namespace places {
37 :
38 : namespace {
39 :
40 : /**
41 : * Fetches information about a page from the database.
42 : *
43 : * @param aDB
44 : * Database connection to history tables.
45 : * @param _page
46 : * Page that should be fetched.
47 : */
48 : nsresult
49 0 : FetchPageInfo(const RefPtr<Database>& aDB,
50 : PageData& _page)
51 : {
52 0 : MOZ_ASSERT(_page.spec.Length(), "Must have a non-empty spec!");
53 0 : MOZ_ASSERT(!NS_IsMainThread());
54 :
55 : // This query finds the bookmarked uri we want to set the icon for,
56 : // walking up to two redirect levels.
57 0 : nsCString query = nsPrintfCString(
58 : "SELECT h.id, pi.id, h.guid, ( "
59 : "SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id "
60 : "UNION ALL " // Union not directly bookmarked pages.
61 : "SELECT url FROM moz_places WHERE id = ( "
62 : "SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id "
63 : "FROM moz_historyvisits dest "
64 : "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
65 : "AND dest.visit_type IN (%d, %d) "
66 : "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
67 : "AND parent.visit_type IN (%d, %d) "
68 : "WHERE dest.place_id = h.id "
69 : "AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
70 : "LIMIT 1 "
71 : ") "
72 : "), fixup_url(get_unreversed_host(h.rev_host)) AS host "
73 : "FROM moz_places h "
74 : "LEFT JOIN moz_pages_w_icons pi ON page_url_hash = hash(:page_url) AND page_url = :page_url "
75 : "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
76 : nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
77 : nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
78 : nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
79 : nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
80 0 : );
81 :
82 0 : nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
83 0 : NS_ENSURE_STATE(stmt);
84 0 : mozStorageStatementScoper scoper(stmt);
85 :
86 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
87 0 : _page.spec);
88 0 : NS_ENSURE_SUCCESS(rv, rv);
89 :
90 : bool hasResult;
91 0 : rv = stmt->ExecuteStep(&hasResult);
92 0 : NS_ENSURE_SUCCESS(rv, rv);
93 0 : if (!hasResult) {
94 : // The page does not exist.
95 0 : return NS_ERROR_NOT_AVAILABLE;
96 : }
97 :
98 0 : rv = stmt->GetInt64(0, &_page.placeId);
99 0 : NS_ENSURE_SUCCESS(rv, rv);
100 : // May be null, and in such a case this will be 0.
101 0 : _page.id = stmt->AsInt64(1);
102 0 : rv = stmt->GetUTF8String(2, _page.guid);
103 0 : NS_ENSURE_SUCCESS(rv, rv);
104 : // Bookmarked url can be nullptr.
105 : bool isNull;
106 0 : rv = stmt->GetIsNull(3, &isNull);
107 0 : NS_ENSURE_SUCCESS(rv, rv);
108 : // The page could not be bookmarked.
109 0 : if (!isNull) {
110 0 : rv = stmt->GetUTF8String(3, _page.bookmarkedSpec);
111 0 : NS_ENSURE_SUCCESS(rv, rv);
112 : }
113 :
114 0 : if (_page.host.IsEmpty()) {
115 0 : rv = stmt->GetUTF8String(4, _page.host);
116 0 : NS_ENSURE_SUCCESS(rv, rv);
117 : }
118 :
119 0 : if (!_page.canAddToHistory) {
120 : // Either history is disabled or the scheme is not supported. In such a
121 : // case we want to update the icon only if the page is bookmarked.
122 :
123 0 : if (_page.bookmarkedSpec.IsEmpty()) {
124 : // The page is not bookmarked. Since updating the icon with a disabled
125 : // history would be a privacy leak, bail out as if the page did not exist.
126 0 : return NS_ERROR_NOT_AVAILABLE;
127 : }
128 : else {
129 : // The page, or a redirect to it, is bookmarked. If the bookmarked spec
130 : // is different from the requested one, use it.
131 0 : if (!_page.bookmarkedSpec.Equals(_page.spec)) {
132 0 : _page.spec = _page.bookmarkedSpec;
133 0 : rv = FetchPageInfo(aDB, _page);
134 0 : NS_ENSURE_SUCCESS(rv, rv);
135 : }
136 : }
137 : }
138 :
139 0 : return NS_OK;
140 : }
141 :
142 : /**
143 : * Stores information about an icon in the database.
144 : *
145 : * @param aDB
146 : * Database connection to history tables.
147 : * @param aIcon
148 : * Icon that should be stored.
149 : * @param aMustReplace
150 : * If set to true, the function will bail out with NS_ERROR_NOT_AVAILABLE
151 : * if it can't find a previous stored icon to replace.
152 : * @note Should be wrapped in a transaction.
153 : */
154 : nsresult
155 0 : SetIconInfo(const RefPtr<Database>& aDB,
156 : IconData& aIcon,
157 : bool aMustReplace = false)
158 : {
159 0 : MOZ_ASSERT(!NS_IsMainThread());
160 0 : MOZ_ASSERT(aIcon.payloads.Length() > 0);
161 0 : MOZ_ASSERT(!aIcon.spec.IsEmpty());
162 0 : MOZ_ASSERT(aIcon.expiration > 0);
163 :
164 : // There are multiple cases possible at this point:
165 : // 1. We must insert some payloads and no payloads exist in the table. This
166 : // would be a straight INSERT.
167 : // 2. The table contains the same number of payloads we are inserting. This
168 : // would be a straight UPDATE.
169 : // 3. The table contains more payloads than we are inserting. This would be
170 : // an UPDATE and a DELETE.
171 : // 4. The table contains less payloads than we are inserting. This would be
172 : // an UPDATE and an INSERT.
173 : // We can't just remove all the old entries and insert the new ones, cause
174 : // we'd lose the referential integrity with pages. For the same reason we
175 : // cannot use INSERT OR REPLACE, since it's implemented as DELETE AND INSERT.
176 : // Thus, we follow this strategy:
177 : // * SELECT all existing icon ids
178 : // * For each payload, either UPDATE OR INSERT reusing icon ids.
179 : // * If any previous icon ids is leftover, DELETE it.
180 :
181 0 : nsCOMPtr<mozIStorageStatement> selectStmt = aDB->GetStatement(
182 : "SELECT id FROM moz_icons "
183 : "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) "
184 : "AND icon_url = :url "
185 0 : );
186 0 : NS_ENSURE_STATE(selectStmt);
187 0 : mozStorageStatementScoper scoper(selectStmt);
188 0 : nsresult rv = URIBinder::Bind(selectStmt, NS_LITERAL_CSTRING("url"), aIcon.spec);
189 0 : NS_ENSURE_SUCCESS(rv, rv);
190 0 : std::deque<int64_t> ids;
191 0 : bool hasResult = false;
192 0 : while (NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult) {
193 0 : int64_t id = selectStmt->AsInt64(0);
194 0 : MOZ_ASSERT(id > 0);
195 0 : ids.push_back(id);
196 : }
197 0 : if (aMustReplace && ids.empty()) {
198 0 : return NS_ERROR_NOT_AVAILABLE;
199 : }
200 :
201 0 : nsCOMPtr<mozIStorageStatement> insertStmt = aDB->GetStatement(
202 : "INSERT INTO moz_icons "
203 : "(icon_url, fixed_icon_url_hash, width, root, expire_ms, data) "
204 : "VALUES (:url, hash(fixup_url(:url)), :width, :root, :expire, :data) "
205 0 : );
206 0 : NS_ENSURE_STATE(insertStmt);
207 0 : nsCOMPtr<mozIStorageStatement> updateStmt = aDB->GetStatement(
208 : "UPDATE moz_icons SET width = :width, "
209 : "expire_ms = :expire, "
210 : "data = :data, "
211 : "root = :root "
212 : "WHERE id = :id "
213 0 : );
214 0 : NS_ENSURE_STATE(updateStmt);
215 :
216 0 : for (auto& payload : aIcon.payloads) {
217 : // Sanity checks.
218 0 : MOZ_ASSERT(payload.mimeType.EqualsLiteral(PNG_MIME_TYPE) ||
219 : payload.mimeType.EqualsLiteral(SVG_MIME_TYPE),
220 : "Only png and svg payloads are supported");
221 0 : MOZ_ASSERT(!payload.mimeType.EqualsLiteral(SVG_MIME_TYPE) ||
222 : payload.width == UINT16_MAX,
223 : "SVG payloads should have max width");
224 0 : MOZ_ASSERT(payload.width > 0, "Payload should have a width");
225 : #ifdef DEBUG
226 : // Done to ensure we fetch the id. See the MOZ_ASSERT below.
227 0 : payload.id = 0;
228 : #endif
229 0 : if (!ids.empty()) {
230 : // Pop the first existing id for reuse.
231 0 : int64_t id = ids.front();
232 0 : ids.pop_front();
233 0 : mozStorageStatementScoper scoper(updateStmt);
234 0 : rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
235 0 : NS_ENSURE_SUCCESS(rv, rv);
236 0 : rv = updateStmt->BindInt32ByName(NS_LITERAL_CSTRING("width"),
237 0 : payload.width);
238 0 : NS_ENSURE_SUCCESS(rv, rv);
239 0 : rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("expire"),
240 0 : aIcon.expiration / 1000);
241 0 : NS_ENSURE_SUCCESS(rv, rv);
242 0 : rv = updateStmt->BindInt32ByName(NS_LITERAL_CSTRING("root"),
243 0 : aIcon.rootIcon);
244 0 : NS_ENSURE_SUCCESS(rv, rv);
245 0 : rv = updateStmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
246 0 : TO_INTBUFFER(payload.data),
247 0 : payload.data.Length());
248 0 : NS_ENSURE_SUCCESS(rv, rv);
249 0 : rv = updateStmt->Execute();
250 0 : NS_ENSURE_SUCCESS(rv, rv);
251 : // Set the new payload id.
252 0 : payload.id = id;
253 : } else {
254 : // Insert a new entry.
255 0 : mozStorageStatementScoper scoper(insertStmt);
256 0 : rv = URIBinder::Bind(insertStmt, NS_LITERAL_CSTRING("url"), aIcon.spec);
257 0 : NS_ENSURE_SUCCESS(rv, rv);
258 0 : rv = insertStmt->BindInt32ByName(NS_LITERAL_CSTRING("width"),
259 0 : payload.width);
260 0 : NS_ENSURE_SUCCESS(rv, rv);
261 :
262 0 : rv = insertStmt->BindInt32ByName(NS_LITERAL_CSTRING("root"),
263 0 : aIcon.rootIcon);
264 0 : NS_ENSURE_SUCCESS(rv, rv);
265 0 : rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("expire"),
266 0 : aIcon.expiration / 1000);
267 0 : NS_ENSURE_SUCCESS(rv, rv);
268 0 : rv = insertStmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
269 0 : TO_INTBUFFER(payload.data),
270 0 : payload.data.Length());
271 0 : NS_ENSURE_SUCCESS(rv, rv);
272 0 : rv = insertStmt->Execute();
273 0 : NS_ENSURE_SUCCESS(rv, rv);
274 : // Set the new payload id.
275 0 : payload.id = nsFaviconService::sLastInsertedIconId;
276 : }
277 0 : MOZ_ASSERT(payload.id > 0, "Payload should have an id");
278 : }
279 :
280 0 : if (!ids.empty()) {
281 : // Remove any old leftover payload.
282 0 : nsAutoCString sql("DELETE FROM moz_icons WHERE id IN (");
283 0 : for (int64_t id : ids) {
284 0 : sql.AppendInt(id);
285 0 : sql.AppendLiteral(",");
286 : }
287 0 : sql.AppendLiteral(" 0)"); // Non-existing id to match the trailing comma.
288 0 : nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(sql);
289 0 : NS_ENSURE_STATE(stmt);
290 0 : mozStorageStatementScoper scoper(stmt);
291 0 : rv = stmt->Execute();
292 0 : NS_ENSURE_SUCCESS(rv, rv);
293 : }
294 :
295 0 : return NS_OK;
296 : }
297 :
298 : /**
299 : * Fetches information on a icon url from the database.
300 : *
301 : * @param aDBConn
302 : * Database connection to history tables.
303 : * @param aPreferredWidth
304 : * The preferred size to fetch.
305 : * @param _icon
306 : * Icon that should be fetched.
307 : */
308 : nsresult
309 1 : FetchIconInfo(const RefPtr<Database>& aDB,
310 : uint16_t aPreferredWidth,
311 : IconData& _icon
312 : )
313 : {
314 1 : MOZ_ASSERT(_icon.spec.Length(), "Must have a non-empty spec!");
315 1 : MOZ_ASSERT(!NS_IsMainThread());
316 :
317 1 : if (_icon.status & ICON_STATUS_CACHED) {
318 : // The icon data has already been set by ReplaceFaviconData.
319 0 : return NS_OK;
320 : }
321 :
322 2 : nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
323 : "/* do not warn (bug no: not worth having a compound index) */ "
324 : "SELECT id, expire_ms, data, width, root "
325 : "FROM moz_icons "
326 : "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) "
327 : "AND icon_url = :url "
328 : "ORDER BY width DESC "
329 2 : );
330 1 : NS_ENSURE_STATE(stmt);
331 2 : mozStorageStatementScoper scoper(stmt);
332 :
333 3 : DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"),
334 3 : _icon.spec);
335 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
336 :
337 1 : bool hasResult = false;
338 1 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
339 0 : IconPayload payload;
340 0 : rv = stmt->GetInt64(0, &payload.id);
341 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
342 :
343 : // Expiration can be nullptr.
344 : bool isNull;
345 0 : rv = stmt->GetIsNull(1, &isNull);
346 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
347 0 : if (!isNull) {
348 : int64_t expire_ms;
349 0 : rv = stmt->GetInt64(1, &expire_ms);
350 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
351 0 : _icon.expiration = expire_ms * 1000;
352 : }
353 :
354 : uint8_t* data;
355 0 : uint32_t dataLen = 0;
356 0 : rv = stmt->GetBlob(2, &dataLen, &data);
357 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
358 0 : payload.data.Adopt(TO_CHARBUFFER(data), dataLen);
359 :
360 : int32_t width;
361 0 : rv = stmt->GetInt32(3, &width);
362 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
363 0 : payload.width = width;
364 0 : if (payload.width == UINT16_MAX) {
365 0 : payload.mimeType.AssignLiteral(SVG_MIME_TYPE);
366 : } else {
367 0 : payload.mimeType.AssignLiteral(PNG_MIME_TYPE);
368 : }
369 :
370 : int32_t rootIcon;
371 0 : rv = stmt->GetInt32(4, &rootIcon);
372 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
373 0 : _icon.rootIcon = rootIcon;
374 :
375 0 : if (aPreferredWidth == 0 || _icon.payloads.Length() == 0) {
376 0 : _icon.payloads.AppendElement(payload);
377 0 : } else if (payload.width >= aPreferredWidth) {
378 : // Only retain the best matching payload.
379 0 : _icon.payloads.ReplaceElementAt(0, payload);
380 : } else {
381 0 : break;
382 : }
383 : }
384 :
385 1 : return NS_OK;
386 : }
387 :
388 : nsresult
389 0 : FetchIconPerSpec(const RefPtr<Database>& aDB,
390 : const nsACString& aPageSpec,
391 : const nsACString& aPageHost,
392 : IconData& aIconData,
393 : uint16_t aPreferredWidth)
394 : {
395 0 : MOZ_ASSERT(!aPageSpec.IsEmpty(), "Page spec must not be empty.");
396 0 : MOZ_ASSERT(!NS_IsMainThread());
397 :
398 : // This selects both associated and root domain icons, ordered by width,
399 : // where an associated icon has priority over a root domain icon.
400 : // Regardless, note that while this way we are far more efficient, we lost
401 : // associations with root domain icons, so it's possible we'll return one
402 : // for a specific size when an associated icon for that size doesn't exist.
403 0 : nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
404 : "/* do not warn (bug no: not worth having a compound index) */ "
405 : "SELECT width, icon_url, root "
406 : "FROM moz_icons i "
407 : "JOIN moz_icons_to_pages ON i.id = icon_id "
408 : "JOIN moz_pages_w_icons p ON p.id = page_id "
409 : "WHERE page_url_hash = hash(:url) AND page_url = :url "
410 : "OR (:hash_idx AND page_url_hash = hash(substr(:url, 0, :hash_idx)) "
411 : "AND page_url = substr(:url, 0, :hash_idx)) "
412 : "UNION ALL "
413 : "SELECT width, icon_url, root "
414 : "FROM moz_icons i "
415 : "WHERE fixed_icon_url_hash = hash(fixup_url(:root_icon_url)) "
416 : "ORDER BY width DESC, root ASC "
417 0 : );
418 0 : NS_ENSURE_STATE(stmt);
419 0 : mozStorageStatementScoper scoper(stmt);
420 :
421 0 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPageSpec);
422 0 : NS_ENSURE_SUCCESS(rv, rv);
423 0 : nsAutoCString rootIconFixedUrl(aPageHost);
424 0 : if (!rootIconFixedUrl.IsEmpty()) {
425 0 : rootIconFixedUrl.AppendLiteral("/favicon.ico");
426 : }
427 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_icon_url"),
428 0 : rootIconFixedUrl);
429 0 : NS_ENSURE_SUCCESS(rv, rv);
430 0 : int32_t hashIdx = PromiseFlatCString(aPageSpec).RFind("#");
431 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hash_idx"), hashIdx + 1);
432 0 : NS_ENSURE_SUCCESS(rv, rv);
433 :
434 : // Return the biggest icon close to the preferred width. It may be bigger
435 : // or smaller if the preferred width isn't found.
436 : bool hasResult;
437 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
438 : int32_t width;
439 0 : rv = stmt->GetInt32(0, &width);
440 0 : if (!aIconData.spec.IsEmpty() && width < aPreferredWidth) {
441 : // We found the best match, or we already found a match so we don't need
442 : // to fallback to the root domain icon.
443 0 : break;
444 : }
445 0 : rv = stmt->GetUTF8String(1, aIconData.spec);
446 0 : NS_ENSURE_SUCCESS(rv, rv);
447 : }
448 :
449 0 : return NS_OK;
450 : }
451 :
452 : /**
453 : * Tries to compute the expiration time for a icon from the channel.
454 : *
455 : * @param aChannel
456 : * The network channel used to fetch the icon.
457 : * @return a valid expiration value for the fetched icon.
458 : */
459 : PRTime
460 0 : GetExpirationTimeFromChannel(nsIChannel* aChannel)
461 : {
462 0 : MOZ_ASSERT(NS_IsMainThread());
463 :
464 : // Attempt to get an expiration time from the cache. If this fails, we'll
465 : // make one up.
466 0 : PRTime expiration = -1;
467 0 : nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
468 0 : if (cachingChannel) {
469 0 : nsCOMPtr<nsISupports> cacheToken;
470 0 : nsresult rv = cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
471 0 : if (NS_SUCCEEDED(rv)) {
472 0 : nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
473 : uint32_t seconds;
474 0 : rv = cacheEntry->GetExpirationTime(&seconds);
475 0 : if (NS_SUCCEEDED(rv)) {
476 : // Set the expiration, but make sure we honor our cap.
477 0 : expiration = PR_Now() + std::min((PRTime)seconds * PR_USEC_PER_SEC,
478 0 : MAX_FAVICON_EXPIRATION);
479 : }
480 : }
481 : }
482 : // If we did not obtain a time from the cache, use the cap value.
483 0 : return expiration < 0 ? PR_Now() + MAX_FAVICON_EXPIRATION
484 0 : : expiration;
485 : }
486 :
487 : } // namespace
488 :
489 : ////////////////////////////////////////////////////////////////////////////////
490 : //// AsyncFetchAndSetIconForPage
491 :
492 123 : NS_IMPL_ISUPPORTS_INHERITED(
493 : AsyncFetchAndSetIconForPage
494 : , Runnable
495 : , nsIStreamListener
496 : , nsIInterfaceRequestor
497 : , nsIChannelEventSink
498 : , mozIPlacesPendingOperation
499 : )
500 :
501 1 : AsyncFetchAndSetIconForPage::AsyncFetchAndSetIconForPage(
502 : IconData& aIcon
503 : , PageData& aPage
504 : , bool aFaviconLoadPrivate
505 : , nsIFaviconDataCallback* aCallback
506 : , nsIPrincipal* aLoadingPrincipal
507 1 : ) : Runnable("places::AsyncFetchAndSetIconForPage")
508 : , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(
509 1 : "AsyncFetchAndSetIconForPage::mCallback", aCallback))
510 : , mIcon(aIcon)
511 : , mPage(aPage)
512 : , mFaviconLoadPrivate(aFaviconLoadPrivate)
513 : , mLoadingPrincipal(new nsMainThreadPtrHolder<nsIPrincipal>(
514 1 : "AsyncFetchAndSetIconForPage::mLoadingPrincipal", aLoadingPrincipal))
515 3 : , mCanceled(false)
516 : {
517 1 : MOZ_ASSERT(NS_IsMainThread());
518 1 : }
519 :
520 : NS_IMETHODIMP
521 1 : AsyncFetchAndSetIconForPage::Run()
522 : {
523 1 : MOZ_ASSERT(!NS_IsMainThread());
524 :
525 : // Try to fetch the icon from the database.
526 2 : RefPtr<Database> DB = Database::GetDatabase();
527 1 : NS_ENSURE_STATE(DB);
528 1 : nsresult rv = FetchIconInfo(DB, 0, mIcon);
529 1 : NS_ENSURE_SUCCESS(rv, rv);
530 :
531 1 : bool isInvalidIcon = !mIcon.payloads.Length() || PR_Now() > mIcon.expiration;
532 3 : bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS ||
533 3 : (mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon);
534 :
535 1 : if (!fetchIconFromNetwork) {
536 : // There is already a valid icon or we don't want to fetch a new one,
537 : // directly proceed with association.
538 : RefPtr<AsyncAssociateIconToPage> event =
539 0 : new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
540 : // We're already on the async thread.
541 0 : return event->Run();
542 : }
543 :
544 : // Fetch the icon from the network, the request starts from the main-thread.
545 : // When done this will associate the icon to the page and notify.
546 : nsCOMPtr<nsIRunnable> event =
547 2 : NewRunnableMethod("places::AsyncFetchAndSetIconForPage::FetchFromNetwork",
548 : this,
549 2 : &AsyncFetchAndSetIconForPage::FetchFromNetwork);
550 1 : return NS_DispatchToMainThread(event);
551 : }
552 :
553 : nsresult
554 1 : AsyncFetchAndSetIconForPage::FetchFromNetwork() {
555 1 : MOZ_ASSERT(NS_IsMainThread());
556 :
557 1 : if (mCanceled) {
558 0 : return NS_OK;
559 : }
560 :
561 : // Ensure data is cleared, since it's going to be overwritten.
562 1 : mIcon.payloads.Clear();
563 :
564 2 : IconPayload payload;
565 1 : mIcon.payloads.AppendElement(payload);
566 :
567 2 : nsCOMPtr<nsIURI> iconURI;
568 1 : nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
569 1 : NS_ENSURE_SUCCESS(rv, rv);
570 2 : nsCOMPtr<nsIChannel> channel;
571 2 : rv = NS_NewChannel(getter_AddRefs(channel),
572 : iconURI,
573 : mLoadingPrincipal,
574 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
575 : nsILoadInfo::SEC_ALLOW_CHROME |
576 : nsILoadInfo::SEC_DISALLOW_SCRIPT,
577 1 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
578 :
579 1 : NS_ENSURE_SUCCESS(rv, rv);
580 : nsCOMPtr<nsIInterfaceRequestor> listenerRequestor =
581 2 : do_QueryInterface(reinterpret_cast<nsISupports*>(this));
582 1 : NS_ENSURE_STATE(listenerRequestor);
583 1 : rv = channel->SetNotificationCallbacks(listenerRequestor);
584 1 : NS_ENSURE_SUCCESS(rv, rv);
585 2 : nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
586 1 : if (pbChannel) {
587 1 : rv = pbChannel->SetPrivate(mFaviconLoadPrivate);
588 1 : NS_ENSURE_SUCCESS(rv, rv);
589 : }
590 :
591 2 : nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(channel);
592 1 : if (priorityChannel) {
593 1 : priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
594 : }
595 :
596 1 : rv = channel->AsyncOpen2(this);
597 1 : if (NS_SUCCEEDED(rv)) {
598 1 : mRequest = channel;
599 : }
600 1 : return rv;
601 : }
602 :
603 : NS_IMETHODIMP
604 0 : AsyncFetchAndSetIconForPage::Cancel()
605 : {
606 0 : MOZ_ASSERT(NS_IsMainThread());
607 0 : if (mCanceled) {
608 0 : return NS_ERROR_UNEXPECTED;
609 : }
610 0 : mCanceled = true;
611 0 : if (mRequest) {
612 0 : mRequest->Cancel(NS_BINDING_ABORTED);
613 : }
614 0 : return NS_OK;
615 : }
616 :
617 : NS_IMETHODIMP
618 1 : AsyncFetchAndSetIconForPage::OnStartRequest(nsIRequest* aRequest,
619 : nsISupports* aContext)
620 : {
621 : // mRequest should already be set from ::FetchFromNetwork, but in the case of
622 : // a redirect we might get a new request, and we should make sure we keep a
623 : // reference to the most current request.
624 1 : mRequest = aRequest;
625 1 : if (mCanceled) {
626 0 : mRequest->Cancel(NS_BINDING_ABORTED);
627 : }
628 1 : return NS_OK;
629 : }
630 :
631 : NS_IMETHODIMP
632 1 : AsyncFetchAndSetIconForPage::OnDataAvailable(nsIRequest* aRequest,
633 : nsISupports* aContext,
634 : nsIInputStream* aInputStream,
635 : uint64_t aOffset,
636 : uint32_t aCount)
637 : {
638 1 : MOZ_ASSERT(mIcon.payloads.Length() == 1);
639 : // Limit downloads to 500KB.
640 1 : const size_t kMaxDownloadSize = 500 * 1024;
641 1 : if (mIcon.payloads[0].data.Length() + aCount > kMaxDownloadSize) {
642 0 : mIcon.payloads.Clear();
643 0 : return NS_ERROR_FILE_TOO_BIG;
644 : }
645 :
646 2 : nsAutoCString buffer;
647 1 : nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
648 1 : if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) {
649 0 : return rv;
650 : }
651 :
652 1 : if (!mIcon.payloads[0].data.Append(buffer, fallible)) {
653 0 : mIcon.payloads.Clear();
654 0 : return NS_ERROR_OUT_OF_MEMORY;
655 : }
656 :
657 1 : return NS_OK;
658 : }
659 :
660 :
661 : NS_IMETHODIMP
662 20 : AsyncFetchAndSetIconForPage::GetInterface(const nsIID& uuid,
663 : void** aResult)
664 : {
665 20 : return QueryInterface(uuid, aResult);
666 : }
667 :
668 :
669 : NS_IMETHODIMP
670 0 : AsyncFetchAndSetIconForPage::AsyncOnChannelRedirect(
671 : nsIChannel* oldChannel
672 : , nsIChannel* newChannel
673 : , uint32_t flags
674 : , nsIAsyncVerifyRedirectCallback *cb
675 : )
676 : {
677 : // If we've been canceled, stop the redirect with NS_BINDING_ABORTED, and
678 : // handle the cancel on the original channel.
679 0 : (void)cb->OnRedirectVerifyCallback(mCanceled ? NS_BINDING_ABORTED : NS_OK);
680 0 : return NS_OK;
681 : }
682 :
683 : NS_IMETHODIMP
684 1 : AsyncFetchAndSetIconForPage::OnStopRequest(nsIRequest* aRequest,
685 : nsISupports* aContext,
686 : nsresult aStatusCode)
687 : {
688 1 : MOZ_ASSERT(NS_IsMainThread());
689 :
690 : // Don't need to track this anymore.
691 1 : mRequest = nullptr;
692 1 : if (mCanceled) {
693 0 : return NS_OK;
694 : }
695 :
696 1 : nsFaviconService* favicons = nsFaviconService::GetFaviconService();
697 1 : NS_ENSURE_STATE(favicons);
698 :
699 : nsresult rv;
700 :
701 : // If fetching the icon failed, add it to the failed cache.
702 1 : if (NS_FAILED(aStatusCode) || mIcon.payloads.Length() == 0) {
703 0 : nsCOMPtr<nsIURI> iconURI;
704 0 : rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
705 0 : NS_ENSURE_SUCCESS(rv, rv);
706 0 : rv = favicons->AddFailedFavicon(iconURI);
707 0 : NS_ENSURE_SUCCESS(rv, rv);
708 0 : return NS_OK;
709 : }
710 :
711 2 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
712 : // aRequest should always QI to nsIChannel.
713 1 : MOZ_ASSERT(channel);
714 :
715 1 : MOZ_ASSERT(mIcon.payloads.Length() == 1);
716 1 : IconPayload& payload = mIcon.payloads[0];
717 :
718 2 : nsAutoCString contentType;
719 1 : channel->GetContentType(contentType);
720 : // Bug 366324 - We don't want to sniff for SVG, so rely on server-specified type.
721 1 : if (contentType.EqualsLiteral(SVG_MIME_TYPE)) {
722 0 : payload.mimeType.AssignLiteral(SVG_MIME_TYPE);
723 0 : payload.width = UINT16_MAX;
724 : } else {
725 1 : NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
726 1 : TO_INTBUFFER(payload.data), payload.data.Length(),
727 1 : payload.mimeType);
728 : }
729 :
730 : // If the icon does not have a valid MIME type, add it to the failed cache.
731 1 : if (payload.mimeType.IsEmpty()) {
732 2 : nsCOMPtr<nsIURI> iconURI;
733 1 : rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
734 1 : NS_ENSURE_SUCCESS(rv, rv);
735 1 : rv = favicons->AddFailedFavicon(iconURI);
736 1 : NS_ENSURE_SUCCESS(rv, rv);
737 1 : return NS_OK;
738 : }
739 :
740 0 : mIcon.expiration = GetExpirationTimeFromChannel(channel);
741 :
742 : // Telemetry probes to measure the favicon file sizes for each different file type.
743 : // This allow us to measure common file sizes while also observing each type popularity.
744 0 : if (payload.mimeType.EqualsLiteral(PNG_MIME_TYPE)) {
745 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_PNG_SIZES, payload.data.Length());
746 : }
747 0 : else if (payload.mimeType.EqualsLiteral("image/x-icon") ||
748 0 : payload.mimeType.EqualsLiteral("image/vnd.microsoft.icon")) {
749 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_ICO_SIZES, payload.data.Length());
750 : }
751 0 : else if (payload.mimeType.EqualsLiteral("image/jpeg") ||
752 0 : payload.mimeType.EqualsLiteral("image/pjpeg")) {
753 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_JPEG_SIZES, payload.data.Length());
754 : }
755 0 : else if (payload.mimeType.EqualsLiteral("image/gif")) {
756 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_GIF_SIZES, payload.data.Length());
757 : }
758 0 : else if (payload.mimeType.EqualsLiteral("image/bmp") ||
759 0 : payload.mimeType.EqualsLiteral("image/x-windows-bmp")) {
760 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_BMP_SIZES, payload.data.Length());
761 : }
762 0 : else if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
763 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_SVG_SIZES, payload.data.Length());
764 : }
765 : else {
766 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_OTHER_SIZES, payload.data.Length());
767 : }
768 :
769 0 : rv = favicons->OptimizeIconSizes(mIcon);
770 0 : NS_ENSURE_SUCCESS(rv, rv);
771 :
772 : // If there's not valid payload, don't store the icon into to the database.
773 0 : if (mIcon.payloads.Length() == 0) {
774 0 : return NS_OK;
775 : }
776 :
777 0 : mIcon.status = ICON_STATUS_CHANGED;
778 :
779 0 : RefPtr<Database> DB = Database::GetDatabase();
780 0 : NS_ENSURE_STATE(DB);
781 : RefPtr<AsyncAssociateIconToPage> event =
782 0 : new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
783 0 : DB->DispatchToAsyncThread(event);
784 :
785 0 : return NS_OK;
786 : }
787 :
788 : ////////////////////////////////////////////////////////////////////////////////
789 : //// AsyncAssociateIconToPage
790 :
791 0 : AsyncAssociateIconToPage::AsyncAssociateIconToPage(
792 : const IconData& aIcon,
793 : const PageData& aPage,
794 0 : const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback)
795 : : Runnable("places::AsyncAssociateIconToPage")
796 : , mCallback(aCallback)
797 : , mIcon(aIcon)
798 0 : , mPage(aPage)
799 : {
800 : // May be created in both threads.
801 0 : }
802 :
803 : NS_IMETHODIMP
804 0 : AsyncAssociateIconToPage::Run()
805 : {
806 0 : MOZ_ASSERT(!NS_IsMainThread());
807 :
808 0 : RefPtr<Database> DB = Database::GetDatabase();
809 0 : NS_ENSURE_STATE(DB);
810 0 : nsresult rv = FetchPageInfo(DB, mPage);
811 0 : if (rv == NS_ERROR_NOT_AVAILABLE){
812 : // We have never seen this page. If we can add the page to history,
813 : // we will try to do it later, otherwise just bail out.
814 0 : if (!mPage.canAddToHistory) {
815 0 : return NS_OK;
816 : }
817 : }
818 : else {
819 0 : NS_ENSURE_SUCCESS(rv, rv);
820 : }
821 :
822 0 : bool shouldUpdateIcon = mIcon.status & ICON_STATUS_CHANGED;
823 0 : if (!shouldUpdateIcon) {
824 0 : for (const auto& payload : mIcon.payloads) {
825 : // If the entry is missing from the database, we should add it.
826 0 : if (payload.id == 0) {
827 0 : shouldUpdateIcon = true;
828 0 : break;
829 : }
830 : }
831 : }
832 :
833 : mozStorageTransaction transaction(DB->MainConn(), false,
834 0 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
835 :
836 0 : if (shouldUpdateIcon) {
837 0 : rv = SetIconInfo(DB, mIcon);
838 0 : NS_ENSURE_SUCCESS(rv, rv);
839 :
840 0 : mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED;
841 : }
842 :
843 : // If the page does not have an id, don't try to insert a new one, cause we
844 : // don't know where the page comes from. Not doing so we may end adding
845 : // a page that otherwise we'd explicitly ignore, like a POST or an error page.
846 0 : if (mPage.placeId == 0) {
847 0 : rv = transaction.Commit();
848 0 : NS_ENSURE_SUCCESS(rv, rv);
849 0 : return NS_OK;
850 : }
851 :
852 : // Don't associate pages to root domain icons, since those will be returned
853 : // regardless. This saves a lot of work and database space since we don't
854 : // need to store urls and relations.
855 : // Though, this is possible only if both the page and the icon have the same
856 : // host, otherwise we couldn't relate them.
857 0 : if (!mIcon.rootIcon || !mIcon.host.Equals(mPage.host)) {
858 : // The page may have associated payloads already, and those could have to be
859 : // expired. For example at a certain point a page could decide to stop serving
860 : // its usual 16px and 32px pngs, and use an svg instead.
861 : // On the other side, we could also be in the process of adding more payloads
862 : // to this page, and we should not expire the payloads we just added.
863 : // For this, we use the expiration field as an indicator and remove relations
864 : // based on it being elapsed. We don't remove orphan icons at this time since
865 : // it would have a cost. The privacy hit is limited since history removal
866 : // methods already expire orphan icons.
867 0 : if (mPage.id != 0) {
868 0 : nsCOMPtr<mozIStorageStatement> stmt;
869 0 : stmt = DB->GetStatement(
870 : "DELETE FROM moz_icons_to_pages "
871 : "WHERE icon_id IN ( "
872 : "SELECT icon_id FROM moz_icons_to_pages "
873 : "JOIN moz_icons i ON icon_id = i.id "
874 : "WHERE page_id = :page_id "
875 : "AND expire_ms < strftime('%s','now','localtime','start of day','-7 days','utc') * 1000 "
876 : ") AND page_id = :page_id "
877 0 : );
878 0 : NS_ENSURE_STATE(stmt);
879 0 : mozStorageStatementScoper scoper(stmt);
880 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
881 0 : NS_ENSURE_SUCCESS(rv, rv);
882 0 : rv = stmt->Execute();
883 0 : NS_ENSURE_SUCCESS(rv, rv);
884 : } else {
885 : // We need to create the page entry.
886 0 : nsCOMPtr<mozIStorageStatement> stmt;
887 0 : stmt = DB->GetStatement(
888 : "INSERT OR IGNORE INTO moz_pages_w_icons (page_url, page_url_hash) "
889 : "VALUES (:page_url, hash(:page_url)) "
890 0 : );
891 0 : NS_ENSURE_STATE(stmt);
892 0 : mozStorageStatementScoper scoper(stmt);
893 0 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
894 0 : NS_ENSURE_SUCCESS(rv, rv);
895 0 : rv = stmt->Execute();
896 0 : NS_ENSURE_SUCCESS(rv, rv);
897 : }
898 :
899 : // Then we can create the relations.
900 0 : nsCOMPtr<mozIStorageStatement> stmt;
901 0 : stmt = DB->GetStatement(
902 : "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
903 : "VALUES ((SELECT id from moz_pages_w_icons WHERE page_url_hash = hash(:page_url) AND page_url = :page_url), "
904 : ":icon_id) "
905 0 : );
906 0 : NS_ENSURE_STATE(stmt);
907 :
908 : // For some reason using BindingParamsArray here fails execution, so we must
909 : // execute the statements one by one.
910 : // In the future we may want to investigate the reasons, sounds like related
911 : // to contraints.
912 0 : for (const auto& payload : mIcon.payloads) {
913 0 : mozStorageStatementScoper scoper(stmt);
914 0 : nsCOMPtr<mozIStorageBindingParams> params;
915 0 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
916 0 : NS_ENSURE_SUCCESS(rv, rv);
917 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), payload.id);
918 0 : NS_ENSURE_SUCCESS(rv, rv);
919 0 : rv = stmt->Execute();
920 0 : NS_ENSURE_SUCCESS(rv, rv);
921 : }
922 : }
923 :
924 0 : mIcon.status |= ICON_STATUS_ASSOCIATED;
925 :
926 0 : rv = transaction.Commit();
927 0 : NS_ENSURE_SUCCESS(rv, rv);
928 :
929 : // Finally, dispatch an event to the main thread to notify observers.
930 0 : nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(mIcon, mPage, mCallback);
931 0 : rv = NS_DispatchToMainThread(event);
932 0 : NS_ENSURE_SUCCESS(rv, rv);
933 :
934 0 : return NS_OK;
935 : }
936 :
937 : ////////////////////////////////////////////////////////////////////////////////
938 : //// AsyncGetFaviconURLForPage
939 :
940 0 : AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
941 : const nsACString& aPageSpec
942 : , const nsACString& aPageHost
943 : , uint16_t aPreferredWidth
944 : , nsIFaviconDataCallback* aCallback
945 0 : ) : Runnable("places::AsyncGetFaviconURLForPage")
946 : , mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
947 : , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(
948 0 : "AsyncGetFaviconURLForPage::mCallback", aCallback))
949 : {
950 0 : MOZ_ASSERT(NS_IsMainThread());
951 0 : mPageSpec.Assign(aPageSpec);
952 0 : mPageHost.Assign(aPageHost);
953 0 : }
954 :
955 : NS_IMETHODIMP
956 0 : AsyncGetFaviconURLForPage::Run()
957 : {
958 0 : MOZ_ASSERT(!NS_IsMainThread());
959 :
960 0 : RefPtr<Database> DB = Database::GetDatabase();
961 0 : NS_ENSURE_STATE(DB);
962 0 : IconData iconData;
963 0 : nsresult rv = FetchIconPerSpec(DB, mPageSpec, mPageHost, iconData, mPreferredWidth);
964 0 : NS_ENSURE_SUCCESS(rv, rv);
965 :
966 : // Now notify our callback of the icon spec we retrieved, even if empty.
967 0 : PageData pageData;
968 0 : pageData.spec.Assign(mPageSpec);
969 :
970 : nsCOMPtr<nsIRunnable> event =
971 0 : new NotifyIconObservers(iconData, pageData, mCallback);
972 0 : rv = NS_DispatchToMainThread(event);
973 0 : NS_ENSURE_SUCCESS(rv, rv);
974 :
975 0 : return NS_OK;
976 : }
977 :
978 : ////////////////////////////////////////////////////////////////////////////////
979 : //// AsyncGetFaviconDataForPage
980 :
981 0 : AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage(
982 : const nsACString& aPageSpec
983 : , const nsACString& aPageHost
984 : , uint16_t aPreferredWidth
985 : , nsIFaviconDataCallback* aCallback
986 0 : ) : Runnable("places::AsyncGetFaviconDataForPage")
987 : , mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
988 : , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(
989 0 : "AsyncGetFaviconDataForPage::mCallback", aCallback))
990 : {
991 0 : MOZ_ASSERT(NS_IsMainThread());
992 0 : mPageSpec.Assign(aPageSpec);
993 0 : mPageHost.Assign(aPageHost);
994 0 : }
995 :
996 : NS_IMETHODIMP
997 0 : AsyncGetFaviconDataForPage::Run()
998 : {
999 0 : MOZ_ASSERT(!NS_IsMainThread());
1000 :
1001 0 : RefPtr<Database> DB = Database::GetDatabase();
1002 0 : NS_ENSURE_STATE(DB);
1003 0 : IconData iconData;
1004 0 : nsresult rv = FetchIconPerSpec(DB, mPageSpec, mPageHost, iconData, mPreferredWidth);
1005 0 : NS_ENSURE_SUCCESS(rv, rv);
1006 :
1007 0 : if (!iconData.spec.IsEmpty()) {
1008 0 : rv = FetchIconInfo(DB, mPreferredWidth, iconData);
1009 0 : if (NS_FAILED(rv)) {
1010 0 : iconData.spec.Truncate();
1011 : }
1012 : }
1013 :
1014 0 : PageData pageData;
1015 0 : pageData.spec.Assign(mPageSpec);
1016 :
1017 : nsCOMPtr<nsIRunnable> event =
1018 0 : new NotifyIconObservers(iconData, pageData, mCallback);
1019 0 : rv = NS_DispatchToMainThread(event);
1020 0 : NS_ENSURE_SUCCESS(rv, rv);
1021 0 : return NS_OK;
1022 : }
1023 :
1024 : ////////////////////////////////////////////////////////////////////////////////
1025 : //// AsyncReplaceFaviconData
1026 :
1027 0 : AsyncReplaceFaviconData::AsyncReplaceFaviconData(const IconData& aIcon)
1028 : : Runnable("places::AsyncReplaceFaviconData")
1029 0 : , mIcon(aIcon)
1030 : {
1031 0 : MOZ_ASSERT(NS_IsMainThread());
1032 0 : }
1033 :
1034 : NS_IMETHODIMP
1035 0 : AsyncReplaceFaviconData::Run()
1036 : {
1037 0 : MOZ_ASSERT(!NS_IsMainThread());
1038 :
1039 0 : RefPtr<Database> DB = Database::GetDatabase();
1040 0 : NS_ENSURE_STATE(DB);
1041 :
1042 : mozStorageTransaction transaction(DB->MainConn(), false,
1043 0 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
1044 0 : nsresult rv = SetIconInfo(DB, mIcon, true);
1045 0 : if (rv == NS_ERROR_NOT_AVAILABLE) {
1046 : // There's no previous icon to replace, we don't need to do anything.
1047 0 : return NS_OK;
1048 : }
1049 0 : NS_ENSURE_SUCCESS(rv, rv);
1050 0 : rv = transaction.Commit();
1051 0 : NS_ENSURE_SUCCESS(rv, rv);
1052 :
1053 : // We can invalidate the cache version since we now persist the icon.
1054 0 : nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1055 : "places::AsyncReplaceFaviconData::RemoveIconDataCacheEntry",
1056 : this,
1057 0 : &AsyncReplaceFaviconData::RemoveIconDataCacheEntry);
1058 0 : rv = NS_DispatchToMainThread(event);
1059 0 : NS_ENSURE_SUCCESS(rv, rv);
1060 :
1061 0 : return NS_OK;
1062 : }
1063 :
1064 : nsresult
1065 0 : AsyncReplaceFaviconData::RemoveIconDataCacheEntry()
1066 : {
1067 0 : MOZ_ASSERT(NS_IsMainThread());
1068 :
1069 0 : nsCOMPtr<nsIURI> iconURI;
1070 0 : nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
1071 0 : NS_ENSURE_SUCCESS(rv, rv);
1072 :
1073 0 : nsFaviconService* favicons = nsFaviconService::GetFaviconService();
1074 0 : NS_ENSURE_STATE(favicons);
1075 0 : favicons->mUnassociatedIcons.RemoveEntry(iconURI);
1076 :
1077 0 : return NS_OK;
1078 : }
1079 :
1080 :
1081 : ////////////////////////////////////////////////////////////////////////////////
1082 : //// NotifyIconObservers
1083 :
1084 0 : NotifyIconObservers::NotifyIconObservers(
1085 : const IconData& aIcon,
1086 : const PageData& aPage,
1087 0 : const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback)
1088 : : Runnable("places::NotifyIconObservers")
1089 : , mCallback(aCallback)
1090 : , mIcon(aIcon)
1091 0 : , mPage(aPage)
1092 : {
1093 0 : }
1094 :
1095 : NS_IMETHODIMP
1096 0 : NotifyIconObservers::Run()
1097 : {
1098 0 : MOZ_ASSERT(NS_IsMainThread());
1099 :
1100 0 : nsCOMPtr<nsIURI> iconURI;
1101 0 : if (!mIcon.spec.IsEmpty()) {
1102 0 : MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(iconURI), mIcon.spec));
1103 0 : if (iconURI)
1104 : {
1105 : // Notify observers only if something changed.
1106 0 : if (mIcon.status & ICON_STATUS_SAVED ||
1107 0 : mIcon.status & ICON_STATUS_ASSOCIATED) {
1108 0 : SendGlobalNotifications(iconURI);
1109 : }
1110 : }
1111 : }
1112 :
1113 0 : if (!mCallback) {
1114 0 : return NS_OK;
1115 : }
1116 :
1117 0 : if (mIcon.payloads.Length() > 0) {
1118 0 : IconPayload& payload = mIcon.payloads[0];
1119 0 : return mCallback->OnComplete(iconURI, payload.data.Length(),
1120 0 : TO_INTBUFFER(payload.data), payload.mimeType,
1121 0 : payload.width);
1122 : }
1123 0 : return mCallback->OnComplete(iconURI, 0, TO_INTBUFFER(EmptyCString()),
1124 0 : EmptyCString(), 0);
1125 : }
1126 :
1127 : void
1128 0 : NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI)
1129 : {
1130 0 : nsCOMPtr<nsIURI> pageURI;
1131 0 : MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(pageURI), mPage.spec));
1132 0 : if (pageURI) {
1133 0 : nsFaviconService* favicons = nsFaviconService::GetFaviconService();
1134 0 : MOZ_ASSERT(favicons);
1135 0 : if (favicons) {
1136 0 : (void)favicons->SendFaviconNotifications(pageURI, aIconURI, mPage.guid);
1137 : }
1138 : }
1139 :
1140 : // If the page is bookmarked and the bookmarked url is different from the
1141 : // updated one, start a new task to update its icon as well.
1142 0 : if (!mPage.bookmarkedSpec.IsEmpty() &&
1143 0 : !mPage.bookmarkedSpec.Equals(mPage.spec)) {
1144 : // Create a new page struct to avoid polluting it with old data.
1145 0 : PageData bookmarkedPage;
1146 0 : bookmarkedPage.spec = mPage.bookmarkedSpec;
1147 :
1148 0 : RefPtr<Database> DB = Database::GetDatabase();
1149 0 : if (!DB)
1150 0 : return;
1151 : // This will be silent, so be sure to not pass in the current callback.
1152 0 : nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
1153 : RefPtr<AsyncAssociateIconToPage> event =
1154 0 : new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
1155 0 : DB->DispatchToAsyncThread(event);
1156 : }
1157 : }
1158 :
1159 : ////////////////////////////////////////////////////////////////////////////////
1160 : //// FetchAndConvertUnsupportedPayloads
1161 :
1162 0 : FetchAndConvertUnsupportedPayloads::FetchAndConvertUnsupportedPayloads(
1163 0 : mozIStorageConnection* aDBConn)
1164 : : Runnable("places::FetchAndConvertUnsupportedPayloads")
1165 0 : , mDB(aDBConn)
1166 : {
1167 :
1168 0 : }
1169 :
1170 : NS_IMETHODIMP
1171 0 : FetchAndConvertUnsupportedPayloads::Run()
1172 : {
1173 0 : if (NS_IsMainThread()) {
1174 0 : Preferences::ClearUser(PREF_CONVERT_PAYLOADS);
1175 0 : return NS_OK;
1176 : }
1177 :
1178 0 : MOZ_ASSERT(!NS_IsMainThread());
1179 0 : NS_ENSURE_STATE(mDB);
1180 :
1181 0 : nsCOMPtr<mozIStorageStatement> stmt;
1182 0 : nsresult rv = mDB->CreateStatement(NS_LITERAL_CSTRING(
1183 : "SELECT id, width, data FROM moz_icons WHERE typeof(width) = 'text' "
1184 : "ORDER BY id ASC "
1185 : "LIMIT 200 "
1186 0 : ), getter_AddRefs(stmt));
1187 0 : NS_ENSURE_SUCCESS(rv, rv);
1188 :
1189 : mozStorageTransaction transaction(mDB, false,
1190 0 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
1191 :
1192 : // We should do the work in chunks, or the wal journal may grow too much.
1193 0 : uint8_t count = 0;
1194 : bool hasResult;
1195 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1196 0 : ++count;
1197 0 : int64_t id = stmt->AsInt64(0);
1198 0 : MOZ_ASSERT(id > 0);
1199 0 : nsAutoCString mimeType;
1200 0 : rv = stmt->GetUTF8String(1, mimeType);
1201 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1202 0 : continue;
1203 : }
1204 : uint8_t* data;
1205 0 : uint32_t dataLen = 0;
1206 0 : rv = stmt->GetBlob(2, &dataLen, &data);
1207 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1208 0 : continue;
1209 : }
1210 0 : nsCString buf;
1211 0 : buf.Adopt(TO_CHARBUFFER(data), dataLen);
1212 :
1213 0 : int32_t width = 0;
1214 0 : rv = ConvertPayload(id, mimeType, buf, &width);
1215 0 : Unused << NS_WARN_IF(NS_FAILED(rv));
1216 0 : if (NS_SUCCEEDED(rv)) {
1217 0 : rv = StorePayload(id, width, buf);
1218 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1219 0 : continue;
1220 : }
1221 : }
1222 : }
1223 :
1224 0 : rv = transaction.Commit();
1225 0 : NS_ENSURE_SUCCESS(rv, rv);
1226 :
1227 0 : if (count == 200) {
1228 : // There are more results to handle. Re-dispatch to the same thread for the
1229 : // next chunk.
1230 0 : return NS_DispatchToCurrentThread(this);
1231 : }
1232 :
1233 : // We're done. Remove any leftovers.
1234 0 : rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1235 : "DELETE FROM moz_icons WHERE typeof(width) = 'text'"
1236 0 : ));
1237 0 : NS_ENSURE_SUCCESS(rv, rv);
1238 : // Run a one-time VACUUM of places.sqlite, since we removed a lot from it.
1239 : // It may cause jank, but not doing it could cause dataloss due to expiration.
1240 0 : rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1241 : "VACUUM"
1242 0 : ));
1243 0 : NS_ENSURE_SUCCESS(rv, rv);
1244 :
1245 : // Re-dispatch to the main-thread to flip the conversion pref.
1246 0 : return NS_DispatchToMainThread(this);
1247 : }
1248 :
1249 : nsresult
1250 0 : FetchAndConvertUnsupportedPayloads::ConvertPayload(int64_t aId,
1251 : const nsACString& aMimeType,
1252 : nsCString& aPayload,
1253 : int32_t* aWidth)
1254 : {
1255 : // TODO (bug 1346139): this should probably be unified with the function that
1256 : // will handle additions optimization off the main thread.
1257 0 : MOZ_ASSERT(!NS_IsMainThread());
1258 0 : *aWidth = 0;
1259 :
1260 : // Exclude invalid mime types.
1261 0 : if (aPayload.Length() == 0 ||
1262 0 : !imgLoader::SupportImageWithMimeType(PromiseFlatCString(aMimeType).get(),
1263 : AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) {
1264 0 : return NS_ERROR_FAILURE;
1265 : }
1266 :
1267 : // If it's an SVG, there's nothing to optimize or convert.
1268 0 : if (aMimeType.EqualsLiteral(SVG_MIME_TYPE)) {
1269 0 : *aWidth = UINT16_MAX;
1270 0 : return NS_OK;
1271 : }
1272 :
1273 : // Convert the payload to an input stream.
1274 0 : nsCOMPtr<nsIInputStream> stream;
1275 0 : nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
1276 0 : aPayload.get(), aPayload.Length(),
1277 0 : NS_ASSIGNMENT_DEPEND);
1278 0 : NS_ENSURE_SUCCESS(rv, rv);
1279 :
1280 : // Decode the input stream to a surface.
1281 : RefPtr<gfx::SourceSurface> surface =
1282 0 : image::ImageOps::DecodeToSurface(stream,
1283 : aMimeType,
1284 0 : imgIContainer::DECODE_FLAGS_DEFAULT);
1285 0 : NS_ENSURE_STATE(surface);
1286 0 : RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
1287 0 : NS_ENSURE_STATE(dataSurface);
1288 :
1289 : // Read the current size and set an appropriate final width.
1290 0 : int32_t width = dataSurface->GetSize().width;
1291 0 : int32_t height = dataSurface->GetSize().height;
1292 : // For non-square images, pick the largest side.
1293 0 : int32_t originalSize = std::max(width, height);
1294 0 : int32_t size = originalSize;
1295 0 : for (uint16_t supportedSize : sFaviconSizes) {
1296 0 : if (supportedSize <= originalSize) {
1297 0 : size = supportedSize;
1298 0 : break;
1299 : }
1300 : }
1301 0 : *aWidth = size;
1302 :
1303 : // If the original payload is png and the size is the same, no reason to
1304 : // rescale the image.
1305 0 : if (aMimeType.EqualsLiteral(PNG_MIME_TYPE) && size == originalSize) {
1306 0 : return NS_OK;
1307 : }
1308 :
1309 : // Rescale when needed.
1310 : RefPtr<gfx::DataSourceSurface> targetDataSurface =
1311 0 : gfx::Factory::CreateDataSourceSurface(gfx::IntSize(size, size),
1312 : gfx::SurfaceFormat::B8G8R8A8,
1313 0 : true);
1314 0 : NS_ENSURE_STATE(targetDataSurface);
1315 :
1316 : { // Block scope for map.
1317 : gfx::DataSourceSurface::MappedSurface map;
1318 0 : if (!targetDataSurface->Map(gfx::DataSourceSurface::MapType::WRITE, &map)) {
1319 0 : return NS_ERROR_FAILURE;
1320 : }
1321 :
1322 : RefPtr<gfx::DrawTarget> dt =
1323 0 : gfx::Factory::CreateDrawTargetForData(gfx::BackendType::CAIRO,
1324 : map.mData,
1325 0 : targetDataSurface->GetSize(),
1326 : map.mStride,
1327 0 : gfx::SurfaceFormat::B8G8R8A8);
1328 0 : NS_ENSURE_STATE(dt);
1329 :
1330 0 : gfx::IntSize frameSize = dataSurface->GetSize();
1331 0 : dt->DrawSurface(dataSurface,
1332 0 : gfx::Rect(0, 0, size, size),
1333 0 : gfx::Rect(0, 0, frameSize.width, frameSize.height),
1334 0 : gfx::DrawSurfaceOptions(),
1335 0 : gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
1336 0 : targetDataSurface->Unmap();
1337 : }
1338 :
1339 : // Finally Encode.
1340 : nsCOMPtr<imgIEncoder> encoder =
1341 0 : do_CreateInstance("@mozilla.org/image/encoder;2?type=image/png");
1342 0 : NS_ENSURE_STATE(encoder);
1343 :
1344 : gfx::DataSourceSurface::MappedSurface map;
1345 0 : if (!targetDataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
1346 0 : return NS_ERROR_FAILURE;
1347 : }
1348 0 : rv = encoder->InitFromData(map.mData, map.mStride * size, size, size,
1349 0 : map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB,
1350 0 : EmptyString());
1351 0 : targetDataSurface->Unmap();
1352 0 : NS_ENSURE_SUCCESS(rv, rv);
1353 :
1354 : // Read the stream into a new buffer.
1355 0 : nsCOMPtr<nsIInputStream> iconStream = do_QueryInterface(encoder);
1356 0 : NS_ENSURE_STATE(iconStream);
1357 0 : rv = NS_ConsumeStream(iconStream, UINT32_MAX, aPayload);
1358 0 : NS_ENSURE_SUCCESS(rv, rv);
1359 :
1360 0 : return NS_OK;
1361 : }
1362 :
1363 : nsresult
1364 0 : FetchAndConvertUnsupportedPayloads::StorePayload(int64_t aId,
1365 : int32_t aWidth,
1366 : const nsCString& aPayload)
1367 : {
1368 0 : MOZ_ASSERT(!NS_IsMainThread());
1369 :
1370 0 : NS_ENSURE_STATE(mDB);
1371 0 : nsCOMPtr<mozIStorageStatement> stmt;
1372 0 : nsresult rv = mDB->CreateStatement(NS_LITERAL_CSTRING(
1373 : "UPDATE moz_icons SET data = :data, width = :width WHERE id = :id"
1374 0 : ), getter_AddRefs(stmt));
1375 0 : NS_ENSURE_SUCCESS(rv, rv);
1376 :
1377 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1378 0 : NS_ENSURE_SUCCESS(rv, rv);
1379 0 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("width"), aWidth);
1380 0 : NS_ENSURE_SUCCESS(rv, rv);
1381 0 : rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
1382 0 : TO_INTBUFFER(aPayload), aPayload.Length());
1383 0 : NS_ENSURE_SUCCESS(rv, rv);
1384 :
1385 0 : rv = stmt->Execute();
1386 0 : NS_ENSURE_SUCCESS(rv, rv);
1387 :
1388 0 : return NS_OK;
1389 : }
1390 :
1391 : ////////////////////////////////////////////////////////////////////////////////
1392 : //// AsyncCopyFavicons
1393 :
1394 0 : AsyncCopyFavicons::AsyncCopyFavicons(PageData& aFromPage,
1395 : PageData& aToPage,
1396 0 : nsIFaviconDataCallback* aCallback)
1397 : : Runnable("places::AsyncCopyFavicons")
1398 : , mFromPage(aFromPage)
1399 : , mToPage(aToPage)
1400 : , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(
1401 0 : "AsyncCopyFavicons::mCallback", aCallback))
1402 : {
1403 0 : MOZ_ASSERT(NS_IsMainThread());
1404 0 : }
1405 :
1406 : NS_IMETHODIMP
1407 0 : AsyncCopyFavicons::Run()
1408 : {
1409 0 : MOZ_ASSERT(!NS_IsMainThread());
1410 :
1411 0 : IconData icon;
1412 :
1413 : // Ensure we'll callback and dispatch notifications to the main-thread.
1414 0 : auto cleanup = MakeScopeExit([&] () {
1415 : // If we bailed out early, just return a null icon uri, since we didn't
1416 : // copy anything.
1417 0 : if (!(icon.status & ICON_STATUS_ASSOCIATED)) {
1418 0 : icon.spec.Truncate();
1419 : }
1420 0 : nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(icon, mToPage, mCallback);
1421 0 : NS_DispatchToMainThread(event);
1422 0 : });
1423 :
1424 0 : RefPtr<Database> DB = Database::GetDatabase();
1425 0 : NS_ENSURE_STATE(DB);
1426 :
1427 0 : nsresult rv = FetchPageInfo(DB, mToPage);
1428 0 : if (rv == NS_ERROR_NOT_AVAILABLE || !mToPage.placeId) {
1429 : // We have never seen this page, or we can't add this page to history and
1430 : // and it's not a bookmark. We won't add the page.
1431 0 : return NS_OK;
1432 : }
1433 0 : NS_ENSURE_SUCCESS(rv, rv);
1434 :
1435 : // Get just one icon, to check whether the page has any, and to notify later.
1436 0 : rv = FetchIconPerSpec(DB, mFromPage.spec, EmptyCString(), icon, UINT16_MAX);
1437 0 : NS_ENSURE_SUCCESS(rv, rv);
1438 :
1439 0 : if (icon.spec.IsEmpty()) {
1440 : // There's nothing to copy.
1441 0 : return NS_OK;
1442 : }
1443 :
1444 : // Insert an entry in moz_pages_w_icons if needed.
1445 0 : if (!mToPage.id) {
1446 : // We need to create the page entry.
1447 0 : nsCOMPtr<mozIStorageStatement> stmt;
1448 0 : stmt = DB->GetStatement(
1449 : "INSERT OR IGNORE INTO moz_pages_w_icons (page_url, page_url_hash) "
1450 : "VALUES (:page_url, hash(:page_url)) "
1451 0 : );
1452 0 : NS_ENSURE_STATE(stmt);
1453 0 : mozStorageStatementScoper scoper(stmt);
1454 0 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mToPage.spec);
1455 0 : NS_ENSURE_SUCCESS(rv, rv);
1456 0 : rv = stmt->Execute();
1457 0 : NS_ENSURE_SUCCESS(rv, rv);
1458 : // Required to to fetch the id and the guid.
1459 0 : rv = FetchPageInfo(DB, mToPage);
1460 0 : NS_ENSURE_SUCCESS(rv, rv);
1461 : }
1462 :
1463 : // Create the relations.
1464 0 : nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
1465 : "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
1466 : "SELECT :id, icon_id "
1467 : "FROM moz_icons_to_pages "
1468 : "WHERE page_id = (SELECT id FROM moz_pages_w_icons WHERE page_url_hash = hash(:url) AND page_url = :url) "
1469 0 : );
1470 0 : NS_ENSURE_STATE(stmt);
1471 0 : mozStorageStatementScoper scoper(stmt);
1472 0 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mToPage.id);
1473 0 : NS_ENSURE_SUCCESS(rv, rv);
1474 0 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), mFromPage.spec);
1475 0 : NS_ENSURE_SUCCESS(rv, rv);
1476 0 : rv = stmt->Execute();
1477 0 : NS_ENSURE_SUCCESS(rv, rv);
1478 :
1479 : // Setting this will make us send pageChanged notifications.
1480 : // The scope exit will take care of the callback and notifications.
1481 0 : icon.status |= ICON_STATUS_ASSOCIATED;
1482 :
1483 0 : return NS_OK;
1484 : }
1485 :
1486 : } // namespace places
1487 : } // namespace mozilla
|