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