Add a second tier of prevalence to facilitate telemetry on very prevalent domains
[WebKit-https.git] / Source / WebCore / loader / ResourceLoadStatistics.cpp
1 /*
2  * Copyright (C) 2016-2017 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/text/StringBuilder.h>
32 #include <wtf/text/StringHash.h>
33
34 namespace WebCore {
35
36 typedef WTF::HashMap<String, unsigned, StringHash, HashTraits<String>, HashTraits<unsigned>>::KeyValuePairType ResourceLoadStatisticsValue;
37
38 static void encodeHashCountedSet(KeyedEncoder& encoder, const String& label, const HashCountedSet<String>& hashCountedSet)
39 {
40     if (hashCountedSet.isEmpty())
41         return;
42
43     encoder.encodeObjects(label, hashCountedSet.begin(), hashCountedSet.end(), [](KeyedEncoder& encoderInner, const ResourceLoadStatisticsValue& origin) {
44         encoderInner.encodeString("origin", origin.key);
45         encoderInner.encodeUInt32("count", origin.value);
46     });
47 }
48
49 static void encodeHashSet(KeyedEncoder& encoder, const String& label, const HashSet<String>& hashSet)
50 {
51     if (hashSet.isEmpty())
52         return;
53     
54     encoder.encodeObjects(label, hashSet.begin(), hashSet.end(), [](KeyedEncoder& encoderInner, const String& origin) {
55         encoderInner.encodeString("origin", origin);
56     });
57 }
58
59 void ResourceLoadStatistics::encode(KeyedEncoder& encoder) const
60 {
61     encoder.encodeString("PrevalentResourceOrigin", highLevelDomain);
62     
63     encoder.encodeDouble("lastSeen", lastSeen.secondsSinceEpoch().value());
64     
65     // User interaction
66     encoder.encodeBool("hadUserInteraction", hadUserInteraction);
67     encoder.encodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTime.secondsSinceEpoch().value());
68     encoder.encodeBool("grandfathered", grandfathered);
69
70     // Storage access
71     encodeHashSet(encoder, "storageAccessUnderTopFrameOrigins", storageAccessUnderTopFrameOrigins);
72
73     // Top frame stats
74     encodeHashCountedSet(encoder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
75     encodeHashCountedSet(encoder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
76
77     // Subframe stats
78     encodeHashCountedSet(encoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
79     
80     // Subresource stats
81     encodeHashCountedSet(encoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
82     encodeHashCountedSet(encoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
83     encodeHashCountedSet(encoder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
84
85     // Prevalent Resource
86     encoder.encodeBool("isPrevalentResource", isPrevalentResource);
87     encoder.encodeBool("isVeryPrevalentResource", isVeryPrevalentResource);
88     encoder.encodeUInt32("dataRecordsRemoved", dataRecordsRemoved);
89
90     encoder.encodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction", timesAccessedAsFirstPartyDueToUserInteraction);
91     encoder.encodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI", timesAccessedAsFirstPartyDueToStorageAccessAPI);
92 }
93
94 static void decodeHashCountedSet(KeyedDecoder& decoder, const String& label, HashCountedSet<String>& hashCountedSet)
95 {
96     Vector<String> ignore;
97     decoder.decodeObjects(label, ignore, [&hashCountedSet](KeyedDecoder& decoderInner, String& origin) {
98         if (!decoderInner.decodeString("origin", origin))
99             return false;
100         
101         unsigned count;
102         if (!decoderInner.decodeUInt32("count", count))
103             return false;
104
105         hashCountedSet.add(origin, count);
106         return true;
107     });
108 }
109
110 static void decodeHashSet(KeyedDecoder& decoder, const String& label, HashSet<String>& hashSet)
111 {
112     Vector<String> ignore;
113     decoder.decodeObjects(label, ignore, [&hashSet](KeyedDecoder& decoderInner, String& origin) {
114         if (!decoderInner.decodeString("origin", origin))
115             return false;
116         
117         hashSet.add(origin);
118         return true;
119     });
120 }
121
122 bool ResourceLoadStatistics::decode(KeyedDecoder& decoder, unsigned modelVersion)
123 {
124     if (!decoder.decodeString("PrevalentResourceOrigin", highLevelDomain))
125         return false;
126     
127     // User interaction
128     if (!decoder.decodeBool("hadUserInteraction", hadUserInteraction))
129         return false;
130
131     // Storage access
132     decodeHashSet(decoder, "storageAccessUnderTopFrameOrigins", storageAccessUnderTopFrameOrigins);
133
134     // Top frame stats
135     if (modelVersion >= 11) {
136         decodeHashCountedSet(decoder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
137         decodeHashCountedSet(decoder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
138     }
139
140     // Subframe stats
141     decodeHashCountedSet(decoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
142     
143     // Subresource stats
144     decodeHashCountedSet(decoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
145     decodeHashCountedSet(decoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
146     if (modelVersion >= 11)
147         decodeHashCountedSet(decoder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
148
149     // Prevalent Resource
150     if (!decoder.decodeBool("isPrevalentResource", isPrevalentResource))
151         return false;
152
153     if (modelVersion >= 12) {
154         if (!decoder.decodeBool("isVeryPrevalentResource", isPrevalentResource))
155             return false;
156     }
157
158     if (!decoder.decodeUInt32("dataRecordsRemoved", dataRecordsRemoved))
159         return false;
160
161     double mostRecentUserInteractionTimeAsDouble;
162     if (!decoder.decodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTimeAsDouble))
163         return false;
164     mostRecentUserInteractionTime = WallTime::fromRawSeconds(mostRecentUserInteractionTimeAsDouble);
165
166     if (!decoder.decodeBool("grandfathered", grandfathered))
167         return false;
168
169     double lastSeenTimeAsDouble;
170     if (!decoder.decodeDouble("lastSeen", lastSeenTimeAsDouble))
171         return false;
172     lastSeen = WallTime::fromRawSeconds(lastSeenTimeAsDouble);
173
174     if (modelVersion >= 11) {
175         if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction", timesAccessedAsFirstPartyDueToUserInteraction))
176             timesAccessedAsFirstPartyDueToUserInteraction = 0;
177         if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI", timesAccessedAsFirstPartyDueToStorageAccessAPI))
178             timesAccessedAsFirstPartyDueToStorageAccessAPI = 0;
179     }
180     return true;
181 }
182
183 static void appendBoolean(StringBuilder& builder, const String& label, bool flag)
184 {
185     builder.appendLiteral("    ");
186     builder.append(label);
187     builder.appendLiteral(": ");
188     builder.append(flag ? "Yes" : "No");
189 }
190
191 static void appendHashCountedSet(StringBuilder& builder, const String& label, const HashCountedSet<String>& hashCountedSet)
192 {
193     if (hashCountedSet.isEmpty())
194         return;
195
196     builder.appendLiteral("    ");
197     builder.append(label);
198     builder.appendLiteral(":\n");
199
200     for (auto& entry : hashCountedSet) {
201         builder.appendLiteral("        ");
202         builder.append(entry.key);
203         builder.appendLiteral(": ");
204         builder.appendNumber(entry.value);
205         builder.append('\n');
206     }
207 }
208
209 static void appendHashSet(StringBuilder& builder, const String& label, const HashSet<String>& hashSet)
210 {
211     if (hashSet.isEmpty())
212         return;
213     
214     builder.appendLiteral("    ");
215     builder.append(label);
216     builder.appendLiteral(":\n");
217     
218     for (auto& entry : hashSet) {
219         builder.appendLiteral("        ");
220         builder.append(entry);
221         builder.append('\n');
222     }
223 }
224
225 String ResourceLoadStatistics::toString() const
226 {
227     StringBuilder builder;
228     
229     builder.appendLiteral("lastSeen");
230     builder.appendNumber(lastSeen.secondsSinceEpoch().value());
231     builder.append('\n');
232     
233     // User interaction
234     appendBoolean(builder, "hadUserInteraction", hadUserInteraction);
235     builder.append('\n');
236     builder.appendLiteral("    mostRecentUserInteraction: ");
237     builder.appendNumber(mostRecentUserInteractionTime.secondsSinceEpoch().value());
238     builder.append('\n');
239     appendBoolean(builder, "    grandfathered", grandfathered);
240     builder.append('\n');
241
242     // Storage access
243     appendHashSet(builder, "storageAccessUnderTopFrameOrigins", storageAccessUnderTopFrameOrigins);
244
245     // Top frame stats
246     appendHashCountedSet(builder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
247     appendHashCountedSet(builder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
248
249     // Subframe stats
250     appendHashCountedSet(builder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
251     
252     // Subresource stats
253     appendHashCountedSet(builder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
254     appendHashCountedSet(builder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
255     appendHashCountedSet(builder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
256
257     // Prevalent Resource
258     appendBoolean(builder, "isPrevalentResource", isPrevalentResource);
259     appendBoolean(builder, "    isVeryPrevalentResource", isVeryPrevalentResource);
260     builder.appendLiteral("    dataRecordsRemoved: ");
261     builder.appendNumber(dataRecordsRemoved);
262     builder.append('\n');
263
264     // In-memory only
265     appendBoolean(builder, "isMarkedForCookiePartitioning", isMarkedForCookiePartitioning);
266     appendBoolean(builder, "isMarkedForCookieBlocking", isMarkedForCookieBlocking);
267     builder.append('\n');
268
269     builder.append('\n');
270
271     return builder.toString();
272 }
273
274 template <typename T>
275 static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
276 {
277     for (auto& entry : from)
278         to.add(entry.key, entry.value);
279 }
280
281 template <typename T>
282 static void mergeHashSet(HashSet<T>& to, const HashSet<T>& from)
283 {
284     for (auto& entry : from)
285         to.add(entry);
286 }
287
288 void ResourceLoadStatistics::merge(const ResourceLoadStatistics& other)
289 {
290     ASSERT(other.highLevelDomain == highLevelDomain);
291
292     if (lastSeen < other.lastSeen)
293         lastSeen = other.lastSeen;
294     
295     if (!other.hadUserInteraction) {
296         // If user interaction has been reset do so here too.
297         // Else, do nothing.
298         if (!other.mostRecentUserInteractionTime) {
299             hadUserInteraction = false;
300             mostRecentUserInteractionTime = { };
301         }
302     } else {
303         hadUserInteraction = true;
304         if (mostRecentUserInteractionTime < other.mostRecentUserInteractionTime)
305             mostRecentUserInteractionTime = other.mostRecentUserInteractionTime;
306     }
307     grandfathered |= other.grandfathered;
308
309     // Storage access
310     mergeHashSet(storageAccessUnderTopFrameOrigins, other.storageAccessUnderTopFrameOrigins);
311
312     // Top frame stats
313     mergeHashCountedSet(topFrameUniqueRedirectsTo, other.topFrameUniqueRedirectsTo);
314     mergeHashCountedSet(topFrameUniqueRedirectsFrom, other.topFrameUniqueRedirectsFrom);
315
316     // Subframe stats
317     mergeHashCountedSet(subframeUnderTopFrameOrigins, other.subframeUnderTopFrameOrigins);
318     
319     // Subresource stats
320     mergeHashCountedSet(subresourceUnderTopFrameOrigins, other.subresourceUnderTopFrameOrigins);
321     mergeHashCountedSet(subresourceUniqueRedirectsTo, other.subresourceUniqueRedirectsTo);
322     mergeHashCountedSet(subresourceUniqueRedirectsFrom, other.subresourceUniqueRedirectsFrom);
323
324     // Prevalent resource stats
325     isPrevalentResource |= other.isPrevalentResource;
326     isVeryPrevalentResource |= other.isVeryPrevalentResource;
327     dataRecordsRemoved = std::max(dataRecordsRemoved, other.dataRecordsRemoved);
328     
329     // In-memory only
330     isMarkedForCookiePartitioning |= other.isMarkedForCookiePartitioning;
331     isMarkedForCookieBlocking |= other.isMarkedForCookieBlocking;
332 }
333
334 String ResourceLoadStatistics::primaryDomain(const URL& url)
335 {
336     return primaryDomain(url.host());
337 }
338
339 String ResourceLoadStatistics::primaryDomain(const String& host)
340 {
341     if (host.isNull() || host.isEmpty())
342         return ASCIILiteral("nullOrigin");
343
344 #if ENABLE(PUBLIC_SUFFIX_LIST)
345     String primaryDomain = topPrivatelyControlledDomain(host);
346     // We will have an empty string here if there is no TLD. Use the host as a fallback.
347     if (!primaryDomain.isEmpty())
348         return primaryDomain;
349 #endif
350
351     return host;
352 }
353
354 }