Introduce and add plumbing for a website policy for meta viewport tag handling
[WebKit-https.git] / Source / WebCore / loader / ResourceLoadStatistics.cpp
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
36 namespace WebCore {
37
38 static Seconds timestampResolution { 5_s };
39
40 typedef WTF::HashMap<RegistrableDomain, unsigned, RegistrableDomain::RegistrableDomainHash, HashTraits<RegistrableDomain>, HashTraits<unsigned>>::KeyValuePairType ResourceLoadStatisticsValue;
41
42 static 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
52 template<typename T>
53 static 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)
63 static void encodeFontHashSet(KeyedEncoder& encoder, const String& label, const HashSet<String>& hashSet)
64 {
65     encodeHashSet(encoder, label, "font", hashSet);
66 }
67     
68 static 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
79 void 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
125 static 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
141 static 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
153 template<typename T>
154 static 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)
162 static void decodeFontHashSet(KeyedDecoder& decoder, const String& label, HashSet<String>& hashSet)
163 {
164     decodeHashSet(decoder, label, "font", hashSet);
165 }
166     
167 static 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
184 bool 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
314 static 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
322 static 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)
339 static 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
359 static 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     
383 static 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     
395 static 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
408 String 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
468 template <typename T>
469 static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
470 {
471     for (auto& entry : from)
472         to.add(entry.key, entry.value);
473 }
474
475 template <typename T>
476 static void mergeHashSet(HashSet<T>& to, const HashSet<T>& from)
477 {
478     for (auto& entry : from)
479         to.add(entry);
480 }
481
482 void 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
535 WallTime ResourceLoadStatistics::reduceTimeResolution(WallTime time)
536 {
537     return WallTime::fromRawSeconds(std::floor(time.secondsSinceEpoch() / timestampResolution) * timestampResolution.seconds());
538 }
539
540 }