1/*
2 * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ResourceLoadStatistics.h"
28
29#include "KeyedCoding.h"
30#include "PublicSuffix.h"
31#include <wtf/MainThread.h>
32#include <wtf/text/ASCIILiteral.h>
33#include <wtf/text/StringBuilder.h>
34#include <wtf/text/StringHash.h>
35
36namespace WebCore {
37
38static Seconds timestampResolution { 5_s };
39
40typedef WTF::HashMap<RegistrableDomain, unsigned, RegistrableDomain::RegistrableDomainHash, HashTraits<RegistrableDomain>, HashTraits<unsigned>>::KeyValuePairType ResourceLoadStatisticsValue;
41
42static void encodeHashSet(KeyedEncoder& encoder, const String& label, const String& key, const HashSet<RegistrableDomain>& hashSet)
43{
44 if (hashSet.isEmpty())
45 return;
46
47 encoder.encodeObjects(label, hashSet.begin(), hashSet.end(), [&key](KeyedEncoder& encoderInner, const RegistrableDomain& domain) {
48 encoderInner.encodeString(key, domain.string());
49 });
50}
51
52template<typename T>
53static void encodeOptionSet(KeyedEncoder& encoder, const String& label, const OptionSet<T>& optionSet)
54{
55 if (optionSet.isEmpty())
56 return;
57
58 uint64_t optionSetBitMask = optionSet.toRaw();
59 encoder.encodeUInt64(label, optionSetBitMask);
60}
61
62#if ENABLE(WEB_API_STATISTICS)
63static void encodeFontHashSet(KeyedEncoder& encoder, const String& label, const HashSet<String>& hashSet)
64{
65 encodeHashSet(encoder, label, "font", hashSet);
66}
67
68static void encodeCanvasActivityRecord(KeyedEncoder& encoder, const String& label, const CanvasActivityRecord& canvasActivityRecord)
69{
70 encoder.encodeObject(label, canvasActivityRecord, [] (KeyedEncoder& encoderInner, const CanvasActivityRecord& canvasActivityRecord) {
71 encoderInner.encodeBool("wasDataRead", canvasActivityRecord.wasDataRead);
72 encoderInner.encodeObjects("textWritten", canvasActivityRecord.textWritten.begin(), canvasActivityRecord.textWritten.end(), [] (KeyedEncoder& encoderInner2, const String& text) {
73 encoderInner2.encodeString("text", text);
74 });
75 });
76}
77#endif
78
79void ResourceLoadStatistics::encode(KeyedEncoder& encoder) const
80{
81 encoder.encodeString("PrevalentResourceDomain"_s, registrableDomain.string());
82
83 encoder.encodeDouble("lastSeen"_s, lastSeen.secondsSinceEpoch().value());
84
85 // User interaction
86 encoder.encodeBool("hadUserInteraction"_s, hadUserInteraction);
87 encoder.encodeDouble("mostRecentUserInteraction"_s, mostRecentUserInteractionTime.secondsSinceEpoch().value());
88 encoder.encodeBool("grandfathered"_s, grandfathered);
89
90 // Storage access
91 encodeHashSet(encoder, "storageAccessUnderTopFrameDomains"_s, "domain"_s, storageAccessUnderTopFrameDomains);
92
93 // Top frame stats
94 encodeHashSet(encoder, "topFrameUniqueRedirectsTo"_s, "domain"_s, topFrameUniqueRedirectsTo);
95 encodeHashSet(encoder, "topFrameUniqueRedirectsFrom"_s, "domain"_s, topFrameUniqueRedirectsFrom);
96 encodeHashSet(encoder, "topFrameLinkDecorationsFrom"_s, "domain", topFrameLinkDecorationsFrom);
97 encoder.encodeBool("gotLinkDecorationFromPrevalentResource"_s, gotLinkDecorationFromPrevalentResource);
98
99 // Subframe stats
100 encodeHashSet(encoder, "subframeUnderTopFrameDomains"_s, "domain"_s, subframeUnderTopFrameDomains);
101
102 // Subresource stats
103 encodeHashSet(encoder, "subresourceUnderTopFrameDomains"_s, "domain"_s, subresourceUnderTopFrameDomains);
104 encodeHashSet(encoder, "subresourceUniqueRedirectsTo"_s, "domain"_s, subresourceUniqueRedirectsTo);
105 encodeHashSet(encoder, "subresourceUniqueRedirectsFrom"_s, "domain"_s, subresourceUniqueRedirectsFrom);
106
107 // Prevalent Resource
108 encoder.encodeBool("isPrevalentResource"_s, isPrevalentResource);
109 encoder.encodeBool("isVeryPrevalentResource"_s, isVeryPrevalentResource);
110 encoder.encodeUInt32("dataRecordsRemoved"_s, dataRecordsRemoved);
111
112 encoder.encodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction"_s, timesAccessedAsFirstPartyDueToUserInteraction);
113 encoder.encodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI"_s, timesAccessedAsFirstPartyDueToStorageAccessAPI);
114
115#if ENABLE(WEB_API_STATISTICS)
116 encodeFontHashSet(encoder, "fontsFailedToLoad", fontsFailedToLoad);
117 encodeFontHashSet(encoder, "fontsSuccessfullyLoaded", fontsSuccessfullyLoaded);
118 encodeHashCountedSet(encoder, "topFrameRegistrableDomainsWhichAccessedWebAPIs", topFrameRegistrableDomainsWhichAccessedWebAPIs);
119 encodeCanvasActivityRecord(encoder, "canvasActivityRecord", canvasActivityRecord);
120 encodeOptionSet(encoder, "navigatorFunctionsAccessedBitMask", navigatorFunctionsAccessed);
121 encodeOptionSet(encoder, "screenFunctionsAccessedBitMask", screenFunctionsAccessed);
122#endif
123}
124
125static void decodeHashCountedSet(KeyedDecoder& decoder, const String& label, HashCountedSet<RegistrableDomain>& hashCountedSet)
126{
127 Vector<String> ignore;
128 decoder.decodeObjects(label, ignore, [&hashCountedSet](KeyedDecoder& decoderInner, String& domain) {
129 if (!decoderInner.decodeString("origin", domain))
130 return false;
131
132 unsigned count;
133 if (!decoderInner.decodeUInt32("count", count))
134 return false;
135
136 hashCountedSet.add(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(domain), count);
137 return true;
138 });
139}
140
141static void decodeHashSet(KeyedDecoder& decoder, const String& label, const String& key, HashSet<RegistrableDomain>& hashSet)
142{
143 Vector<String> ignore;
144 decoder.decodeObjects(label, ignore, [&hashSet, &key](KeyedDecoder& decoderInner, String& domain) {
145 if (!decoderInner.decodeString(key, domain))
146 return false;
147
148 hashSet.add(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(domain));
149 return true;
150 });
151}
152
153template<typename T>
154static void decodeOptionSet(KeyedDecoder& decoder, const String& label, OptionSet<T>& optionSet)
155{
156 uint64_t optionSetBitMask = 0;
157 decoder.decodeUInt64(label, optionSetBitMask);
158 optionSet = OptionSet<T>::fromRaw(optionSetBitMask);
159}
160
161#if ENABLE(WEB_API_STATISTICS)
162static void decodeFontHashSet(KeyedDecoder& decoder, const String& label, HashSet<String>& hashSet)
163{
164 decodeHashSet(decoder, label, "font", hashSet);
165}
166
167static void decodeCanvasActivityRecord(KeyedDecoder& decoder, const String& label, CanvasActivityRecord& canvasActivityRecord)
168{
169 decoder.decodeObject(label, canvasActivityRecord, [] (KeyedDecoder& decoderInner, CanvasActivityRecord& canvasActivityRecord) {
170 if (!decoderInner.decodeBool("wasDataRead", canvasActivityRecord.wasDataRead))
171 return false;
172 Vector<String> ignore;
173 decoderInner.decodeObjects("textWritten", ignore, [&canvasActivityRecord] (KeyedDecoder& decoderInner2, String& text) {
174 if (!decoderInner2.decodeString("text", text))
175 return false;
176 canvasActivityRecord.textWritten.add(text);
177 return true;
178 });
179 return true;
180 });
181}
182#endif
183
184bool ResourceLoadStatistics::decode(KeyedDecoder& decoder, unsigned modelVersion)
185{
186 String registrableDomainAsString;
187 if (modelVersion >= 15) {
188 if (!decoder.decodeString("PrevalentResourceDomain", registrableDomainAsString))
189 return false;
190 } else {
191 if (!decoder.decodeString("PrevalentResourceOrigin", registrableDomainAsString))
192 return false;
193 }
194 registrableDomain = RegistrableDomain::uncheckedCreateFromRegistrableDomainString(registrableDomainAsString);
195
196 // User interaction
197 if (!decoder.decodeBool("hadUserInteraction", hadUserInteraction))
198 return false;
199
200 // Storage access
201 if (modelVersion >= 15)
202 decodeHashSet(decoder, "storageAccessUnderTopFrameDomains", "domain", storageAccessUnderTopFrameDomains);
203 else
204 decodeHashSet(decoder, "storageAccessUnderTopFrameOrigins", "origin", storageAccessUnderTopFrameDomains);
205
206 // Top frame stats
207 if (modelVersion >= 15) {
208 decodeHashSet(decoder, "topFrameUniqueRedirectsTo", "domain", topFrameUniqueRedirectsTo);
209 decodeHashSet(decoder, "topFrameUniqueRedirectsFrom", "domain", topFrameUniqueRedirectsFrom);
210 } else if (modelVersion >= 11) {
211 HashCountedSet<RegistrableDomain> topFrameUniqueRedirectsToCounted;
212 decodeHashCountedSet(decoder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsToCounted);
213 for (auto& domain : topFrameUniqueRedirectsToCounted.values())
214 topFrameUniqueRedirectsTo.add(domain);
215
216 HashCountedSet<RegistrableDomain> topFrameUniqueRedirectsFromCounted;
217 decodeHashCountedSet(decoder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFromCounted);
218 for (auto& domain : topFrameUniqueRedirectsFromCounted.values())
219 topFrameUniqueRedirectsFrom.add(domain);
220 }
221
222 if (modelVersion >= 16) {
223 decodeHashSet(decoder, "topFrameLinkDecorationsFrom", "domain", topFrameLinkDecorationsFrom);
224 if (!decoder.decodeBool("gotLinkDecorationFromPrevalentResource", gotLinkDecorationFromPrevalentResource))
225 return false;
226 }
227
228 // Subframe stats
229 if (modelVersion >= 15)
230 decodeHashSet(decoder, "subframeUnderTopFrameDomains", "domain", subframeUnderTopFrameDomains);
231 else if (modelVersion >= 14) {
232 HashCountedSet<RegistrableDomain> subframeUnderTopFrameDomainsCounted;
233 decodeHashCountedSet(decoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameDomainsCounted);
234 for (auto& domain : subframeUnderTopFrameDomainsCounted.values())
235 subframeUnderTopFrameDomains.add(domain);
236 }
237
238 // Subresource stats
239 if (modelVersion >= 15) {
240 decodeHashSet(decoder, "subresourceUnderTopFrameDomains", "domain", subresourceUnderTopFrameDomains);
241 decodeHashSet(decoder, "subresourceUniqueRedirectsTo", "domain", subresourceUniqueRedirectsTo);
242 decodeHashSet(decoder, "subresourceUniqueRedirectsFrom", "domain", subresourceUniqueRedirectsFrom);
243 } else {
244 HashCountedSet<RegistrableDomain> subresourceUnderTopFrameDomainsCounted;
245 decodeHashCountedSet(decoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameDomainsCounted);
246 for (auto& domain : subresourceUnderTopFrameDomainsCounted.values())
247 subresourceUnderTopFrameDomains.add(domain);
248
249 HashCountedSet<RegistrableDomain> subresourceUniqueRedirectsToCounted;
250 decodeHashCountedSet(decoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsToCounted);
251 for (auto& domain : subresourceUniqueRedirectsToCounted.values())
252 subresourceUniqueRedirectsTo.add(domain);
253 if (modelVersion >= 11) {
254 HashCountedSet<RegistrableDomain> subresourceUniqueRedirectsFromCounted;
255 decodeHashCountedSet(decoder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFromCounted);
256 for (auto& domain : subresourceUniqueRedirectsFromCounted.values())
257 subresourceUniqueRedirectsFrom.add(domain);
258 }
259 }
260
261
262 // Prevalent Resource
263 if (!decoder.decodeBool("isPrevalentResource", isPrevalentResource))
264 return false;
265
266 if (modelVersion >= 12) {
267 if (!decoder.decodeBool("isVeryPrevalentResource", isVeryPrevalentResource))
268 return false;
269 }
270
271 // Trigger re-classification based on model 14.
272 if (modelVersion < 14) {
273 isPrevalentResource = false;
274 isVeryPrevalentResource = false;
275 }
276
277 if (!decoder.decodeUInt32("dataRecordsRemoved", dataRecordsRemoved))
278 return false;
279
280 double mostRecentUserInteractionTimeAsDouble;
281 if (!decoder.decodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTimeAsDouble))
282 return false;
283 mostRecentUserInteractionTime = WallTime::fromRawSeconds(mostRecentUserInteractionTimeAsDouble);
284
285 if (!decoder.decodeBool("grandfathered", grandfathered))
286 return false;
287
288 double lastSeenTimeAsDouble;
289 if (!decoder.decodeDouble("lastSeen", lastSeenTimeAsDouble))
290 return false;
291 lastSeen = WallTime::fromRawSeconds(lastSeenTimeAsDouble);
292
293 if (modelVersion >= 11) {
294 if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction", timesAccessedAsFirstPartyDueToUserInteraction))
295 timesAccessedAsFirstPartyDueToUserInteraction = 0;
296 if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI", timesAccessedAsFirstPartyDueToStorageAccessAPI))
297 timesAccessedAsFirstPartyDueToStorageAccessAPI = 0;
298 }
299
300#if ENABLE(WEB_API_STATISTICS)
301 if (modelVersion >= 13) {
302 decodeFontHashSet(decoder, "fontsFailedToLoad", fontsFailedToLoad);
303 decodeFontHashSet(decoder, "fontsSuccessfullyLoaded", fontsSuccessfullyLoaded);
304 decodeHashCountedSet(decoder, "topFrameRegistrableDomainsWhichAccessedWebAPIs", topFrameRegistrableDomainsWhichAccessedWebAPIs);
305 decodeCanvasActivityRecord(decoder, "canvasActivityRecord", canvasActivityRecord);
306 decodeOptionSet(decoder, "navigatorFunctionsAccessedBitMask", navigatorFunctionsAccessed);
307 decodeOptionSet(decoder, "screenFunctionsAccessedBitMask", screenFunctionsAccessed);
308 }
309#endif
310
311 return true;
312}
313
314static void appendBoolean(StringBuilder& builder, const String& label, bool flag)
315{
316 builder.appendLiteral(" ");
317 builder.append(label);
318 builder.appendLiteral(": ");
319 builder.append(flag ? "Yes" : "No");
320}
321
322static void appendHashSet(StringBuilder& builder, const String& label, const HashSet<RegistrableDomain>& hashSet)
323{
324 if (hashSet.isEmpty())
325 return;
326
327 builder.appendLiteral(" ");
328 builder.append(label);
329 builder.appendLiteral(":\n");
330
331 for (auto& entry : hashSet) {
332 builder.appendLiteral(" ");
333 builder.append(entry.string());
334 builder.append('\n');
335 }
336}
337
338#if ENABLE(WEB_API_STATISTICS)
339static ASCIILiteral navigatorAPIEnumToString(ResourceLoadStatistics::NavigatorAPI navigatorEnum)
340{
341 switch (navigatorEnum) {
342 case ResourceLoadStatistics::NavigatorAPI::JavaEnabled:
343 return "javaEnabled"_s;
344 case ResourceLoadStatistics::NavigatorAPI::MimeTypes:
345 return "mimeTypes"_s;
346 case ResourceLoadStatistics::NavigatorAPI::CookieEnabled:
347 return "cookieEnabled"_s;
348 case ResourceLoadStatistics::NavigatorAPI::Plugins:
349 return "plugins"_s;
350 case ResourceLoadStatistics::NavigatorAPI::UserAgent:
351 return "userAgent"_s;
352 case ResourceLoadStatistics::NavigatorAPI::AppVersion:
353 return "appVersion"_s;
354 }
355 ASSERT_NOT_REACHED();
356 return "Invalid navigator API"_s;
357}
358
359static ASCIILiteral screenAPIEnumToString(ResourceLoadStatistics::ScreenAPI screenEnum)
360{
361 switch (screenEnum) {
362 case ResourceLoadStatistics::ScreenAPI::Height:
363 return "height"_s;
364 case ResourceLoadStatistics::ScreenAPI::Width:
365 return "width"_s;
366 case ResourceLoadStatistics::ScreenAPI::ColorDepth:
367 return "colorDepth"_s;
368 case ResourceLoadStatistics::ScreenAPI::PixelDepth:
369 return "pixelDepth"_s;
370 case ResourceLoadStatistics::ScreenAPI::AvailLeft:
371 return "availLeft"_s;
372 case ResourceLoadStatistics::ScreenAPI::AvailTop:
373 return "availTop"_s;
374 case ResourceLoadStatistics::ScreenAPI::AvailHeight:
375 return "availHeight"_s;
376 case ResourceLoadStatistics::ScreenAPI::AvailWidth:
377 return "availWidth"_s;
378 }
379 ASSERT_NOT_REACHED();
380 return "Invalid screen API"_s;
381}
382
383static void appendNavigatorAPIOptionSet(StringBuilder& builder, const OptionSet<ResourceLoadStatistics::NavigatorAPI>& optionSet)
384{
385 if (optionSet.isEmpty())
386 return;
387 builder.appendLiteral(" navigatorFunctionsAccessed:\n");
388 for (auto navigatorAPI : optionSet) {
389 builder.appendLiteral(" ");
390 builder.append(navigatorAPIEnumToString(navigatorAPI).characters());
391 builder.append('\n');
392 }
393}
394
395static void appendScreenAPIOptionSet(StringBuilder& builder, const OptionSet<ResourceLoadStatistics::ScreenAPI>& optionSet)
396{
397 if (optionSet.isEmpty())
398 return;
399 builder.appendLiteral(" screenFunctionsAccessed:\n");
400 for (auto screenAPI : optionSet) {
401 builder.appendLiteral(" ");
402 builder.append(screenAPIEnumToString(screenAPI).characters());
403 builder.append('\n');
404 }
405}
406#endif
407
408String ResourceLoadStatistics::toString() const
409{
410 StringBuilder builder;
411 builder.appendLiteral("Registrable domain: ");
412 builder.append(registrableDomain.string());
413 builder.append('\n');
414 builder.appendLiteral(" lastSeen: ");
415 builder.appendFixedPrecisionNumber(lastSeen.secondsSinceEpoch().value());
416 builder.append('\n');
417
418 // User interaction
419 appendBoolean(builder, "hadUserInteraction", hadUserInteraction);
420 builder.append('\n');
421 builder.appendLiteral(" mostRecentUserInteraction: ");
422 builder.appendFixedPrecisionNumber(mostRecentUserInteractionTime.secondsSinceEpoch().value());
423 builder.append('\n');
424 appendBoolean(builder, "grandfathered", grandfathered);
425 builder.append('\n');
426
427 // Storage access
428 appendHashSet(builder, "storageAccessUnderTopFrameDomains", storageAccessUnderTopFrameDomains);
429
430 // Top frame stats
431 appendHashSet(builder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
432 appendHashSet(builder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
433 appendHashSet(builder, "topFrameLinkDecorationsFrom", topFrameLinkDecorationsFrom);
434 appendBoolean(builder, "gotLinkDecorationFromPrevalentResource", gotLinkDecorationFromPrevalentResource);
435
436 // Subframe stats
437 appendHashSet(builder, "subframeUnderTopFrameDomains", subframeUnderTopFrameDomains);
438
439 // Subresource stats
440 appendHashSet(builder, "subresourceUnderTopFrameDomains", subresourceUnderTopFrameDomains);
441 appendHashSet(builder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
442 appendHashSet(builder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
443
444 // Prevalent Resource
445 appendBoolean(builder, "isPrevalentResource", isPrevalentResource);
446 builder.append('\n');
447 appendBoolean(builder, "isVeryPrevalentResource", isVeryPrevalentResource);
448 builder.append('\n');
449 builder.appendLiteral(" dataRecordsRemoved: ");
450 builder.appendNumber(dataRecordsRemoved);
451 builder.append('\n');
452
453#if ENABLE(WEB_API_STATISTICS)
454 appendHashSet(builder, "fontsFailedToLoad", fontsFailedToLoad);
455 appendHashSet(builder, "fontsSuccessfullyLoaded", fontsSuccessfullyLoaded);
456 appendHashCountedSet(builder, "topFrameRegistrableDomainsWhichAccessedWebAPIs", topFrameRegistrableDomainsWhichAccessedWebAPIs);
457 appendNavigatorAPIOptionSet(builder, navigatorFunctionsAccessed);
458 appendScreenAPIOptionSet(builder, screenFunctionsAccessed);
459 appendHashSet(builder, "canvasTextWritten", canvasActivityRecord.textWritten);
460 appendBoolean(builder, "canvasReadData", canvasActivityRecord.wasDataRead);
461 builder.append('\n');
462 builder.append('\n');
463#endif
464
465 return builder.toString();
466}
467
468template <typename T>
469static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
470{
471 for (auto& entry : from)
472 to.add(entry.key, entry.value);
473}
474
475template <typename T>
476static void mergeHashSet(HashSet<T>& to, const HashSet<T>& from)
477{
478 for (auto& entry : from)
479 to.add(entry);
480}
481
482void ResourceLoadStatistics::merge(const ResourceLoadStatistics& other)
483{
484 ASSERT(other.registrableDomain == registrableDomain);
485
486 if (lastSeen < other.lastSeen)
487 lastSeen = other.lastSeen;
488
489 if (!other.hadUserInteraction) {
490 // If user interaction has been reset do so here too.
491 // Else, do nothing.
492 if (!other.mostRecentUserInteractionTime) {
493 hadUserInteraction = false;
494 mostRecentUserInteractionTime = { };
495 }
496 } else {
497 hadUserInteraction = true;
498 if (mostRecentUserInteractionTime < other.mostRecentUserInteractionTime)
499 mostRecentUserInteractionTime = other.mostRecentUserInteractionTime;
500 }
501 grandfathered |= other.grandfathered;
502
503 // Storage access
504 mergeHashSet(storageAccessUnderTopFrameDomains, other.storageAccessUnderTopFrameDomains);
505
506 // Top frame stats
507 mergeHashSet(topFrameUniqueRedirectsTo, other.topFrameUniqueRedirectsTo);
508 mergeHashSet(topFrameUniqueRedirectsFrom, other.topFrameUniqueRedirectsFrom);
509 mergeHashSet(topFrameLinkDecorationsFrom, other.topFrameLinkDecorationsFrom);
510 gotLinkDecorationFromPrevalentResource |= other.gotLinkDecorationFromPrevalentResource;
511
512 // Subframe stats
513 mergeHashSet(subframeUnderTopFrameDomains, other.subframeUnderTopFrameDomains);
514
515 // Subresource stats
516 mergeHashSet(subresourceUnderTopFrameDomains, other.subresourceUnderTopFrameDomains);
517 mergeHashSet(subresourceUniqueRedirectsTo, other.subresourceUniqueRedirectsTo);
518 mergeHashSet(subresourceUniqueRedirectsFrom, other.subresourceUniqueRedirectsFrom);
519
520 // Prevalent resource stats
521 isPrevalentResource |= other.isPrevalentResource;
522 isVeryPrevalentResource |= other.isVeryPrevalentResource;
523 dataRecordsRemoved = std::max(dataRecordsRemoved, other.dataRecordsRemoved);
524
525#if ENABLE(WEB_API_STATISTICS)
526 mergeHashSet(fontsFailedToLoad, other.fontsFailedToLoad);
527 mergeHashSet(fontsSuccessfullyLoaded, other.fontsSuccessfullyLoaded);
528 mergeHashSet(topFrameRegistrableDomainsWhichAccessedWebAPIs, other.topFrameRegistrableDomainsWhichAccessedWebAPIs);
529 canvasActivityRecord.mergeWith(other.canvasActivityRecord);
530 navigatorFunctionsAccessed.add(other.navigatorFunctionsAccessed);
531 screenFunctionsAccessed.add(other.screenFunctionsAccessed);
532#endif
533}
534
535WallTime ResourceLoadStatistics::reduceTimeResolution(WallTime time)
536{
537 return WallTime::fromRawSeconds(std::floor(time.secondsSinceEpoch() / timestampResolution) * timestampResolution.seconds());
538}
539
540}
541