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