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 | } |
541 | |