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