Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
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
/**
8
* This is the favicon service, which stores favicons for web pages with your
9
* history as you browse. It is also used to save the favicons for bookmarks.
10
*
11
* DANGER: The history query system makes assumptions about the favicon storage
12
* so that icons can be quickly generated for history/bookmark result sets. If
13
* you change the database layout at all, you will have to update both services.
14
*/
15
16
#include "nsFaviconService.h"
17
18
#include "nsNavHistory.h"
19
#include "nsPlacesMacros.h"
20
#include "Helpers.h"
21
22
#include "nsNetUtil.h"
23
#include "nsReadableUtils.h"
24
#include "nsStreamUtils.h"
25
#include "plbase64.h"
26
#include "nsIClassInfoImpl.h"
27
#include "mozilla/ArrayUtils.h"
28
#include "mozilla/LoadInfo.h"
29
#include "mozilla/NullPrincipal.h"
30
#include "mozilla/Preferences.h"
31
#include "nsILoadInfo.h"
32
#include "nsIContentPolicy.h"
33
#include "nsIScriptError.h"
34
#include "nsContentUtils.h"
35
#include "imgICache.h"
36
37
#define UNASSOCIATED_FAVICONS_LENGTH 32
38
39
// When replaceFaviconData is called, we store the icons in an in-memory cache
40
// instead of in storage. Icons in the cache are expired according to this
41
// interval.
42
#define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
43
44
using namespace mozilla;
45
using namespace mozilla::places;
46
47
const uint16_t gFaviconSizes[7] = {192, 144, 96, 64, 48, 32, 16};
48
49
/**
50
* Used to notify a topic to system observers on async execute completion.
51
* Will throw on error.
52
*/
53
class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback {
54
public:
55
ExpireFaviconsStatementCallbackNotifier();
56
NS_IMETHOD HandleCompletion(uint16_t aReason) override;
57
};
58
59
namespace {
60
61
/**
62
* Extracts and filters native sizes from the given container, based on the
63
* list of sizes we are supposed to retain.
64
* All calculation is done considering square sizes and the largest side.
65
* In case of multiple frames of the same size, only the first one is retained.
66
*/
67
nsresult GetFramesInfoForContainer(imgIContainer* aContainer,
68
nsTArray<FrameData>& aFramesInfo) {
69
// Don't extract frames from animated images.
70
bool animated;
71
nsresult rv = aContainer->GetAnimated(&animated);
72
if (NS_FAILED(rv) || !animated) {
73
nsTArray<nsIntSize> nativeSizes;
74
rv = aContainer->GetNativeSizes(nativeSizes);
75
if (NS_SUCCEEDED(rv) && nativeSizes.Length() > 1) {
76
for (uint32_t i = 0; i < nativeSizes.Length(); ++i) {
77
nsIntSize nativeSize = nativeSizes[i];
78
// Only retain square frames.
79
if (nativeSize.width != nativeSize.height) {
80
continue;
81
}
82
// Check if it's one of the sizes we care about.
83
auto end = std::end(gFaviconSizes);
84
const uint16_t* matchingSize =
85
std::find(std::begin(gFaviconSizes), end, nativeSize.width);
86
if (matchingSize != end) {
87
// We must avoid duped sizes, an image could contain multiple frames
88
// of the same size, but we can only store one. We could use an
89
// hashtable, but considered the average low number of frames, we'll
90
// just do a linear search.
91
bool dupe = false;
92
for (const auto& frameInfo : aFramesInfo) {
93
if (frameInfo.width == *matchingSize) {
94
dupe = true;
95
break;
96
}
97
}
98
if (!dupe) {
99
aFramesInfo.AppendElement(FrameData(i, *matchingSize));
100
}
101
}
102
}
103
}
104
}
105
106
if (aFramesInfo.Length() == 0) {
107
// Always have at least the default size.
108
int32_t width;
109
rv = aContainer->GetWidth(&width);
110
NS_ENSURE_SUCCESS(rv, rv);
111
int32_t height;
112
rv = aContainer->GetHeight(&height);
113
NS_ENSURE_SUCCESS(rv, rv);
114
// For non-square images, pick the largest side.
115
aFramesInfo.AppendElement(FrameData(0, std::max(width, height)));
116
}
117
return NS_OK;
118
}
119
120
} // namespace
121
122
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
123
124
NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
125
NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService, nsITimerCallback,
126
nsINamed)
127
128
nsFaviconService::nsFaviconService()
129
: mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH),
130
mDefaultIconURIPreferredSize(UINT16_MAX) {
131
NS_ASSERTION(!gFaviconService,
132
"Attempting to create two instances of the service!");
133
gFaviconService = this;
134
}
135
136
nsFaviconService::~nsFaviconService() {
137
NS_ASSERTION(gFaviconService == this,
138
"Deleting a non-singleton instance of the service");
139
if (gFaviconService == this) gFaviconService = nullptr;
140
}
141
142
Atomic<int64_t> nsFaviconService::sLastInsertedIconId(0);
143
144
void // static
145
nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
146
const int64_t aLastInsertedId) {
147
MOZ_ASSERT(aTable.EqualsLiteral("moz_icons"));
148
sLastInsertedIconId = aLastInsertedId;
149
}
150
151
nsresult nsFaviconService::Init() {
152
mDB = Database::GetDatabase();
153
NS_ENSURE_STATE(mDB);
154
155
mExpireUnassociatedIconsTimer = NS_NewTimer();
156
NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
157
158
// Check if there are still icon payloads to be converted.
159
bool shouldConvertPayloads =
160
Preferences::GetBool(PREF_CONVERT_PAYLOADS, false);
161
if (shouldConvertPayloads) {
162
ConvertUnsupportedPayloads(mDB->MainConn());
163
}
164
165
return NS_OK;
166
}
167
168
NS_IMETHODIMP
169
nsFaviconService::ExpireAllFavicons() {
170
NS_ENSURE_STATE(mDB);
171
172
nsCOMPtr<mozIStorageAsyncStatement> removePagesStmt =
173
mDB->GetAsyncStatement("DELETE FROM moz_pages_w_icons");
174
NS_ENSURE_STATE(removePagesStmt);
175
nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt =
176
mDB->GetAsyncStatement("DELETE FROM moz_icons");
177
NS_ENSURE_STATE(removeIconsStmt);
178
nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt =
179
mDB->GetAsyncStatement("DELETE FROM moz_icons_to_pages");
180
NS_ENSURE_STATE(unlinkIconsStmt);
181
182
nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
183
ToRefPtr(std::move(removePagesStmt)),
184
ToRefPtr(std::move(removeIconsStmt)),
185
ToRefPtr(std::move(unlinkIconsStmt))};
186
nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
187
if (!conn) {
188
return NS_ERROR_UNEXPECTED;
189
}
190
nsCOMPtr<mozIStoragePendingStatement> ps;
191
RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
192
new ExpireFaviconsStatementCallbackNotifier();
193
return conn->ExecuteAsync(stmts, callback, getter_AddRefs(ps));
194
}
195
196
////////////////////////////////////////////////////////////////////////////////
197
//// nsITimerCallback
198
199
NS_IMETHODIMP
200
nsFaviconService::Notify(nsITimer* timer) {
201
if (timer != mExpireUnassociatedIconsTimer.get()) {
202
return NS_ERROR_INVALID_ARG;
203
}
204
205
PRTime now = PR_Now();
206
for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
207
UnassociatedIconHashKey* iconKey = iter.Get();
208
if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
209
iter.Remove();
210
}
211
}
212
213
// Re-init the expiry timer if the cache isn't empty.
214
if (mUnassociatedIcons.Count() > 0) {
215
mExpireUnassociatedIconsTimer->InitWithCallback(
216
this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
217
}
218
219
return NS_OK;
220
}
221
222
////////////////////////////////////////////////////////////////////////
223
//// nsINamed
224
225
NS_IMETHODIMP
226
nsFaviconService::GetName(nsACString& aName) {
227
aName.AssignLiteral("nsFaviconService");
228
return NS_OK;
229
}
230
231
////////////////////////////////////////////////////////////////////////////////
232
//// nsIFaviconService
233
234
NS_IMETHODIMP
235
nsFaviconService::GetDefaultFavicon(nsIURI** _retval) {
236
NS_ENSURE_ARG_POINTER(_retval);
237
238
// not found, use default
239
if (!mDefaultIcon) {
240
nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
241
NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
242
NS_ENSURE_SUCCESS(rv, rv);
243
}
244
245
nsCOMPtr<nsIURI> uri = mDefaultIcon;
246
uri.forget(_retval);
247
return NS_OK;
248
}
249
250
NS_IMETHODIMP
251
nsFaviconService::GetDefaultFaviconMimeType(nsACString& _retval) {
252
_retval = NS_LITERAL_CSTRING(FAVICON_DEFAULT_MIMETYPE);
253
return NS_OK;
254
}
255
256
void nsFaviconService::SendFaviconNotifications(nsIURI* aPageURI,
257
nsIURI* aFaviconURI,
258
const nsACString& aGUID) {
259
nsAutoCString faviconSpec;
260
nsNavHistory* history = nsNavHistory::GetHistoryService();
261
if (history && NS_SUCCEEDED(aFaviconURI->GetSpec(faviconSpec))) {
262
// Invalide page-icon image cache, since the icon is about to change.
263
nsCString spec;
264
nsresult rv = aPageURI->GetSpec(spec);
265
MOZ_ASSERT(NS_SUCCEEDED(rv));
266
if (NS_SUCCEEDED(rv)) {
267
nsCString pageIconSpec("page-icon:");
268
pageIconSpec.Append(spec);
269
nsCOMPtr<nsIURI> pageIconURI;
270
rv = NS_NewURI(getter_AddRefs(pageIconURI), pageIconSpec);
271
MOZ_ASSERT(NS_SUCCEEDED(rv));
272
if (NS_SUCCEEDED(rv)) {
273
nsCOMPtr<imgICache> imgCache;
274
rv = GetImgTools()->GetImgCacheForDocument(nullptr,
275
getter_AddRefs(imgCache));
276
MOZ_ASSERT(NS_SUCCEEDED(rv));
277
if (NS_SUCCEEDED(rv)) {
278
Unused << imgCache->RemoveEntry(pageIconURI, nullptr);
279
}
280
}
281
}
282
283
history->SendPageChangedNotification(
284
aPageURI, nsINavHistoryObserver::ATTRIBUTE_FAVICON,
285
NS_ConvertUTF8toUTF16(faviconSpec), aGUID);
286
}
287
}
288
289
NS_IMETHODIMP
290
nsFaviconService::SetAndFetchFaviconForPage(
291
nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload,
292
uint32_t aFaviconLoadType, nsIFaviconDataCallback* aCallback,
293
nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
294
mozIPlacesPendingOperation** _canceler) {
295
MOZ_ASSERT(NS_IsMainThread());
296
NS_ENSURE_ARG(aPageURI);
297
NS_ENSURE_ARG(aFaviconURI);
298
NS_ENSURE_ARG_POINTER(_canceler);
299
300
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
301
MOZ_ASSERT(loadingPrincipal,
302
"please provide aLoadingPrincipal for this favicon");
303
if (!loadingPrincipal) {
304
// Let's default to the nullPrincipal if no loadingPrincipal is provided.
305
AutoTArray<nsString, 2> params = {
306
NS_LITERAL_STRING("nsFaviconService::setAndFetchFaviconForPage()"),
307
NS_LITERAL_STRING("nsFaviconService::setAndFetchFaviconForPage(..., "
308
"[optional aLoadingPrincipal])")};
309
nsContentUtils::ReportToConsole(
310
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Security by Default"),
311
nullptr, // aDocument
312
nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
313
loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
314
}
315
NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
316
317
bool loadPrivate =
318
aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
319
320
// Build page data.
321
PageData page;
322
nsresult rv = aPageURI->GetSpec(page.spec);
323
NS_ENSURE_SUCCESS(rv, rv);
324
// URIs can arguably lack a host.
325
Unused << aPageURI->GetHost(page.host);
326
if (StringBeginsWith(page.host, NS_LITERAL_CSTRING("www."))) {
327
page.host.Cut(0, 4);
328
}
329
bool canAddToHistory;
330
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
331
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
332
rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
333
NS_ENSURE_SUCCESS(rv, rv);
334
page.canAddToHistory = !!canAddToHistory && !loadPrivate;
335
336
// Build icon data.
337
IconData icon;
338
// If we have an in-memory icon payload, it overwrites the actual request.
339
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
340
if (iconKey) {
341
icon = iconKey->iconData;
342
mUnassociatedIcons.RemoveEntry(iconKey);
343
} else {
344
icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
345
rv = aFaviconURI->GetSpec(icon.spec);
346
NS_ENSURE_SUCCESS(rv, rv);
347
// URIs can arguably lack a host.
348
Unused << aFaviconURI->GetHost(icon.host);
349
if (StringBeginsWith(icon.host, NS_LITERAL_CSTRING("www."))) {
350
icon.host.Cut(0, 4);
351
}
352
}
353
354
// A root icon is when the icon and page have the same host and the path
355
// is just /favicon.ico. These icons are considered valid for the whole
356
// origin and expired with the origin through a trigger.
357
nsAutoCString path;
358
if (NS_SUCCEEDED(aFaviconURI->GetPathQueryRef(path)) &&
359
!icon.host.IsEmpty() && icon.host.Equals(page.host) &&
360
path.EqualsLiteral("/favicon.ico")) {
361
icon.rootIcon = 1;
362
}
363
364
// If the page url points to an image, the icon's url will be the same.
365
// TODO (Bug 403651): store a resample of the image. For now avoid that
366
// for database size and UX concerns.
367
// Don't store favicons for error pages too.
368
if (icon.spec.Equals(page.spec) ||
369
icon.spec.EqualsLiteral(FAVICON_ERRORPAGE_URL)) {
370
return NS_OK;
371
}
372
373
RefPtr<AsyncFetchAndSetIconForPage> event = new AsyncFetchAndSetIconForPage(
374
icon, page, loadPrivate, aCallback, aLoadingPrincipal, aRequestContextID);
375
376
// Get the target thread and start the work.
377
// DB will be updated and observers notified when data has finished loading.
378
RefPtr<Database> DB = Database::GetDatabase();
379
NS_ENSURE_STATE(DB);
380
DB->DispatchToAsyncThread(event);
381
382
// Return this event to the caller to allow aborting an eventual fetch.
383
event.forget(_canceler);
384
385
return NS_OK;
386
}
387
388
NS_IMETHODIMP
389
nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
390
const nsTArray<uint8_t>& aData,
391
const nsACString& aMimeType,
392
PRTime aExpiration) {
393
MOZ_ASSERT(NS_IsMainThread());
394
NS_ENSURE_ARG(aFaviconURI);
395
NS_ENSURE_ARG(aData.Length() > 0);
396
NS_ENSURE_ARG(aMimeType.Length() > 0);
397
NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(
398
PromiseFlatCString(aMimeType).get(),
399
AcceptedMimeTypes::IMAGES_AND_DOCUMENTS));
400
401
if (aExpiration == 0) {
402
aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
403
}
404
405
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
406
if (!iconKey) {
407
return NS_ERROR_OUT_OF_MEMORY;
408
}
409
410
iconKey->created = PR_Now();
411
412
// If the cache contains unassociated icons, an expiry timer should already
413
// exist, otherwise there may be a timer left hanging around, so make sure we
414
// fire a new one.
415
int32_t unassociatedCount = mUnassociatedIcons.Count();
416
if (unassociatedCount == 1) {
417
mExpireUnassociatedIconsTimer->Cancel();
418
mExpireUnassociatedIconsTimer->InitWithCallback(
419
this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
420
}
421
422
IconData* iconData = &(iconKey->iconData);
423
iconData->expiration = aExpiration;
424
iconData->status = ICON_STATUS_CACHED;
425
iconData->fetchMode = FETCH_NEVER;
426
nsresult rv = aFaviconURI->GetSpec(iconData->spec);
427
NS_ENSURE_SUCCESS(rv, rv);
428
// URIs can arguably lack a host.
429
Unused << aFaviconURI->GetHost(iconData->host);
430
if (StringBeginsWith(iconData->host, NS_LITERAL_CSTRING("www."))) {
431
iconData->host.Cut(0, 4);
432
}
433
434
// Note we can't set rootIcon here, because don't know the page it will be
435
// associated with. We'll do that later in SetAndFetchFaviconForPage if the
436
// icon doesn't exist; otherwise, if AsyncReplaceFaviconData updates an
437
// existing icon, it will take care of not overwriting an existing
438
// root = 1 value.
439
440
IconPayload payload;
441
payload.mimeType = aMimeType;
442
payload.data.Assign(TO_CHARBUFFER(aData.Elements()), aData.Length());
443
if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
444
payload.width = UINT16_MAX;
445
}
446
// There may already be a previous payload, so ensure to only have one.
447
iconData->payloads.Clear();
448
iconData->payloads.AppendElement(payload);
449
450
rv = OptimizeIconSizes(*iconData);
451
NS_ENSURE_SUCCESS(rv, rv);
452
453
// If there's not valid payload, don't store the icon into to the database.
454
if ((*iconData).payloads.Length() == 0) {
455
// We cannot optimize this favicon size and we are over the maximum size
456
// allowed, so we will not save data to the db to avoid bloating it.
457
mUnassociatedIcons.RemoveEntry(aFaviconURI);
458
return NS_ERROR_FAILURE;
459
}
460
461
// If the database contains an icon at the given url, we will update the
462
// database immediately so that the associated pages are kept in sync.
463
// Otherwise, do nothing and let the icon be picked up from the memory hash.
464
RefPtr<AsyncReplaceFaviconData> event =
465
new AsyncReplaceFaviconData(*iconData);
466
RefPtr<Database> DB = Database::GetDatabase();
467
NS_ENSURE_STATE(DB);
468
DB->DispatchToAsyncThread(event);
469
470
return NS_OK;
471
}
472
473
NS_IMETHODIMP
474
nsFaviconService::ReplaceFaviconDataFromDataURL(
475
nsIURI* aFaviconURI, const nsAString& aDataURL, PRTime aExpiration,
476
nsIPrincipal* aLoadingPrincipal) {
477
NS_ENSURE_ARG(aFaviconURI);
478
NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
479
if (aExpiration == 0) {
480
aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
481
}
482
483
nsCOMPtr<nsIURI> dataURI;
484
nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
485
NS_ENSURE_SUCCESS(rv, rv);
486
487
// Use the data: protocol handler to convert the data.
488
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
489
NS_ENSURE_SUCCESS(rv, rv);
490
nsCOMPtr<nsIProtocolHandler> protocolHandler;
491
rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
492
NS_ENSURE_SUCCESS(rv, rv);
493
494
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
495
MOZ_ASSERT(loadingPrincipal,
496
"please provide aLoadingPrincipal for this favicon");
497
if (!loadingPrincipal) {
498
// Let's default to the nullPrincipal if no loadingPrincipal is provided.
499
AutoTArray<nsString, 2> params = {
500
NS_LITERAL_STRING("nsFaviconService::ReplaceFaviconDataFromDataURL()"),
501
NS_LITERAL_STRING("nsFaviconService::ReplaceFaviconDataFromDataURL(...,"
502
" [optional aLoadingPrincipal])")};
503
nsContentUtils::ReportToConsole(
504
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Security by Default"),
505
nullptr, // aDocument
506
nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
507
508
loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
509
}
510
NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
511
512
nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
513
loadingPrincipal,
514
nullptr, // aTriggeringPrincipal
515
nullptr, // aLoadingNode
516
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
517
nsILoadInfo::SEC_ALLOW_CHROME | nsILoadInfo::SEC_DISALLOW_SCRIPT,
518
nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
519
520
nsCOMPtr<nsIChannel> channel;
521
rv = protocolHandler->NewChannel(dataURI, loadInfo, getter_AddRefs(channel));
522
NS_ENSURE_SUCCESS(rv, rv);
523
524
// Blocking stream is OK for data URIs.
525
nsCOMPtr<nsIInputStream> stream;
526
rv = channel->Open(getter_AddRefs(stream));
527
NS_ENSURE_SUCCESS(rv, rv);
528
529
uint64_t available64;
530
rv = stream->Available(&available64);
531
NS_ENSURE_SUCCESS(rv, rv);
532
if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t))
533
return NS_ERROR_FILE_TOO_BIG;
534
uint32_t available = (uint32_t)available64;
535
536
// Read all the decoded data.
537
nsTArray<uint8_t> buffer;
538
buffer.SetLength(available);
539
uint32_t numRead;
540
rv = stream->Read(TO_CHARBUFFER(buffer.Elements()), available, &numRead);
541
if (NS_FAILED(rv) || numRead != available) {
542
return rv;
543
}
544
545
nsAutoCString mimeType;
546
rv = channel->GetContentType(mimeType);
547
if (NS_FAILED(rv)) {
548
return rv;
549
}
550
551
// ReplaceFaviconData can now do the dirty work.
552
rv = ReplaceFaviconData(aFaviconURI, buffer, mimeType, aExpiration);
553
NS_ENSURE_SUCCESS(rv, rv);
554
555
return NS_OK;
556
}
557
558
NS_IMETHODIMP
559
nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
560
nsIFaviconDataCallback* aCallback,
561
uint16_t aPreferredWidth) {
562
MOZ_ASSERT(NS_IsMainThread());
563
NS_ENSURE_ARG(aPageURI);
564
NS_ENSURE_ARG(aCallback);
565
566
nsAutoCString pageSpec;
567
nsresult rv = aPageURI->GetSpec(pageSpec);
568
NS_ENSURE_SUCCESS(rv, rv);
569
nsAutoCString pageHost;
570
// It's expected that some domains may not have a host.
571
Unused << aPageURI->GetHost(pageHost);
572
573
RefPtr<AsyncGetFaviconURLForPage> event = new AsyncGetFaviconURLForPage(
574
pageSpec, pageHost, aPreferredWidth, aCallback);
575
576
RefPtr<Database> DB = Database::GetDatabase();
577
NS_ENSURE_STATE(DB);
578
DB->DispatchToAsyncThread(event);
579
580
return NS_OK;
581
}
582
583
NS_IMETHODIMP
584
nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
585
nsIFaviconDataCallback* aCallback,
586
uint16_t aPreferredWidth) {
587
MOZ_ASSERT(NS_IsMainThread());
588
NS_ENSURE_ARG(aPageURI);
589
NS_ENSURE_ARG(aCallback);
590
591
nsAutoCString pageSpec;
592
nsresult rv = aPageURI->GetSpec(pageSpec);
593
NS_ENSURE_SUCCESS(rv, rv);
594
nsAutoCString pageHost;
595
// It's expected that some domains may not have a host.
596
Unused << aPageURI->GetHost(pageHost);
597
598
RefPtr<AsyncGetFaviconDataForPage> event = new AsyncGetFaviconDataForPage(
599
pageSpec, pageHost, aPreferredWidth, aCallback);
600
RefPtr<Database> DB = Database::GetDatabase();
601
NS_ENSURE_STATE(DB);
602
DB->DispatchToAsyncThread(event);
603
604
return NS_OK;
605
}
606
607
NS_IMETHODIMP
608
nsFaviconService::CopyFavicons(nsIURI* aFromPageURI, nsIURI* aToPageURI,
609
uint32_t aFaviconLoadType,
610
nsIFaviconDataCallback* aCallback) {
611
MOZ_ASSERT(NS_IsMainThread());
612
NS_ENSURE_ARG(aFromPageURI);
613
NS_ENSURE_ARG(aToPageURI);
614
NS_ENSURE_TRUE(
615
aFaviconLoadType >= nsIFaviconService::FAVICON_LOAD_PRIVATE &&
616
aFaviconLoadType <= nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
617
NS_ERROR_INVALID_ARG);
618
619
PageData fromPage;
620
nsresult rv = aFromPageURI->GetSpec(fromPage.spec);
621
NS_ENSURE_SUCCESS(rv, rv);
622
PageData toPage;
623
rv = aToPageURI->GetSpec(toPage.spec);
624
NS_ENSURE_SUCCESS(rv, rv);
625
626
bool canAddToHistory;
627
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
628
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
629
rv = navHistory->CanAddURI(aToPageURI, &canAddToHistory);
630
NS_ENSURE_SUCCESS(rv, rv);
631
toPage.canAddToHistory =
632
!!canAddToHistory &&
633
aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE;
634
635
RefPtr<AsyncCopyFavicons> event =
636
new AsyncCopyFavicons(fromPage, toPage, aCallback);
637
638
// Get the target thread and start the work.
639
// DB will be updated and observers notified when done.
640
RefPtr<Database> DB = Database::GetDatabase();
641
NS_ENSURE_STATE(DB);
642
DB->DispatchToAsyncThread(event);
643
644
return NS_OK;
645
}
646
647
nsresult nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
648
nsIURI** aOutputURI) {
649
NS_ENSURE_ARG(aFaviconURI);
650
NS_ENSURE_ARG_POINTER(aOutputURI);
651
652
nsAutoCString spec;
653
if (aFaviconURI) {
654
nsresult rv = aFaviconURI->GetSpec(spec);
655
NS_ENSURE_SUCCESS(rv, rv);
656
}
657
return GetFaviconLinkForIconString(spec, aOutputURI);
658
}
659
660
// nsFaviconService::GetFaviconLinkForIconString
661
//
662
// This computes a favicon URL with string input and using the cached
663
// default one to minimize parsing.
664
665
nsresult nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
666
nsIURI** aOutput) {
667
if (aSpec.IsEmpty()) {
668
return GetDefaultFavicon(aOutput);
669
}
670
671
if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
672
// pass through for chrome URLs, since they can be referenced without
673
// this service
674
return NS_NewURI(aOutput, aSpec);
675
}
676
677
nsAutoCString annoUri;
678
annoUri.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
679
annoUri += aSpec;
680
return NS_NewURI(aOutput, annoUri);
681
}
682
683
/**
684
* Checks the icon and evaluates if it needs to be optimized.
685
*
686
* @param aIcon
687
* The icon to be evaluated.
688
*/
689
nsresult nsFaviconService::OptimizeIconSizes(IconData& aIcon) {
690
// TODO (bug 1346139): move optimization to the async thread.
691
MOZ_ASSERT(NS_IsMainThread());
692
// There should only be a single payload at this point, it may have to be
693
// split though, if it's an ico file.
694
MOZ_ASSERT(aIcon.payloads.Length() == 1);
695
696
// Even if the page provides a large image for the favicon (eg, a highres
697
// image or a multiresolution .ico file), don't try to store more data than
698
// needed.
699
IconPayload payload = aIcon.payloads[0];
700
if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
701
// Nothing to optimize, but check the payload size.
702
if (payload.data.Length() >= nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
703
aIcon.payloads.Clear();
704
}
705
return NS_OK;
706
}
707
708
// Make space for the optimized payloads.
709
aIcon.payloads.Clear();
710
711
// decode image
712
nsCOMPtr<imgIContainer> container;
713
nsresult rv = GetImgTools()->DecodeImageFromBuffer(
714
payload.data.get(), payload.data.Length(), payload.mimeType,
715
getter_AddRefs(container));
716
NS_ENSURE_SUCCESS(rv, rv);
717
718
// For ICO files, we must evaluate each of the frames we care about.
719
nsTArray<FrameData> framesInfo;
720
rv = GetFramesInfoForContainer(container, framesInfo);
721
NS_ENSURE_SUCCESS(rv, rv);
722
723
for (const auto& frameInfo : framesInfo) {
724
IconPayload newPayload;
725
newPayload.mimeType = NS_LITERAL_CSTRING(PNG_MIME_TYPE);
726
newPayload.width = frameInfo.width;
727
for (uint16_t size : gFaviconSizes) {
728
// The icon could be smaller than 16, that is our minimum.
729
// Icons smaller than 16px are kept as-is.
730
if (frameInfo.width >= 16) {
731
if (size > frameInfo.width) {
732
continue;
733
}
734
newPayload.width = size;
735
}
736
737
// If the original payload is png and the size is the same, rescale the
738
// image only if it's larger than the maximum allowed.
739
if (newPayload.mimeType.Equals(payload.mimeType) &&
740
newPayload.width == frameInfo.width &&
741
payload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
742
newPayload.data = payload.data;
743
} else {
744
// Otherwise, scale and recompress.
745
// Since EncodeScaledImage uses SYNC_DECODE, it will pick the best
746
// frame.
747
nsCOMPtr<nsIInputStream> iconStream;
748
rv = GetImgTools()->EncodeScaledImage(
749
container, newPayload.mimeType, newPayload.width, newPayload.width,
750
EmptyString(), getter_AddRefs(iconStream));
751
NS_ENSURE_SUCCESS(rv, rv);
752
// Read the stream into the new buffer.
753
rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
754
NS_ENSURE_SUCCESS(rv, rv);
755
}
756
757
// If the icon size is good, we are done, otherwise try the next size.
758
if (newPayload.data.Length() <
759
nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
760
break;
761
}
762
}
763
764
MOZ_ASSERT(newPayload.data.Length() <
765
nsIFaviconService::MAX_FAVICON_BUFFER_SIZE);
766
if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
767
aIcon.payloads.AppendElement(newPayload);
768
}
769
}
770
771
return NS_OK;
772
}
773
774
nsresult nsFaviconService::GetFaviconDataAsync(
775
const nsCString& aFaviconURI, mozIStorageStatementCallback* aCallback) {
776
MOZ_ASSERT(aCallback, "Doesn't make sense to call this without a callback");
777
778
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
779
"/*Do not warn (bug no: not worth adding an index */ "
780
"SELECT data, width FROM moz_icons "
781
"WHERE fixed_icon_url_hash = hash(fixup_url(:url)) AND icon_url = :url "
782
"ORDER BY width DESC");
783
NS_ENSURE_STATE(stmt);
784
785
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aFaviconURI);
786
NS_ENSURE_SUCCESS(rv, rv);
787
788
nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
789
return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
790
}
791
792
void // static
793
nsFaviconService::ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn) {
794
MOZ_ASSERT(NS_IsMainThread());
795
// Ensure imgTools are initialized, so that the image decoders can be used
796
// off the main thread.
797
nsCOMPtr<imgITools> imgTools =
798
do_CreateInstance("@mozilla.org/image/tools;1");
799
800
Preferences::SetBool(PREF_CONVERT_PAYLOADS, true);
801
MOZ_ASSERT(aDBConn);
802
if (aDBConn) {
803
RefPtr<FetchAndConvertUnsupportedPayloads> event =
804
new FetchAndConvertUnsupportedPayloads(aDBConn);
805
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aDBConn);
806
MOZ_ASSERT(target);
807
if (target) {
808
(void)target->Dispatch(event, NS_DISPATCH_NORMAL);
809
}
810
}
811
}
812
813
NS_IMETHODIMP
814
nsFaviconService::SetDefaultIconURIPreferredSize(uint16_t aDefaultSize) {
815
mDefaultIconURIPreferredSize = aDefaultSize > 0 ? aDefaultSize : UINT16_MAX;
816
return NS_OK;
817
}
818
819
NS_IMETHODIMP
820
nsFaviconService::PreferredSizeFromURI(nsIURI* aURI, uint16_t* _size) {
821
*_size = mDefaultIconURIPreferredSize;
822
nsAutoCString ref;
823
// Check for a ref first.
824
if (NS_FAILED(aURI->GetRef(ref)) || ref.Length() == 0) return NS_OK;
825
826
// Look for a "size=" fragment.
827
int32_t start = ref.RFind("size=");
828
if (start >= 0 && ref.Length() > static_cast<uint32_t>(start) + 5) {
829
nsDependentCSubstring size;
830
// This is safe regardless, since Rebind checks start is not over Length().
831
size.Rebind(ref, start + 5);
832
// Check if the string contains any non-digit.
833
auto begin = size.BeginReading(), end = size.EndReading();
834
for (auto ch = begin; ch < end; ++ch) {
835
if (*ch < '0' || *ch > '9') {
836
// Not a digit.
837
return NS_OK;
838
}
839
}
840
// Convert the string to an integer value.
841
nsresult rv;
842
uint16_t val = PromiseFlatCString(size).ToInteger(&rv);
843
if (NS_SUCCEEDED(rv)) {
844
*_size = val;
845
}
846
}
847
return NS_OK;
848
}
849
850
////////////////////////////////////////////////////////////////////////////////
851
//// ExpireFaviconsStatementCallbackNotifier
852
853
ExpireFaviconsStatementCallbackNotifier::
854
ExpireFaviconsStatementCallbackNotifier() = default;
855
856
NS_IMETHODIMP
857
ExpireFaviconsStatementCallbackNotifier::HandleCompletion(uint16_t aReason) {
858
// We should dispatch only if expiration has been successful.
859
if (aReason != mozIStorageStatementCallback::REASON_FINISHED) return NS_OK;
860
861
nsCOMPtr<nsIObserverService> observerService =
862
mozilla::services::GetObserverService();
863
if (observerService) {
864
(void)observerService->NotifyObservers(
865
nullptr, NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID, nullptr);
866
}
867
868
return NS_OK;
869
}