1817d20cf17ac8c04b6be712fb448bc8995257af
[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.encodeUInt32("dataRecordsRemoved", dataRecordsRemoved);
88
89     encoder.encodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction", timesAccessedAsFirstPartyDueToUserInteraction);
90     encoder.encodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI", timesAccessedAsFirstPartyDueToStorageAccessAPI);
91 }
92
93 static void decodeHashCountedSet(KeyedDecoder& decoder, const String& label, HashCountedSet<String>& hashCountedSet)
94 {
95     Vector<String> ignore;
96     decoder.decodeObjects(label, ignore, [&hashCountedSet](KeyedDecoder& decoderInner, String& origin) {
97         if (!decoderInner.decodeString("origin", origin))
98             return false;
99         
100         unsigned count;
101         if (!decoderInner.decodeUInt32("count", count))
102             return false;
103
104         hashCountedSet.add(origin, count);
105         return true;
106     });
107 }
108
109 static void decodeHashSet(KeyedDecoder& decoder, const String& label, HashSet<String>& hashSet)
110 {
111     Vector<String> ignore;
112     decoder.decodeObjects(label, ignore, [&hashSet](KeyedDecoder& decoderInner, String& origin) {
113         if (!decoderInner.decodeString("origin", origin))
114             return false;
115         
116         hashSet.add(origin);
117         return true;
118     });
119 }
120
121 bool ResourceLoadStatistics::decode(KeyedDecoder& decoder)
122 {
123     if (!decoder.decodeString("PrevalentResourceOrigin", highLevelDomain))
124         return false;
125     
126     // User interaction
127     if (!decoder.decodeBool("hadUserInteraction", hadUserInteraction))
128         return false;
129
130     // Storage access
131     decodeHashSet(decoder, "storageAccessUnderTopFrameOrigins", storageAccessUnderTopFrameOrigins);
132
133     // Top frame stats
134     decodeHashCountedSet(decoder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
135     decodeHashCountedSet(decoder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
136
137     // Subframe stats
138     decodeHashCountedSet(decoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
139     
140     // Subresource stats
141     decodeHashCountedSet(decoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
142     decodeHashCountedSet(decoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
143     decodeHashCountedSet(decoder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
144
145     // Prevalent Resource
146     if (!decoder.decodeBool("isPrevalentResource", isPrevalentResource))
147         return false;
148
149     if (!decoder.decodeUInt32("dataRecordsRemoved", dataRecordsRemoved))
150         return false;
151
152     double mostRecentUserInteractionTimeAsDouble;
153     if (!decoder.decodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTimeAsDouble))
154         return false;
155     mostRecentUserInteractionTime = WallTime::fromRawSeconds(mostRecentUserInteractionTimeAsDouble);
156
157     if (!decoder.decodeBool("grandfathered", grandfathered))
158         return false;
159
160     double lastSeenTimeAsDouble;
161     if (!decoder.decodeDouble("lastSeen", lastSeenTimeAsDouble))
162         return false;
163     lastSeen = WallTime::fromRawSeconds(lastSeenTimeAsDouble);
164
165     if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToUserInteraction", timesAccessedAsFirstPartyDueToUserInteraction))
166         timesAccessedAsFirstPartyDueToUserInteraction = 0;
167     if (!decoder.decodeUInt32("timesAccessedAsFirstPartyDueToStorageAccessAPI", timesAccessedAsFirstPartyDueToStorageAccessAPI))
168         timesAccessedAsFirstPartyDueToStorageAccessAPI = 0;
169
170     return true;
171 }
172
173 static void appendBoolean(StringBuilder& builder, const String& label, bool flag)
174 {
175     builder.appendLiteral("    ");
176     builder.append(label);
177     builder.appendLiteral(": ");
178     builder.append(flag ? "Yes" : "No");
179 }
180
181 static void appendHashCountedSet(StringBuilder& builder, const String& label, const HashCountedSet<String>& hashCountedSet)
182 {
183     if (hashCountedSet.isEmpty())
184         return;
185
186     builder.appendLiteral("    ");
187     builder.append(label);
188     builder.appendLiteral(":\n");
189
190     for (auto& entry : hashCountedSet) {
191         builder.appendLiteral("        ");
192         builder.append(entry.key);
193         builder.appendLiteral(": ");
194         builder.appendNumber(entry.value);
195         builder.append('\n');
196     }
197 }
198
199 static void appendHashSet(StringBuilder& builder, const String& label, const HashSet<String>& hashSet)
200 {
201     if (hashSet.isEmpty())
202         return;
203     
204     builder.appendLiteral("    ");
205     builder.append(label);
206     builder.appendLiteral(":\n");
207     
208     for (auto& entry : hashSet) {
209         builder.appendLiteral("        ");
210         builder.append(entry);
211         builder.append('\n');
212     }
213 }
214
215 String ResourceLoadStatistics::toString() const
216 {
217     StringBuilder builder;
218     
219     builder.appendLiteral("lastSeen");
220     builder.appendNumber(lastSeen.secondsSinceEpoch().value());
221     builder.append('\n');
222     
223     // User interaction
224     appendBoolean(builder, "hadUserInteraction", hadUserInteraction);
225     builder.append('\n');
226     builder.appendLiteral("    mostRecentUserInteraction: ");
227     builder.appendNumber(mostRecentUserInteractionTime.secondsSinceEpoch().value());
228     builder.append('\n');
229     appendBoolean(builder, "    grandfathered", grandfathered);
230     builder.append('\n');
231
232     // Storage access
233     appendHashSet(builder, "storageAccessUnderTopFrameOrigins", storageAccessUnderTopFrameOrigins);
234
235     // Top frame stats
236     appendHashCountedSet(builder, "topFrameUniqueRedirectsTo", topFrameUniqueRedirectsTo);
237     appendHashCountedSet(builder, "topFrameUniqueRedirectsFrom", topFrameUniqueRedirectsFrom);
238
239     // Subframe stats
240     appendHashCountedSet(builder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
241     
242     // Subresource stats
243     appendHashCountedSet(builder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
244     appendHashCountedSet(builder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
245     appendHashCountedSet(builder, "subresourceUniqueRedirectsFrom", subresourceUniqueRedirectsFrom);
246
247     // Prevalent Resource
248     appendBoolean(builder, "isPrevalentResource", isPrevalentResource);
249     builder.appendLiteral("    dataRecordsRemoved: ");
250     builder.appendNumber(dataRecordsRemoved);
251     builder.append('\n');
252
253     // In-memory only
254     appendBoolean(builder, "isMarkedForCookiePartitioning", isMarkedForCookiePartitioning);
255     appendBoolean(builder, "isMarkedForCookieBlocking", isMarkedForCookieBlocking);
256     builder.append('\n');
257
258     builder.append('\n');
259
260     return builder.toString();
261 }
262
263 template <typename T>
264 static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
265 {
266     for (auto& entry : from)
267         to.add(entry.key, entry.value);
268 }
269
270 template <typename T>
271 static void mergeHashSet(HashSet<T>& to, const HashSet<T>& from)
272 {
273     for (auto& entry : from)
274         to.add(entry);
275 }
276
277 void ResourceLoadStatistics::merge(const ResourceLoadStatistics& other)
278 {
279     ASSERT(other.highLevelDomain == highLevelDomain);
280
281     if (lastSeen < other.lastSeen)
282         lastSeen = other.lastSeen;
283     
284     if (!other.hadUserInteraction) {
285         // If user interaction has been reset do so here too.
286         // Else, do nothing.
287         if (!other.mostRecentUserInteractionTime) {
288             hadUserInteraction = false;
289             mostRecentUserInteractionTime = { };
290         }
291     } else {
292         hadUserInteraction = true;
293         if (mostRecentUserInteractionTime < other.mostRecentUserInteractionTime)
294             mostRecentUserInteractionTime = other.mostRecentUserInteractionTime;
295     }
296     grandfathered |= other.grandfathered;
297
298     // Storage access
299     mergeHashSet(storageAccessUnderTopFrameOrigins, other.storageAccessUnderTopFrameOrigins);
300
301     // Top frame stats
302     mergeHashCountedSet(topFrameUniqueRedirectsTo, other.topFrameUniqueRedirectsTo);
303     mergeHashCountedSet(topFrameUniqueRedirectsFrom, other.topFrameUniqueRedirectsFrom);
304
305     // Subframe stats
306     mergeHashCountedSet(subframeUnderTopFrameOrigins, other.subframeUnderTopFrameOrigins);
307     
308     // Subresource stats
309     mergeHashCountedSet(subresourceUnderTopFrameOrigins, other.subresourceUnderTopFrameOrigins);
310     mergeHashCountedSet(subresourceUniqueRedirectsTo, other.subresourceUniqueRedirectsTo);
311     mergeHashCountedSet(subresourceUniqueRedirectsFrom, other.subresourceUniqueRedirectsFrom);
312
313     // Prevalent resource stats
314     isPrevalentResource |= other.isPrevalentResource;
315     dataRecordsRemoved = std::max(dataRecordsRemoved, other.dataRecordsRemoved);
316     
317     // In-memory only
318     isMarkedForCookiePartitioning |= other.isMarkedForCookiePartitioning;
319     isMarkedForCookieBlocking |= other.isMarkedForCookieBlocking;
320 }
321
322 String ResourceLoadStatistics::primaryDomain(const URL& url)
323 {
324     return primaryDomain(url.host());
325 }
326
327 String ResourceLoadStatistics::primaryDomain(const String& host)
328 {
329     if (host.isNull() || host.isEmpty())
330         return ASCIILiteral("nullOrigin");
331
332 #if ENABLE(PUBLIC_SUFFIX_LIST)
333     String primaryDomain = topPrivatelyControlledDomain(host);
334     // We will have an empty string here if there is no TLD. Use the host as a fallback.
335     if (!primaryDomain.isEmpty())
336         return primaryDomain;
337 #endif
338
339     return host;
340 }
341
342 }