Source code

Revision control

Other Tools

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