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