Coverage Report

Created: 2018-09-25 14:53

/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