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