Resource Load Statistics: Merge of dataRecordsRemoved should use std::max()
[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 void ResourceLoadStatistics::encode(KeyedEncoder& encoder) const
50 {
51     encoder.encodeString("PrevalentResourceOrigin", highLevelDomain);
52     
53     encoder.encodeDouble("lastSeen", lastSeen.secondsSinceEpoch().value());
54     
55     // User interaction
56     encoder.encodeBool("hadUserInteraction", hadUserInteraction);
57     encoder.encodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTime.secondsSinceEpoch().value());
58     encoder.encodeBool("grandfathered", grandfathered);
59     
60     // Subframe stats
61     encodeHashCountedSet(encoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
62     
63     // Subresource stats
64     encodeHashCountedSet(encoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
65     encodeHashCountedSet(encoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
66     
67     // Prevalent Resource
68     encoder.encodeBool("isPrevalentResource", isPrevalentResource);
69     encoder.encodeUInt32("dataRecordsRemoved", dataRecordsRemoved);
70 }
71
72 static void decodeHashCountedSet(KeyedDecoder& decoder, const String& label, HashCountedSet<String>& hashCountedSet)
73 {
74     Vector<String> ignore;
75     decoder.decodeObjects(label, ignore, [&hashCountedSet](KeyedDecoder& decoderInner, String& origin) {
76         if (!decoderInner.decodeString("origin", origin))
77             return false;
78         
79         unsigned count;
80         if (!decoderInner.decodeUInt32("count", count))
81             return false;
82
83         hashCountedSet.add(origin, count);
84         return true;
85     });
86 }
87
88 bool ResourceLoadStatistics::decode(KeyedDecoder& decoder)
89 {
90     if (!decoder.decodeString("PrevalentResourceOrigin", highLevelDomain))
91         return false;
92     
93     // User interaction
94     if (!decoder.decodeBool("hadUserInteraction", hadUserInteraction))
95         return false;
96     
97     // Subframe stats
98     decodeHashCountedSet(decoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
99     
100     // Subresource stats
101     decodeHashCountedSet(decoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
102     decodeHashCountedSet(decoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
103     
104     // Prevalent Resource
105     if (!decoder.decodeBool("isPrevalentResource", isPrevalentResource))
106         return false;
107
108     if (!decoder.decodeUInt32("dataRecordsRemoved", dataRecordsRemoved))
109         return false;
110
111     double mostRecentUserInteractionTimeAsDouble;
112     if (!decoder.decodeDouble("mostRecentUserInteraction", mostRecentUserInteractionTimeAsDouble))
113         return false;
114     mostRecentUserInteractionTime = WallTime::fromRawSeconds(mostRecentUserInteractionTimeAsDouble);
115
116     if (!decoder.decodeBool("grandfathered", grandfathered))
117         return false;
118
119     double lastSeenTimeAsDouble;
120     if (!decoder.decodeDouble("lastSeen", lastSeenTimeAsDouble))
121         return false;
122     lastSeen = WallTime::fromRawSeconds(lastSeenTimeAsDouble);
123     
124     return true;
125 }
126
127 static void appendBoolean(StringBuilder& builder, const String& label, bool flag)
128 {
129     builder.appendLiteral("    ");
130     builder.append(label);
131     builder.appendLiteral(": ");
132     builder.append(flag ? "Yes" : "No");
133 }
134
135 static void appendHashCountedSet(StringBuilder& builder, const String& label, const HashCountedSet<String>& hashCountedSet)
136 {
137     if (hashCountedSet.isEmpty())
138         return;
139
140     builder.appendLiteral("    ");
141     builder.append(label);
142     builder.appendLiteral(":\n");
143
144     for (auto& entry : hashCountedSet) {
145         builder.appendLiteral("        ");
146         builder.append(entry.key);
147         builder.appendLiteral(": ");
148         builder.appendNumber(entry.value);
149         builder.append('\n');
150     }
151 }
152
153 String ResourceLoadStatistics::toString() const
154 {
155     StringBuilder builder;
156     
157     builder.appendLiteral("lastSeen");
158     builder.appendNumber(lastSeen.secondsSinceEpoch().value());
159     builder.append('\n');
160     
161     // User interaction
162     appendBoolean(builder, "hadUserInteraction", hadUserInteraction);
163     builder.append('\n');
164     builder.appendLiteral("    mostRecentUserInteraction: ");
165     builder.appendNumber(mostRecentUserInteractionTime.secondsSinceEpoch().value());
166     builder.append('\n');
167     appendBoolean(builder, "    grandfathered", grandfathered);
168     builder.append('\n');
169     
170     // Subframe stats
171     appendHashCountedSet(builder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
172     
173     // Subresource stats
174     appendHashCountedSet(builder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
175     appendHashCountedSet(builder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
176     
177     // Prevalent Resource
178     appendBoolean(builder, "isPrevalentResource", isPrevalentResource);
179     builder.appendLiteral("    dataRecordsRemoved: ");
180     builder.appendNumber(dataRecordsRemoved);
181     builder.append('\n');
182
183     // In-memory only
184     appendBoolean(builder, "isMarkedForCookiePartitioning", isMarkedForCookiePartitioning);
185     builder.append('\n');
186
187     builder.append('\n');
188
189     return builder.toString();
190 }
191
192 template <typename T>
193 static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
194 {
195     for (auto& entry : from)
196         to.add(entry.key, entry.value);
197 }
198
199 void ResourceLoadStatistics::merge(const ResourceLoadStatistics& other)
200 {
201     ASSERT(other.highLevelDomain == highLevelDomain);
202
203     if (lastSeen < other.lastSeen)
204         lastSeen = other.lastSeen;
205     
206     if (!other.hadUserInteraction) {
207         // If user interaction has been reset do so here too.
208         // Else, do nothing.
209         if (!other.mostRecentUserInteractionTime) {
210             hadUserInteraction = false;
211             mostRecentUserInteractionTime = { };
212         }
213     } else {
214         hadUserInteraction = true;
215         if (mostRecentUserInteractionTime < other.mostRecentUserInteractionTime)
216             mostRecentUserInteractionTime = other.mostRecentUserInteractionTime;
217     }
218     grandfathered |= other.grandfathered;
219     
220     // Subframe stats
221     mergeHashCountedSet(subframeUnderTopFrameOrigins, other.subframeUnderTopFrameOrigins);
222     
223     // Subresource stats
224     mergeHashCountedSet(subresourceUnderTopFrameOrigins, other.subresourceUnderTopFrameOrigins);
225     mergeHashCountedSet(subresourceUniqueRedirectsTo, other.subresourceUniqueRedirectsTo);
226     
227     // Prevalent resource stats
228     isPrevalentResource |= other.isPrevalentResource;
229     dataRecordsRemoved = std::max(dataRecordsRemoved, other.dataRecordsRemoved);
230     
231     // In-memory only
232     isMarkedForCookiePartitioning |= other.isMarkedForCookiePartitioning;
233 }
234
235 String ResourceLoadStatistics::primaryDomain(const URL& url)
236 {
237     return primaryDomain(url.host());
238 }
239
240 String ResourceLoadStatistics::primaryDomain(const String& host)
241 {
242     if (host.isNull() || host.isEmpty())
243         return ASCIILiteral("nullOrigin");
244
245 #if ENABLE(PUBLIC_SUFFIX_LIST)
246     String primaryDomain = topPrivatelyControlledDomain(host);
247     // We will have an empty string here if there is no TLD. Use the host as a fallback.
248     if (!primaryDomain.isEmpty())
249         return primaryDomain;
250 #endif
251
252     return host;
253 }
254
255 }