1 | /* |
2 | * Copyright (C) 2019 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. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "ResourceLoadStatisticsDatabaseStore.h" |
28 | |
29 | #if ENABLE(RESOURCE_LOAD_STATISTICS) |
30 | |
31 | #include "Logging.h" |
32 | #include "NetworkSession.h" |
33 | #include "PluginProcessManager.h" |
34 | #include "PluginProcessProxy.h" |
35 | #include "ResourceLoadStatisticsMemoryStore.h" |
36 | #include "StorageAccessStatus.h" |
37 | #include "WebProcessProxy.h" |
38 | #include "WebResourceLoadStatisticsTelemetry.h" |
39 | #include "WebsiteDataStore.h" |
40 | #include <WebCore/DocumentStorageAccess.h> |
41 | #include <WebCore/KeyedCoding.h> |
42 | #include <WebCore/NetworkStorageSession.h> |
43 | #include <WebCore/ResourceLoadStatistics.h> |
44 | #include <WebCore/SQLiteDatabase.h> |
45 | #include <WebCore/SQLiteStatement.h> |
46 | #include <WebCore/UserGestureIndicator.h> |
47 | #include <wtf/CallbackAggregator.h> |
48 | #include <wtf/DateMath.h> |
49 | #include <wtf/HashMap.h> |
50 | #include <wtf/MathExtras.h> |
51 | #include <wtf/StdSet.h> |
52 | #include <wtf/text/StringBuilder.h> |
53 | |
54 | namespace WebKit { |
55 | using namespace WebCore; |
56 | |
57 | #if PLATFORM(COCOA) |
58 | #define RELEASE_LOG_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - ResourceLoadStatisticsDatabaseStore::" fmt, this, ##__VA_ARGS__) |
59 | #define RELEASE_LOG_ERROR_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_ERROR_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - ResourceLoadStatisticsDatabaseStore::" fmt, this, ##__VA_ARGS__) |
60 | #else |
61 | #define RELEASE_LOG_IF_ALLOWED(sessionID, fmt, ...) ((void)0) |
62 | #define RELEASE_LOG_ERROR_IF_ALLOWED(sessionID, fmt, ...) ((void)0) |
63 | #endif |
64 | |
65 | constexpr auto observedDomainCountQuery = "SELECT COUNT(*) FROM ObservedDomains"_s ; |
66 | constexpr auto insertObservedDomainQuery = "INSERT INTO ObservedDomains (registrableDomain, lastSeen, hadUserInteraction," |
67 | "mostRecentUserInteractionTime, grandfathered, isPrevalent, isVeryPrevalent, dataRecordsRemoved, timesAccessedAsFirstPartyDueToUserInteraction," |
68 | "timesAccessedAsFirstPartyDueToStorageAccessAPI) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"_s ; |
69 | constexpr auto insertTopLevelDomainQuery = "INSERT INTO TopLevelDomains VALUES (?)"_s ; |
70 | constexpr auto domainIDFromStringQuery = "SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
71 | constexpr auto storageAccessUnderTopFrameDomainsQuery = "INSERT INTO StorageAccessUnderTopFrameDomains (domainID, topLevelDomainID) " |
72 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain == ?"_s ; |
73 | constexpr auto topFrameUniqueRedirectsToQuery = "INSERT INTO TopFrameUniqueRedirectsTo (sourceDomainID, toDomainID) SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain == ?"_s ; |
74 | constexpr auto topFrameUniqueRedirectsToExistsQuery = "SELECT EXISTS (SELECT 1 FROM TopFrameUniqueRedirectsTo WHERE sourceDomainID = ? " |
75 | "AND toDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
76 | constexpr auto topFrameUniqueRedirectsFromQuery = "INSERT INTO TopFrameUniqueRedirectsFrom (targetDomainID, fromDomainID) " |
77 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
78 | constexpr auto topFrameUniqueRedirectsFromExistsQuery = "SELECT EXISTS (SELECT 1 FROM TopFrameUniqueRedirectsFrom WHERE targetDomainID = ? " |
79 | "AND fromDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
80 | constexpr auto topFrameLinkDecorationsFromQuery = "INSERT INTO TopFrameLinkDecorationsFrom (fromDomainID, toDomainID) " |
81 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
82 | constexpr auto topFrameLinkDecorationsFromExistsQuery = "SELECT EXISTS (SELECT 1 FROM TopFrameLinkDecorationsFrom WHERE fromDomainID = ? " |
83 | "AND toDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
84 | constexpr auto subframeUnderTopFrameDomainsQuery = "INSERT INTO SubframeUnderTopFrameDomains (subFrameDomainID, topFrameDomainID) " |
85 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
86 | constexpr auto subframeUnderTopFrameDomainExistsQuery = "SELECT EXISTS (SELECT 1 FROM SubframeUnderTopFrameDomains WHERE subFrameDomainID = ? " |
87 | "AND topFrameDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
88 | constexpr auto subresourceUnderTopFrameDomainsQuery = "INSERT INTO SubresourceUnderTopFrameDomains (subresourceDomainID, topFrameDomainID) " |
89 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
90 | constexpr auto subresourceUnderTopFrameDomainExistsQuery = "SELECT EXISTS (SELECT 1 FROM SubresourceUnderTopFrameDomains " |
91 | "WHERE subresourceDomainID = ? AND topFrameDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
92 | constexpr auto subresourceUniqueRedirectsToQuery = "INSERT INTO SubresourceUniqueRedirectsTo (subresourceDomainID, toDomainID) " |
93 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain == ?"_s ; |
94 | constexpr auto subresourceUniqueRedirectsToExistsQuery = "SELECT EXISTS (SELECT 1 FROM SubresourceUniqueRedirectsTo WHERE subresourceDomainID = ? " |
95 | "AND toDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
96 | constexpr auto subresourceUniqueRedirectsFromQuery = "INSERT INTO SubresourceUniqueRedirectsFrom (subresourceDomainID, fromDomainID) " |
97 | "SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain == ?"_s ; |
98 | constexpr auto subresourceUniqueRedirectsFromExistsQuery = "SELECT EXISTS (SELECT 1 FROM SubresourceUniqueRedirectsFrom WHERE subresourceDomainID = ? " |
99 | "AND fromDomainID = (SELECT domainID FROM ObservedDomains WHERE registrableDomain = ?))"_s ; |
100 | constexpr auto mostRecentUserInteractionQuery = "UPDATE ObservedDomains SET hadUserInteraction = ?, mostRecentUserInteractionTime = ? " |
101 | "WHERE registrableDomain = ?"_s ; |
102 | constexpr auto updateLastSeenQuery = "UPDATE ObservedDomains SET lastSeen = ? WHERE registrableDomain = ?"_s ; |
103 | constexpr auto updatePrevalentResourceQuery = "UPDATE ObservedDomains SET isPrevalent = ? WHERE registrableDomain = ?"_s ; |
104 | constexpr auto isPrevalentResourceQuery = "SELECT isPrevalent FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
105 | constexpr auto updateVeryPrevalentResourceQuery = "UPDATE ObservedDomains SET isVeryPrevalent = ? WHERE registrableDomain = ?"_s ; |
106 | constexpr auto isVeryPrevalentResourceQuery = "SELECT isVeryPrevalent FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
107 | constexpr auto clearPrevalentResourceQuery = "UPDATE ObservedDomains SET isPrevalent = 0, isVeryPrevalent = 0 WHERE registrableDomain = ?"_s ; |
108 | constexpr auto hadUserInteractionQuery = "SELECT hadUserInteraction, mostRecentUserInteractionTime FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
109 | constexpr auto updateGrandfatheredQuery = "UPDATE ObservedDomains SET grandfathered = ? WHERE registrableDomain = ?"_s ; |
110 | constexpr auto isGrandfatheredQuery = "SELECT grandfathered FROM ObservedDomains WHERE registrableDomain = ?"_s ; |
111 | constexpr auto findExpiredUserInteractionQuery = "SELECT domainID FROM ObservedDomains WHERE hadUserInteraction = 1 AND mostRecentUserInteractionTime < ?"_s ; |
112 | |
113 | constexpr auto createObservedDomain = "CREATE TABLE ObservedDomains (" |
114 | "domainID INTEGER PRIMARY KEY, registrableDomain TEXT NOT NULL UNIQUE ON CONFLICT FAIL, lastSeen REAL NOT NULL, " |
115 | "hadUserInteraction INTEGER NOT NULL, mostRecentUserInteractionTime REAL NOT NULL, grandfathered INTEGER NOT NULL, " |
116 | "isPrevalent INTEGER NOT NULL, isVeryPrevalent INTEGER NOT NULL, dataRecordsRemoved INTEGER NOT NULL," |
117 | "timesAccessedAsFirstPartyDueToUserInteraction INTEGER NOT NULL, timesAccessedAsFirstPartyDueToStorageAccessAPI INTEGER NOT NULL);"_s ; |
118 | |
119 | constexpr auto createTopLevelDomains = "CREATE TABLE TopLevelDomains (" |
120 | "topLevelDomainID INTEGER PRIMARY KEY, CONSTRAINT fkDomainID FOREIGN KEY(topLevelDomainID) " |
121 | "REFERENCES ObservedDomains(domainID) ON DELETE CASCADE);"_s ; |
122 | |
123 | constexpr auto createStorageAccessUnderTopFrameDomains = "CREATE TABLE StorageAccessUnderTopFrameDomains (" |
124 | "domainID INTEGER NOT NULL, topLevelDomainID INTEGER NOT NULL ON CONFLICT FAIL, " |
125 | "CONSTRAINT fkDomainID FOREIGN KEY(domainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, " |
126 | "FOREIGN KEY(topLevelDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
127 | |
128 | constexpr auto createTopFrameUniqueRedirectsTo = "CREATE TABLE TopFrameUniqueRedirectsTo (" |
129 | "sourceDomainID INTEGER NOT NULL, toDomainID INTEGER NOT NULL, " |
130 | "FOREIGN KEY(sourceDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE, " |
131 | "FOREIGN KEY(toDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
132 | |
133 | constexpr auto createTopFrameUniqueRedirectsFrom = "CREATE TABLE TopFrameUniqueRedirectsFrom (" |
134 | "targetDomainID INTEGER NOT NULL, fromDomainID INTEGER NOT NULL, " |
135 | "FOREIGN KEY(targetDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE, " |
136 | "FOREIGN KEY(fromDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
137 | |
138 | constexpr auto createTopFrameLinkDecorationsFrom = "CREATE TABLE TopFrameLinkDecorationsFrom (" |
139 | "fromDomainID INTEGER NOT NULL, toDomainID INTEGER NOT NULL, " |
140 | "FOREIGN KEY(fromDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE, " |
141 | "FOREIGN KEY(toDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
142 | |
143 | constexpr auto createSubframeUnderTopFrameDomains = "CREATE TABLE SubframeUnderTopFrameDomains (" |
144 | "subFrameDomainID INTEGER NOT NULL, topFrameDomainID INTEGER NOT NULL, " |
145 | "FOREIGN KEY(subFrameDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, " |
146 | "FOREIGN KEY(topFrameDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
147 | |
148 | constexpr auto createSubresourceUnderTopFrameDomains = "CREATE TABLE SubresourceUnderTopFrameDomains (" |
149 | "subresourceDomainID INTEGER NOT NULL, topFrameDomainID INTEGER NOT NULL, " |
150 | "FOREIGN KEY(subresourceDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, " |
151 | "FOREIGN KEY(topFrameDomainID) REFERENCES TopLevelDomains(topLevelDomainID) ON DELETE CASCADE);"_s ; |
152 | |
153 | constexpr auto createSubresourceUniqueRedirectsTo = "CREATE TABLE SubresourceUniqueRedirectsTo (" |
154 | "subresourceDomainID INTEGER NOT NULL, toDomainID INTEGER NOT NULL, " |
155 | "FOREIGN KEY(subresourceDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, " |
156 | "FOREIGN KEY(toDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE);"_s ; |
157 | |
158 | constexpr auto createSubresourceUniqueRedirectsFrom = "CREATE TABLE SubresourceUniqueRedirectsFrom (" |
159 | "subresourceDomainID INTEGER NOT NULL, fromDomainID INTEGER NOT NULL, " |
160 | "FOREIGN KEY(subresourceDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, " |
161 | "FOREIGN KEY(fromDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE);"_s ; |
162 | |
163 | ResourceLoadStatisticsDatabaseStore::ResourceLoadStatisticsDatabaseStore(WebResourceLoadStatisticsStore& store, WorkQueue& workQueue, ShouldIncludeLocalhost shouldIncludeLocalhost, const String& storageDirectoryPath) |
164 | : ResourceLoadStatisticsStore(store, workQueue, shouldIncludeLocalhost) |
165 | , m_storageDirectoryPath(storageDirectoryPath + "/observations.db" ) |
166 | , m_observedDomainCount(m_database, observedDomainCountQuery) |
167 | , m_insertObservedDomainStatement(m_database, insertObservedDomainQuery) |
168 | , m_insertTopLevelDomainStatement(m_database, insertTopLevelDomainQuery) |
169 | , m_domainIDFromStringStatement(m_database, domainIDFromStringQuery) |
170 | , m_storageAccessUnderTopFrameDomainsStatement(m_database, storageAccessUnderTopFrameDomainsQuery) |
171 | , m_topFrameUniqueRedirectsTo(m_database, topFrameUniqueRedirectsToQuery) |
172 | , m_topFrameUniqueRedirectsToExists(m_database, topFrameUniqueRedirectsToExistsQuery) |
173 | , m_topFrameUniqueRedirectsFrom(m_database, topFrameUniqueRedirectsFromQuery) |
174 | , m_topFrameUniqueRedirectsFromExists(m_database, topFrameUniqueRedirectsFromExistsQuery) |
175 | , m_topFrameLinkDecorationsFrom(m_database, topFrameLinkDecorationsFromQuery) |
176 | , m_topFrameLinkDecorationsFromExists(m_database, topFrameLinkDecorationsFromExistsQuery) |
177 | , m_subframeUnderTopFrameDomains(m_database, subframeUnderTopFrameDomainsQuery) |
178 | , m_subframeUnderTopFrameDomainExists(m_database, subframeUnderTopFrameDomainExistsQuery) |
179 | , m_subresourceUnderTopFrameDomains(m_database, subresourceUnderTopFrameDomainsQuery) |
180 | , m_subresourceUnderTopFrameDomainExists(m_database, subresourceUnderTopFrameDomainExistsQuery) |
181 | , m_subresourceUniqueRedirectsTo(m_database, subresourceUniqueRedirectsToQuery) |
182 | , m_subresourceUniqueRedirectsToExists(m_database, subresourceUniqueRedirectsToExistsQuery) |
183 | , m_subresourceUniqueRedirectsFrom(m_database, subresourceUniqueRedirectsFromQuery) |
184 | , m_subresourceUniqueRedirectsFromExists(m_database, subresourceUniqueRedirectsFromExistsQuery) |
185 | , m_mostRecentUserInteractionStatement(m_database, mostRecentUserInteractionQuery) |
186 | , m_updateLastSeenStatement(m_database, updateLastSeenQuery) |
187 | , m_updatePrevalentResourceStatement(m_database, updatePrevalentResourceQuery) |
188 | , m_isPrevalentResourceStatement(m_database, isPrevalentResourceQuery) |
189 | , m_updateVeryPrevalentResourceStatement(m_database, updateVeryPrevalentResourceQuery) |
190 | , m_isVeryPrevalentResourceStatement(m_database, isVeryPrevalentResourceQuery) |
191 | , m_clearPrevalentResourceStatement(m_database, clearPrevalentResourceQuery) |
192 | , m_hadUserInteractionStatement(m_database, hadUserInteractionQuery) |
193 | , m_updateGrandfatheredStatement(m_database, updateGrandfatheredQuery) |
194 | , m_isGrandfatheredStatement(m_database, isGrandfatheredQuery) |
195 | , m_findExpiredUserInteractionStatement(m_database, findExpiredUserInteractionQuery) |
196 | { |
197 | ASSERT(!RunLoop::isMain()); |
198 | |
199 | if (!m_database.open(m_storageDirectoryPath)) { |
200 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::open failed, error message: %{public}s, database path: %{public}s" , this, m_database.lastErrorMsg(), m_storageDirectoryPath.utf8().data()); |
201 | ASSERT_NOT_REACHED(); |
202 | return; |
203 | } |
204 | |
205 | // Since we are using a workerQueue, the sequential dispatch blocks may be called by different threads. |
206 | m_database.disableThreadingChecks(); |
207 | |
208 | if (!m_database.tableExists("ObservedDomains"_s )) { |
209 | if (!createSchema()) { |
210 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::createSchema failed, error message: %{public}s, database path: %{public}s" , this, m_database.lastErrorMsg(), m_storageDirectoryPath.utf8().data()); |
211 | ASSERT_NOT_REACHED(); |
212 | return; |
213 | } |
214 | } |
215 | |
216 | if (!prepareStatements()) { |
217 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::prepareStatements failed, error message: %{public}s, database path: %{public}s" , this, m_database.lastErrorMsg(), m_storageDirectoryPath.utf8().data()); |
218 | ASSERT_NOT_REACHED(); |
219 | return; |
220 | } |
221 | |
222 | workQueue.dispatchAfter(5_s, [weakThis = makeWeakPtr(*this)] { |
223 | if (weakThis) |
224 | weakThis->calculateAndSubmitTelemetry(); |
225 | }); |
226 | } |
227 | |
228 | bool ResourceLoadStatisticsDatabaseStore::isEmpty() const |
229 | { |
230 | ASSERT(!RunLoop::isMain()); |
231 | |
232 | bool result = false; |
233 | if (m_observedDomainCount.step() == SQLITE_ROW) |
234 | result = !m_observedDomainCount.getColumnInt(0); |
235 | |
236 | int resetResult = m_observedDomainCount.reset(); |
237 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
238 | |
239 | return result; |
240 | } |
241 | |
242 | bool ResourceLoadStatisticsDatabaseStore::createSchema() |
243 | { |
244 | ASSERT(!RunLoop::isMain()); |
245 | |
246 | if (!m_database.executeCommand(createObservedDomain)) { |
247 | LOG_ERROR("Could not create ObservedDomains table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
248 | return false; |
249 | } |
250 | |
251 | if (!m_database.executeCommand(createTopLevelDomains)) { |
252 | LOG_ERROR("Could not create TopLevelDomains table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
253 | return false; |
254 | } |
255 | |
256 | if (!m_database.executeCommand(createStorageAccessUnderTopFrameDomains)) { |
257 | LOG_ERROR("Could not create StorageAccessUnderTopFrameDomains table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
258 | return false; |
259 | } |
260 | |
261 | if (!m_database.executeCommand(createTopFrameUniqueRedirectsTo)) { |
262 | LOG_ERROR("Could not create TopFrameUniqueRedirectsTo table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
263 | return false; |
264 | } |
265 | |
266 | if (!m_database.executeCommand(createTopFrameUniqueRedirectsFrom)) { |
267 | LOG_ERROR("Could not create TopFrameUniqueRedirectsFrom table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
268 | return false; |
269 | } |
270 | |
271 | if (!m_database.executeCommand(createTopFrameLinkDecorationsFrom)) { |
272 | LOG_ERROR("Could not create TopFrameLinkDecorationsFrom table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
273 | return false; |
274 | } |
275 | |
276 | if (!m_database.executeCommand(createSubframeUnderTopFrameDomains)) { |
277 | LOG_ERROR("Could not create SubframeUnderTopFrameDomains table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
278 | return false; |
279 | } |
280 | |
281 | if (!m_database.executeCommand(createSubresourceUnderTopFrameDomains)) { |
282 | LOG_ERROR("Could not create SubresourceUnderTopFrameDomains table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
283 | return false; |
284 | } |
285 | |
286 | if (!m_database.executeCommand(createSubresourceUniqueRedirectsTo)) { |
287 | LOG_ERROR("Could not create SubresourceUniqueRedirectsTo table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
288 | return false; |
289 | } |
290 | |
291 | if (!m_database.executeCommand(createSubresourceUniqueRedirectsFrom)) { |
292 | LOG_ERROR("Could not create SubresourceUniqueRedirectsFrom table in database (%i) - %s" , m_database.lastError(), m_database.lastErrorMsg()); |
293 | return false; |
294 | } |
295 | |
296 | return true; |
297 | } |
298 | |
299 | bool ResourceLoadStatisticsDatabaseStore::prepareStatements() |
300 | { |
301 | ASSERT(!RunLoop::isMain()); |
302 | |
303 | if (m_observedDomainCount.prepare() != SQLITE_OK |
304 | || m_insertObservedDomainStatement.prepare() != SQLITE_OK |
305 | || m_insertTopLevelDomainStatement.prepare() != SQLITE_OK |
306 | || m_domainIDFromStringStatement.prepare() != SQLITE_OK |
307 | || m_storageAccessUnderTopFrameDomainsStatement.prepare() != SQLITE_OK |
308 | || m_topFrameUniqueRedirectsTo.prepare() != SQLITE_OK |
309 | || m_topFrameUniqueRedirectsToExists.prepare() != SQLITE_OK |
310 | || m_topFrameUniqueRedirectsFrom.prepare() != SQLITE_OK |
311 | || m_topFrameUniqueRedirectsFromExists.prepare() != SQLITE_OK |
312 | || m_subframeUnderTopFrameDomains.prepare() != SQLITE_OK |
313 | || m_subframeUnderTopFrameDomainExists.prepare() != SQLITE_OK |
314 | || m_subresourceUnderTopFrameDomains.prepare() != SQLITE_OK |
315 | || m_subresourceUnderTopFrameDomainExists.prepare() != SQLITE_OK |
316 | || m_subresourceUniqueRedirectsTo.prepare() != SQLITE_OK |
317 | || m_subresourceUniqueRedirectsToExists.prepare() != SQLITE_OK |
318 | || m_subresourceUniqueRedirectsFrom.prepare() != SQLITE_OK |
319 | || m_subresourceUniqueRedirectsFromExists.prepare() != SQLITE_OK |
320 | || m_updateLastSeenStatement.prepare() != SQLITE_OK |
321 | || m_mostRecentUserInteractionStatement.prepare() != SQLITE_OK |
322 | || m_updatePrevalentResourceStatement.prepare() != SQLITE_OK |
323 | || m_isPrevalentResourceStatement.prepare() != SQLITE_OK |
324 | || m_updateVeryPrevalentResourceStatement.prepare() != SQLITE_OK |
325 | || m_isVeryPrevalentResourceStatement.prepare() != SQLITE_OK |
326 | || m_clearPrevalentResourceStatement.prepare() != SQLITE_OK |
327 | || m_hadUserInteractionStatement.prepare() != SQLITE_OK |
328 | || m_updateGrandfatheredStatement.prepare() != SQLITE_OK |
329 | || m_isGrandfatheredStatement.prepare() != SQLITE_OK |
330 | || m_findExpiredUserInteractionStatement.prepare() != SQLITE_OK |
331 | ) { |
332 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::prepareStatements failed to prepare, error message: %{public}s" , this, m_database.lastErrorMsg()); |
333 | ASSERT_NOT_REACHED(); |
334 | return false; |
335 | } |
336 | |
337 | return true; |
338 | } |
339 | |
340 | bool ResourceLoadStatisticsDatabaseStore::insertObservedDomain(const ResourceLoadStatistics& loadStatistics) |
341 | { |
342 | ASSERT(!RunLoop::isMain()); |
343 | |
344 | #ifndef NDEBUG |
345 | ASSERT(confirmDomainDoesNotExist(loadStatistics.registrableDomain)); |
346 | #endif |
347 | |
348 | if (m_insertObservedDomainStatement.bindText(1, loadStatistics.registrableDomain.string()) != SQLITE_OK |
349 | || m_insertObservedDomainStatement.bindDouble(2, loadStatistics.lastSeen.secondsSinceEpoch().value()) != SQLITE_OK |
350 | || m_insertObservedDomainStatement.bindInt(3, loadStatistics.hadUserInteraction) != SQLITE_OK |
351 | || m_insertObservedDomainStatement.bindDouble(4, loadStatistics.mostRecentUserInteractionTime.secondsSinceEpoch().value()) != SQLITE_OK |
352 | || m_insertObservedDomainStatement.bindInt(5, loadStatistics.grandfathered) != SQLITE_OK |
353 | || m_insertObservedDomainStatement.bindInt(6, loadStatistics.isPrevalentResource) != SQLITE_OK |
354 | || m_insertObservedDomainStatement.bindInt(7, loadStatistics.isVeryPrevalentResource) != SQLITE_OK |
355 | || m_insertObservedDomainStatement.bindInt(8, loadStatistics.dataRecordsRemoved) != SQLITE_OK |
356 | || m_insertObservedDomainStatement.bindInt(9, loadStatistics.timesAccessedAsFirstPartyDueToUserInteraction) != SQLITE_OK |
357 | || m_insertObservedDomainStatement.bindInt(10, loadStatistics.timesAccessedAsFirstPartyDueToStorageAccessAPI) != SQLITE_OK) { |
358 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::insertObservedDomain failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
359 | ASSERT_NOT_REACHED(); |
360 | return false; |
361 | } |
362 | |
363 | if (m_insertObservedDomainStatement.step() != SQLITE_DONE) { |
364 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::insertObservedDomain failed to commit, error message: %{public}s" , this, m_database.lastErrorMsg()); |
365 | ASSERT_NOT_REACHED(); |
366 | return false; |
367 | } |
368 | |
369 | int resetResult = m_insertObservedDomainStatement.reset(); |
370 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
371 | |
372 | return true; |
373 | } |
374 | |
375 | bool ResourceLoadStatisticsDatabaseStore::relationshipExists(WebCore::SQLiteStatement& statement, unsigned firstDomainID, const RegistrableDomain& secondDomain) const |
376 | { |
377 | ASSERT(!RunLoop::isMain()); |
378 | |
379 | if (statement.bindInt(1, firstDomainID) != SQLITE_OK |
380 | || statement.bindText(2, secondDomain.string()) != SQLITE_OK |
381 | || statement.step() != SQLITE_ROW) { |
382 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::m_insertDomainRelationshipStatement failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
383 | ASSERT_NOT_REACHED(); |
384 | return false; |
385 | } |
386 | |
387 | bool relationShipExists = !!statement.getColumnInt(0); |
388 | |
389 | int resetResult = statement.reset(); |
390 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
391 | |
392 | return relationShipExists; |
393 | } |
394 | |
395 | bool ResourceLoadStatisticsDatabaseStore::insertDomainRelationship(WebCore::SQLiteStatement& statement, unsigned domainID, const RegistrableDomain& topFrame) |
396 | { |
397 | ASSERT(!RunLoop::isMain()); |
398 | |
399 | if (statement.bindInt(1, domainID) != SQLITE_OK |
400 | || statement.bindText(2, topFrame.string()) != SQLITE_OK |
401 | || statement.step() != SQLITE_DONE) { |
402 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::m_insertDomainRelationshipStatement failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
403 | ASSERT_NOT_REACHED(); |
404 | return false; |
405 | } |
406 | |
407 | int resetResult = statement.reset(); |
408 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
409 | |
410 | return true; |
411 | } |
412 | |
413 | #ifndef NDEBUG |
414 | bool ResourceLoadStatisticsDatabaseStore::confirmDomainDoesNotExist(const RegistrableDomain& domain) const |
415 | { |
416 | ASSERT(!RunLoop::isMain()); |
417 | |
418 | if (m_domainIDFromStringStatement.bindText(1, domain.string()) != SQLITE_OK) { |
419 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::confirmDomainDoesNotExist failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
420 | ASSERT_NOT_REACHED(); |
421 | return false; |
422 | } |
423 | |
424 | if (m_domainIDFromStringStatement.step() == SQLITE_ROW) { |
425 | int resetResult = m_domainIDFromStringStatement.reset(); |
426 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
427 | return false; |
428 | } |
429 | |
430 | int resetResult = m_domainIDFromStringStatement.reset(); |
431 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
432 | |
433 | return true; |
434 | } |
435 | #endif |
436 | |
437 | unsigned ResourceLoadStatisticsDatabaseStore::domainID(const RegistrableDomain& domain) const |
438 | { |
439 | ASSERT(!RunLoop::isMain()); |
440 | |
441 | unsigned domainID = 0; |
442 | |
443 | if (m_domainIDFromStringStatement.bindText(1, domain.string()) != SQLITE_OK |
444 | || m_domainIDFromStringStatement.step() != SQLITE_ROW) { |
445 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::domainIDFromString failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
446 | ASSERT_NOT_REACHED(); |
447 | return domainID; |
448 | } |
449 | |
450 | domainID = m_domainIDFromStringStatement.getColumnInt(0); |
451 | |
452 | int resetResult = m_domainIDFromStringStatement.reset(); |
453 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
454 | |
455 | return domainID; |
456 | } |
457 | |
458 | void ResourceLoadStatisticsDatabaseStore::insertDomainRelationships(const ResourceLoadStatistics& loadStatistics) |
459 | { |
460 | ASSERT(!RunLoop::isMain()); |
461 | |
462 | auto registrableDomainID = domainID(loadStatistics.registrableDomain); |
463 | for (auto& topFrameDomain : loadStatistics.storageAccessUnderTopFrameDomains) |
464 | insertDomainRelationship(m_storageAccessUnderTopFrameDomainsStatement, registrableDomainID, topFrameDomain); |
465 | |
466 | for (auto& toDomain : loadStatistics.topFrameUniqueRedirectsTo) |
467 | insertDomainRelationship(m_topFrameUniqueRedirectsTo, registrableDomainID, toDomain); |
468 | |
469 | for (auto& fromDomain : loadStatistics.topFrameUniqueRedirectsFrom) |
470 | insertDomainRelationship(m_topFrameUniqueRedirectsFrom, registrableDomainID, fromDomain); |
471 | |
472 | for (auto& topFrameDomain : loadStatistics.subframeUnderTopFrameDomains) |
473 | insertDomainRelationship(m_subframeUnderTopFrameDomains, registrableDomainID, topFrameDomain); |
474 | |
475 | for (auto& topFrameDomain : loadStatistics.subresourceUnderTopFrameDomains) |
476 | insertDomainRelationship(m_subresourceUnderTopFrameDomains, registrableDomainID, topFrameDomain); |
477 | |
478 | for (auto& toDomain : loadStatistics.subresourceUniqueRedirectsTo) |
479 | insertDomainRelationship(m_subresourceUniqueRedirectsTo, registrableDomainID, toDomain); |
480 | |
481 | for (auto& fromDomain : loadStatistics.subresourceUniqueRedirectsFrom) |
482 | insertDomainRelationship(m_subresourceUniqueRedirectsFrom, registrableDomainID, fromDomain); |
483 | } |
484 | |
485 | void ResourceLoadStatisticsDatabaseStore::populateFromMemoryStore(const ResourceLoadStatisticsMemoryStore& memoryStore) |
486 | { |
487 | ASSERT(!RunLoop::isMain()); |
488 | |
489 | if (!isEmpty()) |
490 | return; |
491 | |
492 | auto& statisticsMap = memoryStore.data(); |
493 | for (const auto& statistic : statisticsMap) |
494 | insertObservedDomain(statistic.value); |
495 | |
496 | // Make a separate pass for inter-domain relationships so we |
497 | // can refer to the ObservedDomain table entries |
498 | for (auto& statistic : statisticsMap) |
499 | insertDomainRelationships(statistic.value); |
500 | |
501 | m_database.runVacuumCommand(); |
502 | } |
503 | |
504 | void ResourceLoadStatisticsDatabaseStore::calculateAndSubmitTelemetry() const |
505 | { |
506 | ASSERT(!RunLoop::isMain()); |
507 | |
508 | // FIXME(195088): Implement for Database version. |
509 | } |
510 | |
511 | static String domainsToString(const HashSet<RegistrableDomain>& domains) |
512 | { |
513 | StringBuilder builder; |
514 | for (auto domainName : domains) { |
515 | if (!builder.isEmpty()) |
516 | builder.appendLiteral(", " ); |
517 | builder.append('"'); |
518 | builder.append(domainName.string()); |
519 | builder.append('"'); |
520 | } |
521 | |
522 | return builder.toString(); |
523 | } |
524 | |
525 | void ResourceLoadStatisticsDatabaseStore::incrementRecordsDeletedCountForDomains(HashSet<RegistrableDomain>&& domains) |
526 | { |
527 | ASSERT(!RunLoop::isMain()); |
528 | |
529 | SQLiteStatement domainsToUpdateStatement(m_database, makeString("UPDATE ObservedDomains SET dataRecordsRemoved = dataRecordsRemoved + 1 WHERE registrableDomain IN (" , domainsToString(domains), ")" )); |
530 | if (domainsToUpdateStatement.prepare() != SQLITE_OK |
531 | || domainsToUpdateStatement.step() != SQLITE_DONE) { |
532 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::incrementStatisticsForDomains failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
533 | ASSERT_NOT_REACHED(); |
534 | } |
535 | } |
536 | |
537 | unsigned ResourceLoadStatisticsDatabaseStore::recursivelyFindNonPrevalentDomainsThatRedirectedToThisDomain(unsigned primaryDomainID, StdSet<unsigned>& nonPrevalentRedirectionSources, unsigned numberOfRecursiveCalls) |
538 | { |
539 | ASSERT(!RunLoop::isMain()); |
540 | |
541 | if (numberOfRecursiveCalls >= maxNumberOfRecursiveCallsInRedirectTraceBack) { |
542 | RELEASE_LOG(ResourceLoadStatistics, "Hit %u recursive calls in redirect backtrace. Returning early." , maxNumberOfRecursiveCallsInRedirectTraceBack); |
543 | return numberOfRecursiveCalls; |
544 | } |
545 | |
546 | ++numberOfRecursiveCalls; |
547 | |
548 | StdSet<unsigned> newlyIdentifiedDomains; |
549 | SQLiteStatement findSubresources(m_database, makeString("SELECT SubresourceUniqueRedirectsFrom.fromDomainID from SubresourceUniqueRedirectsFrom INNER JOIN ObservedDomains ON ObservedDomains.domainID = SubresourceUniqueRedirectsFrom.fromDomainID WHERE subresourceDomainID = " , String::number(primaryDomainID), "AND ObservedDomains.isPrevalent = 0" )); |
550 | if (findSubresources.prepare() == SQLITE_OK) { |
551 | while (findSubresources.step() == SQLITE_ROW) { |
552 | auto newDomainID = findSubresources.getColumnInt(0); |
553 | auto insertResult = nonPrevalentRedirectionSources.insert(newDomainID); |
554 | if (insertResult.second) |
555 | newlyIdentifiedDomains.insert(newDomainID); |
556 | } |
557 | } |
558 | |
559 | SQLiteStatement findTopFrames(m_database, makeString("SELECT TopFrameUniqueRedirectsFrom.fromDomainID from TopFrameUniqueRedirectsFrom INNER JOIN ObservedDomains ON ObservedDomains.domainID = TopFrameUniqueRedirectsFrom.fromDomainID WHERE targetDomainID = " , String::number(primaryDomainID), "AND ObservedDomains.isPrevalent = 0" )); |
560 | if (findTopFrames.prepare() == SQLITE_OK) { |
561 | while (findTopFrames.step() == SQLITE_ROW) { |
562 | auto newDomainID = findTopFrames.getColumnInt(0); |
563 | auto insertResult = nonPrevalentRedirectionSources.insert(newDomainID); |
564 | if (insertResult.second) |
565 | newlyIdentifiedDomains.insert(newDomainID); |
566 | } |
567 | } |
568 | |
569 | if (newlyIdentifiedDomains.empty()) |
570 | return numberOfRecursiveCalls; |
571 | |
572 | for (auto domainID : newlyIdentifiedDomains) |
573 | numberOfRecursiveCalls = recursivelyFindNonPrevalentDomainsThatRedirectedToThisDomain(domainID, nonPrevalentRedirectionSources, numberOfRecursiveCalls); |
574 | |
575 | return numberOfRecursiveCalls; |
576 | } |
577 | |
578 | template <typename IteratorType> |
579 | static String buildList(const WTF::IteratorRange<IteratorType>& values) |
580 | { |
581 | StringBuilder builder; |
582 | for (auto domainID : values) { |
583 | if (!builder.isEmpty()) |
584 | builder.appendLiteral(", " ); |
585 | builder.appendNumber(domainID); |
586 | } |
587 | return builder.toString(); |
588 | } |
589 | |
590 | void ResourceLoadStatisticsDatabaseStore::markAsPrevalentIfHasRedirectedToPrevalent() |
591 | { |
592 | ASSERT(!RunLoop::isMain()); |
593 | |
594 | StdSet<unsigned> prevalentDueToRedirect; |
595 | SQLiteStatement subresourceRedirectStatement(m_database, "SELECT DISTINCT SubresourceUniqueRedirectsTo.subresourceDomainID FROM SubresourceUniqueRedirectsTo JOIN ObservedDomains ON ObservedDomains.domainID = SubresourceUniqueRedirectsTo.toDomainID AND ObservedDomains.isPrevalent = 1"_s ); |
596 | if (subresourceRedirectStatement.prepare() == SQLITE_OK) { |
597 | while (subresourceRedirectStatement.step() == SQLITE_ROW) |
598 | prevalentDueToRedirect.insert(subresourceRedirectStatement.getColumnInt(0)); |
599 | } |
600 | |
601 | SQLiteStatement topFrameRedirectStatement(m_database, "SELECT DISTINCT TopFrameUniqueRedirectsTo.sourceDomainID FROM TopFrameUniqueRedirectsTo JOIN ObservedDomains ON ObservedDomains.domainID = TopFrameUniqueRedirectsTo.toDomainID AND ObservedDomains.isPrevalent = 1"_s ); |
602 | if (topFrameRedirectStatement.prepare() == SQLITE_OK) { |
603 | while (topFrameRedirectStatement.step() == SQLITE_ROW) |
604 | prevalentDueToRedirect.insert(topFrameRedirectStatement.getColumnInt(0)); |
605 | } |
606 | |
607 | SQLiteStatement markPrevalentStatement(m_database, makeString("UPDATE ObservedDomains SET isPrevalent = 1 WHERE domainID IN (" , buildList(WTF::IteratorRange<StdSet<unsigned>::iterator>(prevalentDueToRedirect.begin(), prevalentDueToRedirect.end())), ")" )); |
608 | if (markPrevalentStatement.prepare() != SQLITE_OK |
609 | || markPrevalentStatement.step() != SQLITE_DONE) { |
610 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::markAsPrevalentIfHasRedirectedToPrevalent failed to execute, error message: %{public}s" , this, m_database.lastErrorMsg()); |
611 | ASSERT_NOT_REACHED(); |
612 | } |
613 | } |
614 | |
615 | HashMap<unsigned, ResourceLoadStatisticsDatabaseStore::NotVeryPrevalentResources> ResourceLoadStatisticsDatabaseStore::findNotVeryPrevalentResources() |
616 | { |
617 | ASSERT(!RunLoop::isMain()); |
618 | |
619 | HashMap<unsigned, NotVeryPrevalentResources> results; |
620 | SQLiteStatement notVeryPrevalentResourcesStatement(m_database, "SELECT domainID, registrableDomain, isPrevalent FROM ObservedDomains WHERE isVeryPrevalent = 0"_s ); |
621 | if (notVeryPrevalentResourcesStatement.prepare() == SQLITE_OK) { |
622 | while (notVeryPrevalentResourcesStatement.step() == SQLITE_ROW) { |
623 | unsigned key = static_cast<unsigned>(notVeryPrevalentResourcesStatement.getColumnInt(0)); |
624 | NotVeryPrevalentResources value({ RegistrableDomain::uncheckedCreateFromRegistrableDomainString(notVeryPrevalentResourcesStatement.getColumnText(1)) |
625 | , notVeryPrevalentResourcesStatement.getColumnInt(2) ? ResourceLoadPrevalence::High : ResourceLoadPrevalence::Low |
626 | , 0, 0, 0, 0 }); |
627 | results.add(key, value); |
628 | } |
629 | } |
630 | |
631 | StringBuilder builder; |
632 | for (auto value : results.keys()) { |
633 | if (!builder.isEmpty()) |
634 | builder.appendLiteral(", " ); |
635 | builder.appendNumber(value); |
636 | } |
637 | |
638 | auto domainIDsOfInterest = builder.toString(); |
639 | |
640 | SQLiteStatement subresourceUnderTopFrameDomainsStatement(m_database, makeString("SELECT subresourceDomainID, COUNT(topFrameDomainID) FROM SubresourceUnderTopFrameDomains WHERE subresourceDomainID IN (" , domainIDsOfInterest, ") GROUP BY subresourceDomainID" )); |
641 | if (subresourceUnderTopFrameDomainsStatement.prepare() == SQLITE_OK) { |
642 | while (subresourceUnderTopFrameDomainsStatement.step() == SQLITE_ROW) { |
643 | unsigned domainID = static_cast<unsigned>(subresourceUnderTopFrameDomainsStatement.getColumnInt(0)); |
644 | auto result = results.find(domainID); |
645 | if (result != results.end()) |
646 | result->value.subresourceUnderTopFrameDomainsCount = static_cast<unsigned>(subresourceUnderTopFrameDomainsStatement.getColumnInt(1)); |
647 | } |
648 | } |
649 | |
650 | SQLiteStatement subresourceUniqueRedirectsToCountStatement(m_database, makeString("SELECT subresourceDomainID, COUNT(toDomainID) FROM SubresourceUniqueRedirectsTo WHERE subresourceDomainID IN (" , domainIDsOfInterest, ") GROUP BY subresourceDomainID" )); |
651 | if (subresourceUniqueRedirectsToCountStatement.prepare() == SQLITE_OK) { |
652 | while (subresourceUniqueRedirectsToCountStatement.step() == SQLITE_ROW) { |
653 | unsigned domainID = static_cast<unsigned>(subresourceUniqueRedirectsToCountStatement.getColumnInt(0)); |
654 | auto result = results.find(domainID); |
655 | if (result != results.end()) |
656 | result->value.subresourceUniqueRedirectsToCount = static_cast<unsigned>(subresourceUniqueRedirectsToCountStatement.getColumnInt(1)); |
657 | } |
658 | } |
659 | |
660 | SQLiteStatement subframeUnderTopFrameDomainsCountStatement(m_database, makeString("SELECT subresourceDomainID, COUNT(topFrameDomainID) FROM SubresourceUnderTopFrameDomains WHERE subresourceDomainID IN (" , domainIDsOfInterest, ") GROUP BY subresourceDomainID" )); |
661 | if (subframeUnderTopFrameDomainsCountStatement.prepare() == SQLITE_OK) { |
662 | while (subframeUnderTopFrameDomainsCountStatement.step() == SQLITE_ROW) { |
663 | unsigned domainID = static_cast<unsigned>(subframeUnderTopFrameDomainsCountStatement.getColumnInt(0)); |
664 | auto result = results.find(domainID); |
665 | if (result != results.end()) |
666 | result->value.subframeUnderTopFrameDomainsCount = static_cast<unsigned>(subframeUnderTopFrameDomainsCountStatement.getColumnInt(1)); |
667 | } |
668 | } |
669 | |
670 | SQLiteStatement topFrameUniqueRedirectsToCountStatement(m_database, makeString("SELECT sourceDomainID, COUNT(toDomainID) FROM TopFrameUniqueRedirectsTo WHERE sourceDomainID IN (" , domainIDsOfInterest, ") GROUP BY sourceDomainID" )); |
671 | if (topFrameUniqueRedirectsToCountStatement.prepare() == SQLITE_OK) { |
672 | while (topFrameUniqueRedirectsToCountStatement.step() == SQLITE_ROW) { |
673 | unsigned domainID = static_cast<unsigned>(topFrameUniqueRedirectsToCountStatement.getColumnInt(0)); |
674 | auto result = results.find(domainID); |
675 | if (result != results.end()) |
676 | result->value.topFrameUniqueRedirectsToCount = static_cast<unsigned>(topFrameUniqueRedirectsToCountStatement.getColumnInt(1)); |
677 | } |
678 | } |
679 | |
680 | return results; |
681 | } |
682 | |
683 | void ResourceLoadStatisticsDatabaseStore::reclassifyResources() |
684 | { |
685 | ASSERT(!RunLoop::isMain()); |
686 | |
687 | auto notVeryPrevalentResources = findNotVeryPrevalentResources(); |
688 | |
689 | for (auto& resourceStatistic : notVeryPrevalentResources.values()) { |
690 | if (shouldSkip(resourceStatistic.registerableDomain)) |
691 | continue; |
692 | |
693 | auto newPrevalence = classifier().calculateResourcePrevalence(resourceStatistic.subresourceUnderTopFrameDomainsCount, resourceStatistic.subresourceUniqueRedirectsToCount, resourceStatistic.subframeUnderTopFrameDomainsCount, resourceStatistic.topFrameUniqueRedirectsToCount, resourceStatistic.prevalence); |
694 | if (newPrevalence != resourceStatistic.prevalence) |
695 | setPrevalentResource(resourceStatistic.registerableDomain, newPrevalence); |
696 | } |
697 | } |
698 | |
699 | void ResourceLoadStatisticsDatabaseStore::classifyPrevalentResources() |
700 | { |
701 | ASSERT(!RunLoop::isMain()); |
702 | ensurePrevalentResourcesForDebugMode(); |
703 | markAsPrevalentIfHasRedirectedToPrevalent(); |
704 | reclassifyResources(); |
705 | } |
706 | |
707 | void ResourceLoadStatisticsDatabaseStore::syncStorageIfNeeded() |
708 | { |
709 | ASSERT(!RunLoop::isMain()); |
710 | m_database.runVacuumCommand(); |
711 | } |
712 | |
713 | void ResourceLoadStatisticsDatabaseStore::syncStorageImmediately() |
714 | { |
715 | ASSERT(!RunLoop::isMain()); |
716 | m_database.runVacuumCommand(); |
717 | } |
718 | |
719 | void ResourceLoadStatisticsDatabaseStore::hasStorageAccess(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain, Optional<FrameID> frameID, PageIdentifier pageID, CompletionHandler<void(bool)>&& completionHandler) |
720 | { |
721 | ASSERT(!RunLoop::isMain()); |
722 | |
723 | ensureResourceStatisticsForRegistrableDomain(subFrameDomain); |
724 | |
725 | switch (cookieTreatmentForOrigin(subFrameDomain)) { |
726 | case CookieTreatmentResult::BlockAndPurge: |
727 | completionHandler(false); |
728 | return; |
729 | case CookieTreatmentResult::BlockAndKeep: |
730 | completionHandler(true); |
731 | return; |
732 | case CookieTreatmentResult::Allow: |
733 | // Do nothing |
734 | break; |
735 | }; |
736 | |
737 | RunLoop::main().dispatch([store = makeRef(store()), subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, completionHandler = WTFMove(completionHandler)]() mutable { |
738 | store->callHasStorageAccessForFrameHandler(subFrameDomain, topFrameDomain, frameID.value(), pageID, [store = store.copyRef(), completionHandler = WTFMove(completionHandler)](bool result) mutable { |
739 | store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler), result] () mutable { |
740 | completionHandler(result); |
741 | }); |
742 | }); |
743 | }); |
744 | } |
745 | |
746 | void ResourceLoadStatisticsDatabaseStore::requestStorageAccess(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, FrameID frameID, PageIdentifier pageID, CompletionHandler<void(StorageAccessStatus)>&& completionHandler) |
747 | { |
748 | ASSERT(!RunLoop::isMain()); |
749 | |
750 | auto subFrameStatus = ensureResourceStatisticsForRegistrableDomain(subFrameDomain); |
751 | |
752 | switch (cookieTreatmentForOrigin(subFrameDomain)) { |
753 | case CookieTreatmentResult::BlockAndPurge: { |
754 | #if !RELEASE_LOG_DISABLED |
755 | RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ResourceLoadStatisticsDebug, "Cannot grant storage access to %{public}s since its cookies are blocked in third-party contexts and it has not received user interaction as first-party." , subFrameDomain.string().utf8().data()); |
756 | #endif |
757 | completionHandler(StorageAccessStatus::CannotRequestAccess); |
758 | } |
759 | return; |
760 | case CookieTreatmentResult::BlockAndKeep: { |
761 | #if !RELEASE_LOG_DISABLED |
762 | RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ResourceLoadStatisticsDebug, "No need to grant storage access to %{public}s since its cookies are not blocked in third-party contexts." , subFrameDomain.string().utf8().data()); |
763 | #endif |
764 | completionHandler(StorageAccessStatus::HasAccess); |
765 | } |
766 | return; |
767 | case CookieTreatmentResult::Allow: |
768 | // Do nothing |
769 | break; |
770 | }; |
771 | |
772 | auto userWasPromptedEarlier = hasUserGrantedStorageAccessThroughPrompt(subFrameStatus.second, topFrameDomain); |
773 | if (userWasPromptedEarlier == StorageAccessPromptWasShown::No) { |
774 | #if !RELEASE_LOG_DISABLED |
775 | RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ResourceLoadStatisticsDebug, "About to ask the user whether they want to grant storage access to %{public}s under %{public}s or not." , subFrameDomain.string().utf8().data(), topFrameDomain.string().utf8().data()); |
776 | #endif |
777 | completionHandler(StorageAccessStatus::RequiresUserPrompt); |
778 | return; |
779 | } |
780 | |
781 | #if !RELEASE_LOG_DISABLED |
782 | if (userWasPromptedEarlier == StorageAccessPromptWasShown::Yes) |
783 | RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ResourceLoadStatisticsDebug, "Storage access was granted to %{public}s under %{public}s." , subFrameDomain.string().utf8().data(), topFrameDomain.string().utf8().data()); |
784 | #endif |
785 | |
786 | SQLiteStatement incrementStorageAccess(m_database, makeString("UPDATE ObservedDomains SET timesAccessedAsFirstPartyDueToStorageAccessAPI = timesAccessedAsFirstPartyDueToStorageAccessAPI + 1 WHERE domainID = " , String::number(subFrameStatus.second))); |
787 | if (incrementStorageAccess.prepare() != SQLITE_OK |
788 | || incrementStorageAccess.step() != SQLITE_DONE) { |
789 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::requestStorageAccess failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
790 | ASSERT_NOT_REACHED(); |
791 | } |
792 | |
793 | grantStorageAccessInternal(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, pageID, userWasPromptedEarlier, [completionHandler = WTFMove(completionHandler)] (StorageAccessWasGranted wasGranted) mutable { |
794 | completionHandler(wasGranted == StorageAccessWasGranted::Yes ? StorageAccessStatus::HasAccess : StorageAccessStatus::CannotRequestAccess); |
795 | }); |
796 | } |
797 | |
798 | void ResourceLoadStatisticsDatabaseStore::requestStorageAccessUnderOpener(DomainInNeedOfStorageAccess&& domainInNeedOfStorageAccess, PageIdentifier openerPageID, OpenerDomain&& openerDomain) |
799 | { |
800 | ASSERT(domainInNeedOfStorageAccess != openerDomain); |
801 | ASSERT(!RunLoop::isMain()); |
802 | |
803 | if (domainInNeedOfStorageAccess == openerDomain) |
804 | return; |
805 | |
806 | ensureResourceStatisticsForRegistrableDomain(domainInNeedOfStorageAccess); |
807 | |
808 | if (cookieTreatmentForOrigin(domainInNeedOfStorageAccess) != CookieTreatmentResult::Allow) |
809 | return; |
810 | |
811 | #if !RELEASE_LOG_DISABLED |
812 | RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ResourceLoadStatisticsDebug, "[Temporary combatibility fix] Storage access was granted for %{public}s under opener page from %{public}s, with user interaction in the opened window." , domainInNeedOfStorageAccess.string().utf8().data(), openerDomain.string().utf8().data()); |
813 | #endif |
814 | grantStorageAccessInternal(WTFMove(domainInNeedOfStorageAccess), WTFMove(openerDomain), WTF::nullopt, openerPageID, StorageAccessPromptWasShown::No, [](StorageAccessWasGranted) { }); |
815 | } |
816 | |
817 | void ResourceLoadStatisticsDatabaseStore::grantStorageAccess(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, uint64_t frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShown, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler) |
818 | { |
819 | ASSERT(!RunLoop::isMain()); |
820 | |
821 | if (promptWasShown == StorageAccessPromptWasShown::Yes) { |
822 | auto subFrameStatus = ensureResourceStatisticsForRegistrableDomain(subFrameDomain); |
823 | ASSERT(subFrameStatus.first == AddedRecord::No); |
824 | ASSERT(hasHadUserInteraction(subFrameDomain, OperatingDatesWindow::Long)); |
825 | insertDomainRelationship(m_storageAccessUnderTopFrameDomainsStatement, subFrameStatus.second, topFrameDomain); |
826 | } |
827 | |
828 | grantStorageAccessInternal(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, pageID, promptWasShown, WTFMove(completionHandler)); |
829 | } |
830 | |
831 | void ResourceLoadStatisticsDatabaseStore::grantStorageAccessInternal(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, Optional<FrameID> frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShownNowOrEarlier, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler) |
832 | { |
833 | ASSERT(!RunLoop::isMain()); |
834 | |
835 | if (subFrameDomain == topFrameDomain) { |
836 | completionHandler(StorageAccessWasGranted::Yes); |
837 | return; |
838 | } |
839 | |
840 | if (promptWasShownNowOrEarlier == StorageAccessPromptWasShown::Yes) { |
841 | #ifndef NDEBUG |
842 | auto subFrameStatus = ensureResourceStatisticsForRegistrableDomain(subFrameDomain); |
843 | ASSERT(subFrameStatus.first == AddedRecord::No); |
844 | ASSERT(hasHadUserInteraction(subFrameDomain, OperatingDatesWindow::Long)); |
845 | ASSERT(hasUserGrantedStorageAccessThroughPrompt(subFrameStatus.second, topFrameDomain) == StorageAccessPromptWasShown::Yes); |
846 | #endif |
847 | setUserInteraction(subFrameDomain, true, WallTime::now()); |
848 | } |
849 | |
850 | RunLoop::main().dispatch([subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, store = makeRef(store()), completionHandler = WTFMove(completionHandler)]() mutable { |
851 | store->callGrantStorageAccessHandler(subFrameDomain, topFrameDomain, frameID, pageID, [completionHandler = WTFMove(completionHandler), store = store.copyRef()](StorageAccessWasGranted wasGranted) mutable { |
852 | store->statisticsQueue().dispatch([wasGranted, completionHandler = WTFMove(completionHandler)] () mutable { |
853 | completionHandler(wasGranted); |
854 | }); |
855 | }); |
856 | }); |
857 | |
858 | } |
859 | |
860 | void ResourceLoadStatisticsDatabaseStore::grandfatherDataForDomains(const HashSet<RegistrableDomain>& domains) |
861 | { |
862 | ASSERT(!RunLoop::isMain()); |
863 | |
864 | SQLiteStatement domainsToUpdateStatement(m_database, makeString("UPDATE ObservedDomains SET grandfathered = 1 WHERE registrableDomain IN (" , domainsToString(domains), ")" )); |
865 | if (domainsToUpdateStatement.prepare() != SQLITE_OK |
866 | || domainsToUpdateStatement.step() != SQLITE_DONE) { |
867 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::grandfatherDataForDomains failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
868 | ASSERT_NOT_REACHED(); |
869 | } |
870 | } |
871 | |
872 | Vector<RegistrableDomain> ResourceLoadStatisticsDatabaseStore::ensurePrevalentResourcesForDebugMode() |
873 | { |
874 | ASSERT(!RunLoop::isMain()); |
875 | |
876 | if (!debugModeEnabled()) |
877 | return { }; |
878 | |
879 | Vector<RegistrableDomain> primaryDomainsToBlock; |
880 | primaryDomainsToBlock.reserveInitialCapacity(2); |
881 | |
882 | ensureResourceStatisticsForRegistrableDomain(debugStaticPrevalentResource()); |
883 | setPrevalentResource(debugStaticPrevalentResource(), ResourceLoadPrevalence::High); |
884 | primaryDomainsToBlock.uncheckedAppend(debugStaticPrevalentResource()); |
885 | |
886 | if (!debugManualPrevalentResource().isEmpty()) { |
887 | ensureResourceStatisticsForRegistrableDomain(debugManualPrevalentResource()); |
888 | setPrevalentResource(debugManualPrevalentResource(), ResourceLoadPrevalence::High); |
889 | primaryDomainsToBlock.uncheckedAppend(debugManualPrevalentResource()); |
890 | #if !RELEASE_LOG_DISABLED |
891 | RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "Did set %{public}s as prevalent resource for the purposes of ITP Debug Mode." , debugManualPrevalentResource().string().utf8().data()); |
892 | #endif |
893 | } |
894 | |
895 | return primaryDomainsToBlock; |
896 | } |
897 | |
898 | void ResourceLoadStatisticsDatabaseStore::logFrameNavigation(const RegistrableDomain& targetDomain, const RegistrableDomain& topFrameDomain, const RegistrableDomain& sourceDomain, bool isRedirect, bool isMainFrame) |
899 | { |
900 | ASSERT(!RunLoop::isMain()); |
901 | |
902 | bool areTargetAndTopFrameDomainsSameSite = targetDomain == topFrameDomain; |
903 | bool areTargetAndSourceDomainsSameSite = targetDomain == sourceDomain; |
904 | |
905 | bool statisticsWereUpdated = false; |
906 | if (!isMainFrame && !(areTargetAndTopFrameDomainsSameSite || areTargetAndSourceDomainsSameSite)) { |
907 | auto targetResult = ensureResourceStatisticsForRegistrableDomain(targetDomain); |
908 | updateLastSeen(targetDomain, ResourceLoadStatistics::reduceTimeResolution(WallTime::now())); |
909 | |
910 | if (!relationshipExists(m_subframeUnderTopFrameDomainExists, targetResult.second, topFrameDomain)) { |
911 | insertDomainRelationship(m_subframeUnderTopFrameDomains, targetResult.second, topFrameDomain); |
912 | statisticsWereUpdated = true; |
913 | } |
914 | } |
915 | |
916 | if (isRedirect && !areTargetAndSourceDomainsSameSite) { |
917 | if (isMainFrame) { |
918 | auto redirectingDomainResult = ensureResourceStatisticsForRegistrableDomain(sourceDomain); |
919 | if (!relationshipExists(m_topFrameUniqueRedirectsToExists, redirectingDomainResult.second, targetDomain)) { |
920 | insertDomainRelationship(m_topFrameUniqueRedirectsTo, redirectingDomainResult.second, targetDomain); |
921 | statisticsWereUpdated = true; |
922 | } |
923 | |
924 | auto targetResult = ensureResourceStatisticsForRegistrableDomain(targetDomain); |
925 | if (!relationshipExists(m_topFrameUniqueRedirectsFromExists, targetResult.second, sourceDomain)) { |
926 | insertDomainRelationship(m_topFrameUniqueRedirectsFrom, targetResult.second, sourceDomain); |
927 | statisticsWereUpdated = true; |
928 | } |
929 | } else { |
930 | auto redirectingDomainResult = ensureResourceStatisticsForRegistrableDomain(sourceDomain); |
931 | if (!relationshipExists(m_subresourceUniqueRedirectsToExists, redirectingDomainResult.second, targetDomain)) { |
932 | insertDomainRelationship(m_subresourceUniqueRedirectsTo, redirectingDomainResult.second, targetDomain); |
933 | statisticsWereUpdated = true; |
934 | } |
935 | |
936 | auto targetResult = ensureResourceStatisticsForRegistrableDomain(targetDomain); |
937 | if (!relationshipExists(m_subresourceUniqueRedirectsFromExists, targetResult.second, sourceDomain)) { |
938 | insertDomainRelationship(m_subresourceUniqueRedirectsFrom, targetResult.second, sourceDomain); |
939 | statisticsWereUpdated = true; |
940 | } |
941 | } |
942 | } |
943 | |
944 | if (statisticsWereUpdated) |
945 | scheduleStatisticsProcessingRequestIfNecessary(); |
946 | } |
947 | |
948 | void ResourceLoadStatisticsDatabaseStore::logSubresourceLoading(const SubResourceDomain& targetDomain, const TopFrameDomain& topFrameDomain, WallTime lastSeen) |
949 | { |
950 | ASSERT(!RunLoop::isMain()); |
951 | |
952 | auto result = ensureResourceStatisticsForRegistrableDomain(targetDomain); |
953 | updateLastSeen(targetDomain, lastSeen); |
954 | |
955 | auto targetDomainID = result.second; |
956 | if (!relationshipExists(m_subresourceUnderTopFrameDomainExists, targetDomainID, topFrameDomain)) { |
957 | insertDomainRelationship(m_subresourceUnderTopFrameDomains, targetDomainID, topFrameDomain); |
958 | scheduleStatisticsProcessingRequestIfNecessary(); |
959 | } |
960 | } |
961 | |
962 | void ResourceLoadStatisticsDatabaseStore::logSubresourceRedirect(const RedirectedFromDomain& sourceDomain, const RedirectedToDomain& targetDomain) |
963 | { |
964 | ASSERT(!RunLoop::isMain()); |
965 | |
966 | auto sourceDomainResult = ensureResourceStatisticsForRegistrableDomain(sourceDomain); |
967 | auto targetDomainResult = ensureResourceStatisticsForRegistrableDomain(targetDomain); |
968 | |
969 | bool isNewRedirectToEntry = false; |
970 | if (!relationshipExists(m_subresourceUniqueRedirectsToExists, sourceDomainResult.second, targetDomain)) { |
971 | insertDomainRelationship(m_subresourceUniqueRedirectsTo, sourceDomainResult.second, targetDomain); |
972 | isNewRedirectToEntry = true; |
973 | } |
974 | |
975 | bool isNewRedirectFromEntry = false; |
976 | if (!relationshipExists(m_subresourceUniqueRedirectsFromExists, targetDomainResult.second, sourceDomain)) { |
977 | insertDomainRelationship(m_subresourceUniqueRedirectsFrom, targetDomainResult.second, sourceDomain); |
978 | isNewRedirectFromEntry = true; |
979 | } |
980 | |
981 | if (isNewRedirectToEntry || isNewRedirectFromEntry) |
982 | scheduleStatisticsProcessingRequestIfNecessary(); |
983 | } |
984 | |
985 | void ResourceLoadStatisticsDatabaseStore::logCrossSiteLoadWithLinkDecoration(const NavigatedFromDomain& fromDomain, const NavigatedToDomain& toDomain) |
986 | { |
987 | ASSERT(!RunLoop::isMain()); |
988 | ASSERT(fromDomain != toDomain); |
989 | |
990 | auto fromDomainResult = ensureResourceStatisticsForRegistrableDomain(fromDomain); |
991 | |
992 | if (!relationshipExists(m_topFrameLinkDecorationsFromExists, fromDomainResult.second, toDomain)) { |
993 | insertDomainRelationship(m_topFrameLinkDecorationsFrom, fromDomainResult.second, toDomain); |
994 | scheduleStatisticsProcessingRequestIfNecessary(); |
995 | } |
996 | } |
997 | |
998 | void ResourceLoadStatisticsDatabaseStore::setUserInteraction(const RegistrableDomain& domain, bool hadUserInteraction, WallTime mostRecentInteraction) |
999 | { |
1000 | ASSERT(!RunLoop::isMain()); |
1001 | |
1002 | if (m_mostRecentUserInteractionStatement.bindInt(1, hadUserInteraction) |
1003 | || m_mostRecentUserInteractionStatement.bindDouble(2, mostRecentInteraction.secondsSinceEpoch().value()) != SQLITE_OK |
1004 | || m_mostRecentUserInteractionStatement.bindText(3, domain.string()) != SQLITE_OK |
1005 | || m_mostRecentUserInteractionStatement.step() != SQLITE_DONE) { |
1006 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::setUserInteraction, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1007 | ASSERT_NOT_REACHED(); |
1008 | return; |
1009 | } |
1010 | |
1011 | int resetResult = m_mostRecentUserInteractionStatement.reset(); |
1012 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1013 | } |
1014 | |
1015 | void ResourceLoadStatisticsDatabaseStore::logUserInteraction(const TopFrameDomain& domain) |
1016 | { |
1017 | ASSERT(!RunLoop::isMain()); |
1018 | |
1019 | ensureResourceStatisticsForRegistrableDomain(domain); |
1020 | setUserInteraction(domain, true, WallTime::now()); |
1021 | } |
1022 | |
1023 | void ResourceLoadStatisticsDatabaseStore::clearUserInteraction(const RegistrableDomain& domain) |
1024 | { |
1025 | ASSERT(!RunLoop::isMain()); |
1026 | |
1027 | auto targetResult = ensureResourceStatisticsForRegistrableDomain(domain); |
1028 | setUserInteraction(domain, false, { }); |
1029 | |
1030 | SQLiteStatement removeStorageAccess(m_database, makeString("DELETE FROM StorageAccessUnderTopFrameDomains WHERE domainID = " , String::number(targetResult.second))); |
1031 | if (removeStorageAccess.prepare() != SQLITE_OK |
1032 | || removeStorageAccess.step() != SQLITE_DONE) { |
1033 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::logUserInteraction failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1034 | ASSERT_NOT_REACHED(); |
1035 | } |
1036 | } |
1037 | |
1038 | bool ResourceLoadStatisticsDatabaseStore::hasHadUserInteraction(const RegistrableDomain& domain, OperatingDatesWindow operatingDatesWindow) |
1039 | { |
1040 | ASSERT(!RunLoop::isMain()); |
1041 | |
1042 | if (m_hadUserInteractionStatement.bindText(1, domain.string()) != SQLITE_OK |
1043 | || m_hadUserInteractionStatement.step() != SQLITE_DONE) { |
1044 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::m_updatePrevalentResourceStatement failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1045 | ASSERT_NOT_REACHED(); |
1046 | return false; |
1047 | } |
1048 | |
1049 | bool hadUserInteraction = !!m_hadUserInteractionStatement.getColumnInt(0); |
1050 | if (!hadUserInteraction) |
1051 | return false; |
1052 | |
1053 | WallTime mostRecentUserInteractionTime = WallTime::fromRawSeconds(m_hadUserInteractionStatement.getColumnDouble(1)); |
1054 | |
1055 | if (hasStatisticsExpired(mostRecentUserInteractionTime, operatingDatesWindow)) { |
1056 | // Drop privacy sensitive data because we no longer need it. |
1057 | // Set timestamp to 0 so that statistics merge will know |
1058 | // it has been reset as opposed to its default -1. |
1059 | clearUserInteraction(domain); |
1060 | hadUserInteraction = false; |
1061 | } |
1062 | |
1063 | return hadUserInteraction; |
1064 | } |
1065 | |
1066 | void ResourceLoadStatisticsDatabaseStore::setPrevalentResource(const RegistrableDomain& domain, ResourceLoadPrevalence newPrevalence) |
1067 | { |
1068 | ASSERT(!RunLoop::isMain()); |
1069 | if (shouldSkip(domain)) |
1070 | return; |
1071 | |
1072 | if (m_updatePrevalentResourceStatement.bindInt(1, 1) != SQLITE_OK |
1073 | || m_updatePrevalentResourceStatement.bindText(2, domain.string()) != SQLITE_OK |
1074 | || m_updatePrevalentResourceStatement.step() != SQLITE_DONE) { |
1075 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::m_updatePrevalentResourceStatement failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1076 | ASSERT_NOT_REACHED(); |
1077 | return; |
1078 | } |
1079 | |
1080 | int resetResult = m_updatePrevalentResourceStatement.reset(); |
1081 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1082 | |
1083 | if (newPrevalence == ResourceLoadPrevalence::VeryHigh) { |
1084 | if (m_updateVeryPrevalentResourceStatement.bindInt(1, 1) != SQLITE_OK |
1085 | || m_updateVeryPrevalentResourceStatement.bindText(2, domain.string()) != SQLITE_OK |
1086 | || m_updateVeryPrevalentResourceStatement.step() != SQLITE_DONE) { |
1087 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::m_updateVeryPrevalentResourceStatement failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1088 | ASSERT_NOT_REACHED(); |
1089 | return; |
1090 | } |
1091 | |
1092 | int resetResult = m_updateVeryPrevalentResourceStatement.reset(); |
1093 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1094 | } |
1095 | |
1096 | StdSet<unsigned> nonPrevalentRedirectionSources; |
1097 | recursivelyFindNonPrevalentDomainsThatRedirectedToThisDomain(domainID(domain), nonPrevalentRedirectionSources, 0); |
1098 | setDomainsAsPrevalent(WTFMove(nonPrevalentRedirectionSources)); |
1099 | } |
1100 | |
1101 | void ResourceLoadStatisticsDatabaseStore::setDomainsAsPrevalent(StdSet<unsigned>&& domains) |
1102 | { |
1103 | ASSERT(!RunLoop::isMain()); |
1104 | |
1105 | SQLiteStatement domainsToUpdateStatement(m_database, makeString("UPDATE ObservedDomains SET isPrevalent = 1 WHERE domainID IN (" , buildList(WTF::IteratorRange<StdSet<unsigned>::iterator>(domains.begin(), domains.end())), ")" )); |
1106 | if (domainsToUpdateStatement.prepare() != SQLITE_OK |
1107 | || domainsToUpdateStatement.step() != SQLITE_DONE) { |
1108 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::setDomainsAsPrevalent failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1109 | ASSERT_NOT_REACHED(); |
1110 | return; |
1111 | } |
1112 | } |
1113 | |
1114 | String ResourceLoadStatisticsDatabaseStore::dumpResourceLoadStatistics() const |
1115 | { |
1116 | ASSERT(!RunLoop::isMain()); |
1117 | |
1118 | // FIXME(195088): Implement SQLite-based dumping routines. |
1119 | ASSERT_NOT_REACHED(); |
1120 | return { }; |
1121 | } |
1122 | |
1123 | bool ResourceLoadStatisticsDatabaseStore::predicateValueForDomain(WebCore::SQLiteStatement& predicateStatement, const RegistrableDomain& domain) const |
1124 | { |
1125 | ASSERT(!RunLoop::isMain()); |
1126 | |
1127 | if (predicateStatement.bindText(1, domain.string()) != SQLITE_OK |
1128 | || predicateStatement.step() != SQLITE_DONE) { |
1129 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::predicateValueForDomain failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1130 | ASSERT_NOT_REACHED(); |
1131 | return false; |
1132 | } |
1133 | |
1134 | bool result = !!predicateStatement.getColumnInt(0); |
1135 | int resetResult = predicateStatement.reset(); |
1136 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1137 | return result; |
1138 | } |
1139 | |
1140 | bool ResourceLoadStatisticsDatabaseStore::isPrevalentResource(const RegistrableDomain& domain) const |
1141 | { |
1142 | ASSERT(!RunLoop::isMain()); |
1143 | |
1144 | if (shouldSkip(domain)) |
1145 | return false; |
1146 | |
1147 | return predicateValueForDomain(m_isPrevalentResourceStatement, domain); |
1148 | } |
1149 | |
1150 | bool ResourceLoadStatisticsDatabaseStore::isVeryPrevalentResource(const RegistrableDomain& domain) const |
1151 | { |
1152 | ASSERT(!RunLoop::isMain()); |
1153 | |
1154 | if (shouldSkip(domain)) |
1155 | return false; |
1156 | |
1157 | return predicateValueForDomain(m_isVeryPrevalentResourceStatement, domain); |
1158 | } |
1159 | |
1160 | bool ResourceLoadStatisticsDatabaseStore::isRegisteredAsSubresourceUnder(const SubResourceDomain& subresourceDomain, const TopFrameDomain& topFrameDomain) const |
1161 | { |
1162 | ASSERT(!RunLoop::isMain()); |
1163 | |
1164 | return relationshipExists(m_subresourceUnderTopFrameDomainExists, domainID(subresourceDomain), topFrameDomain); |
1165 | } |
1166 | |
1167 | bool ResourceLoadStatisticsDatabaseStore::isRegisteredAsSubFrameUnder(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain) const |
1168 | { |
1169 | ASSERT(!RunLoop::isMain()); |
1170 | |
1171 | return relationshipExists(m_subframeUnderTopFrameDomainExists, domainID(subFrameDomain), topFrameDomain); |
1172 | } |
1173 | |
1174 | bool ResourceLoadStatisticsDatabaseStore::isRegisteredAsRedirectingTo(const RedirectedFromDomain& redirectedFromDomain, const RedirectedToDomain& redirectedToDomain) const |
1175 | { |
1176 | ASSERT(!RunLoop::isMain()); |
1177 | |
1178 | return relationshipExists(m_subresourceUniqueRedirectsToExists, domainID(redirectedFromDomain), redirectedToDomain); |
1179 | } |
1180 | |
1181 | void ResourceLoadStatisticsDatabaseStore::clearPrevalentResource(const RegistrableDomain& domain) |
1182 | { |
1183 | ASSERT(!RunLoop::isMain()); |
1184 | |
1185 | ensureResourceStatisticsForRegistrableDomain(domain); |
1186 | |
1187 | if (m_clearPrevalentResourceStatement.bindText(1, domain.string()) != SQLITE_OK |
1188 | || m_clearPrevalentResourceStatement.step() != SQLITE_DONE) { |
1189 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::clearPrevalentResource, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1190 | ASSERT_NOT_REACHED(); |
1191 | return; |
1192 | } |
1193 | |
1194 | int resetResult = m_clearPrevalentResourceStatement.reset(); |
1195 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1196 | } |
1197 | |
1198 | void ResourceLoadStatisticsDatabaseStore::setGrandfathered(const RegistrableDomain& domain, bool value) |
1199 | { |
1200 | ASSERT(!RunLoop::isMain()); |
1201 | |
1202 | ensureResourceStatisticsForRegistrableDomain(domain); |
1203 | |
1204 | if (m_updateGrandfatheredStatement.bindInt(1, value) != SQLITE_OK |
1205 | || m_updateGrandfatheredStatement.bindText(1, domain.string()) != SQLITE_OK |
1206 | || m_updateGrandfatheredStatement.step() != SQLITE_DONE) { |
1207 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::setGrandfathered failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1208 | ASSERT_NOT_REACHED(); |
1209 | return; |
1210 | } |
1211 | |
1212 | int resetResult = m_updateGrandfatheredStatement.reset(); |
1213 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1214 | } |
1215 | |
1216 | bool ResourceLoadStatisticsDatabaseStore::isGrandfathered(const RegistrableDomain& domain) const |
1217 | { |
1218 | ASSERT(!RunLoop::isMain()); |
1219 | |
1220 | return predicateValueForDomain(m_isGrandfatheredStatement, domain); |
1221 | } |
1222 | |
1223 | void ResourceLoadStatisticsDatabaseStore::setSubframeUnderTopFrameDomain(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain) |
1224 | { |
1225 | ASSERT(!RunLoop::isMain()); |
1226 | |
1227 | auto result = ensureResourceStatisticsForRegistrableDomain(subFrameDomain); |
1228 | |
1229 | // For consistency, make sure we also have a statistics entry for the top frame domain. |
1230 | ensureResourceStatisticsForRegistrableDomain(topFrameDomain); |
1231 | |
1232 | insertDomainRelationship(m_subframeUnderTopFrameDomains, result.second, topFrameDomain); |
1233 | } |
1234 | |
1235 | void ResourceLoadStatisticsDatabaseStore::setSubresourceUnderTopFrameDomain(const SubResourceDomain& subresourceDomain, const TopFrameDomain& topFrameDomain) |
1236 | { |
1237 | ASSERT(!RunLoop::isMain()); |
1238 | |
1239 | auto result = ensureResourceStatisticsForRegistrableDomain(subresourceDomain); |
1240 | |
1241 | // For consistency, make sure we also have a statistics entry for the top frame domain. |
1242 | ensureResourceStatisticsForRegistrableDomain(topFrameDomain); |
1243 | |
1244 | insertDomainRelationship(m_subresourceUnderTopFrameDomains, result.second, topFrameDomain); |
1245 | } |
1246 | |
1247 | void ResourceLoadStatisticsDatabaseStore::setSubresourceUniqueRedirectTo(const SubResourceDomain& subresourceDomain, const RedirectDomain& redirectDomain) |
1248 | { |
1249 | ASSERT(!RunLoop::isMain()); |
1250 | |
1251 | auto result = ensureResourceStatisticsForRegistrableDomain(subresourceDomain); |
1252 | |
1253 | // For consistency, make sure we also have a statistics entry for the redirect domain. |
1254 | ensureResourceStatisticsForRegistrableDomain(redirectDomain); |
1255 | |
1256 | insertDomainRelationship(m_subresourceUniqueRedirectsTo, result.second, redirectDomain); |
1257 | } |
1258 | |
1259 | void ResourceLoadStatisticsDatabaseStore::setSubresourceUniqueRedirectFrom(const SubResourceDomain& subresourceDomain, const RedirectDomain& redirectDomain) |
1260 | { |
1261 | ASSERT(!RunLoop::isMain()); |
1262 | |
1263 | auto result = ensureResourceStatisticsForRegistrableDomain(subresourceDomain); |
1264 | |
1265 | // For consistency, make sure we also have a statistics entry for the redirect domain. |
1266 | ensureResourceStatisticsForRegistrableDomain(redirectDomain); |
1267 | |
1268 | insertDomainRelationship(m_subresourceUniqueRedirectsFrom, result.second, redirectDomain); |
1269 | } |
1270 | |
1271 | void ResourceLoadStatisticsDatabaseStore::setTopFrameUniqueRedirectTo(const TopFrameDomain& topFrameDomain, const RedirectDomain& redirectDomain) |
1272 | { |
1273 | ASSERT(!RunLoop::isMain()); |
1274 | |
1275 | auto result = ensureResourceStatisticsForRegistrableDomain(topFrameDomain); |
1276 | |
1277 | // For consistency, make sure we also have a statistics entry for the redirect domain. |
1278 | ensureResourceStatisticsForRegistrableDomain(redirectDomain); |
1279 | |
1280 | insertDomainRelationship(m_topFrameUniqueRedirectsTo, result.second, redirectDomain); |
1281 | } |
1282 | |
1283 | void ResourceLoadStatisticsDatabaseStore::setTopFrameUniqueRedirectFrom(const TopFrameDomain& topFrameDomain, const RedirectDomain& redirectDomain) |
1284 | { |
1285 | ASSERT(!RunLoop::isMain()); |
1286 | |
1287 | auto result = ensureResourceStatisticsForRegistrableDomain(topFrameDomain); |
1288 | |
1289 | // For consistency, make sure we also have a statistics entry for the redirect domain. |
1290 | ensureResourceStatisticsForRegistrableDomain(redirectDomain); |
1291 | |
1292 | insertDomainRelationship(m_topFrameUniqueRedirectsFrom, result.second, redirectDomain); |
1293 | } |
1294 | |
1295 | std::pair<ResourceLoadStatisticsDatabaseStore::AddedRecord, unsigned> ResourceLoadStatisticsDatabaseStore::ensureResourceStatisticsForRegistrableDomain(const RegistrableDomain& domain) |
1296 | { |
1297 | ASSERT(!RunLoop::isMain()); |
1298 | |
1299 | if (m_domainIDFromStringStatement.bindText(1, domain.string()) != SQLITE_OK) { |
1300 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::createSchema failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1301 | ASSERT_NOT_REACHED(); |
1302 | return { AddedRecord::No, 0 }; |
1303 | } |
1304 | |
1305 | if (m_domainIDFromStringStatement.step() == SQLITE_ROW) { |
1306 | unsigned domainID = m_domainIDFromStringStatement.getColumnInt(0); |
1307 | |
1308 | int resetResult = m_domainIDFromStringStatement.reset(); |
1309 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1310 | return { AddedRecord::No, domainID }; |
1311 | } |
1312 | |
1313 | int resetResult = m_domainIDFromStringStatement.reset(); |
1314 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1315 | |
1316 | ResourceLoadStatistics newObservation(domain); |
1317 | insertObservedDomain(newObservation); |
1318 | |
1319 | return { AddedRecord::Yes, domainID(domain) }; |
1320 | } |
1321 | |
1322 | void ResourceLoadStatisticsDatabaseStore::clear(CompletionHandler<void()>&& completionHandler) |
1323 | { |
1324 | ASSERT(!RunLoop::isMain()); |
1325 | |
1326 | clearOperatingDates(); |
1327 | |
1328 | auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler)); |
1329 | |
1330 | removeAllStorageAccess([callbackAggregator = callbackAggregator.copyRef()] { }); |
1331 | |
1332 | auto primaryDomainsToBlock = ensurePrevalentResourcesForDebugMode(); |
1333 | updateCookieBlockingForDomains(primaryDomainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { }); |
1334 | } |
1335 | |
1336 | ResourceLoadStatisticsDatabaseStore::CookieTreatmentResult ResourceLoadStatisticsDatabaseStore::cookieTreatmentForOrigin(const RegistrableDomain& domain) const |
1337 | { |
1338 | ASSERT(!RunLoop::isMain()); |
1339 | |
1340 | SQLiteStatement statement(m_database, makeString("SELECT isPrevalent, hadUserInteraction FROM ObservedDomains WHERE registrableDomain = " , domain.string())); |
1341 | if (statement.prepare() != SQLITE_OK |
1342 | || statement.step() != SQLITE_ROW) { |
1343 | return CookieTreatmentResult::Allow; |
1344 | } |
1345 | |
1346 | bool isPrevalent = !!statement.getColumnInt(0); |
1347 | if (!isPrevalent) |
1348 | return CookieTreatmentResult::Allow; |
1349 | |
1350 | bool hadUserInteraction = statement.getColumnInt(1) ? true : false; |
1351 | return hadUserInteraction ? CookieTreatmentResult::BlockAndKeep : CookieTreatmentResult::BlockAndPurge; |
1352 | } |
1353 | |
1354 | StorageAccessPromptWasShown ResourceLoadStatisticsDatabaseStore::hasUserGrantedStorageAccessThroughPrompt(unsigned requestingDomainID, const RegistrableDomain& firstPartyDomain) const |
1355 | { |
1356 | ASSERT(!RunLoop::isMain()); |
1357 | |
1358 | auto firstPartyPrimaryDomainID = domainID(firstPartyDomain); |
1359 | |
1360 | SQLiteStatement statement(m_database, makeString("SELECT COUNT(*) FROM StorageAccessUnderTopFrameDomains WHERE domainID = " , String::number(requestingDomainID), " AND topLevelDomainID = " , String::number(firstPartyPrimaryDomainID))); |
1361 | if (statement.prepare() != SQLITE_OK |
1362 | || statement.step() != SQLITE_ROW) |
1363 | return StorageAccessPromptWasShown::No; |
1364 | |
1365 | return !statement.getColumnInt(0) ? StorageAccessPromptWasShown::Yes : StorageAccessPromptWasShown::No; |
1366 | } |
1367 | |
1368 | Vector<RegistrableDomain> ResourceLoadStatisticsDatabaseStore::domainsToBlock() const |
1369 | { |
1370 | ASSERT(!RunLoop::isMain()); |
1371 | |
1372 | Vector<RegistrableDomain> results; |
1373 | SQLiteStatement statement(m_database, "SELECT registrableDomain FROM ObservedDomains WHERE isPrevalent = 1"_s ); |
1374 | if (statement.prepare() != SQLITE_OK) |
1375 | return results; |
1376 | |
1377 | while (statement.step() == SQLITE_ROW) |
1378 | results.append(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(statement.getColumnText(0))); |
1379 | |
1380 | return results; |
1381 | } |
1382 | |
1383 | void ResourceLoadStatisticsDatabaseStore::updateCookieBlocking(CompletionHandler<void()>&& completionHandler) |
1384 | { |
1385 | ASSERT(!RunLoop::isMain()); |
1386 | |
1387 | auto domainsToBlock = this->domainsToBlock(); |
1388 | |
1389 | if (domainsToBlock.isEmpty()) { |
1390 | completionHandler(); |
1391 | return; |
1392 | } |
1393 | |
1394 | if (debugLoggingEnabled() && !domainsToBlock.isEmpty()) |
1395 | debugLogDomainsInBatches("block" , domainsToBlock); |
1396 | |
1397 | RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), store = makeRef(store()), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable { |
1398 | store->callUpdatePrevalentDomainsToBlockCookiesForHandler(domainsToBlock, [weakThis = WTFMove(weakThis), store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable { |
1399 | store->statisticsQueue().dispatch([weakThis = WTFMove(weakThis), completionHandler = WTFMove(completionHandler)]() mutable { |
1400 | completionHandler(); |
1401 | if (!weakThis) |
1402 | return; |
1403 | #if !RELEASE_LOG_DISABLED |
1404 | RELEASE_LOG_INFO_IF(weakThis->debugLoggingEnabled(), ResourceLoadStatisticsDebug, "Done updating cookie blocking." ); |
1405 | #endif |
1406 | }); |
1407 | }); |
1408 | }); |
1409 | } |
1410 | |
1411 | Vector<ResourceLoadStatisticsDatabaseStore::PrevalentDomainData> ResourceLoadStatisticsDatabaseStore::prevalentDomains() const |
1412 | { |
1413 | ASSERT(!RunLoop::isMain()); |
1414 | |
1415 | Vector<PrevalentDomainData> results; |
1416 | SQLiteStatement statement(m_database, "SELECT domainID, registrableDomain, mostRecentUserInteractionTime, hadUserInteraction, grandfathered FROM ObservedDomains WHERE isPrevalent = 1"_s ); |
1417 | if (statement.prepare() != SQLITE_OK) |
1418 | return results; |
1419 | |
1420 | while (statement.step() == SQLITE_ROW) { |
1421 | results.append({ static_cast<unsigned>(statement.getColumnInt(0)) |
1422 | , RegistrableDomain::uncheckedCreateFromRegistrableDomainString(statement.getColumnText(1)) |
1423 | , WallTime::fromRawSeconds(statement.getColumnDouble(2)) |
1424 | , statement.getColumnInt(3) ? true : false |
1425 | , statement.getColumnInt(4) ? true : false |
1426 | }); |
1427 | } |
1428 | |
1429 | return results; |
1430 | } |
1431 | |
1432 | Vector<unsigned> ResourceLoadStatisticsDatabaseStore::findExpiredUserInteractions() const |
1433 | { |
1434 | ASSERT(!RunLoop::isMain()); |
1435 | |
1436 | Vector<unsigned> results; |
1437 | Optional<Seconds> expirationDateTime = statisticsEpirationTime(); |
1438 | if (!expirationDateTime) |
1439 | return results; |
1440 | |
1441 | if (m_findExpiredUserInteractionStatement.bindDouble(1, expirationDateTime.value().value()) != SQLITE_OK) |
1442 | return results; |
1443 | |
1444 | while (m_findExpiredUserInteractionStatement.step() == SQLITE_ROW) |
1445 | results.append(m_findExpiredUserInteractionStatement.getColumnInt(0)); |
1446 | |
1447 | int resetResult = m_findExpiredUserInteractionStatement.reset(); |
1448 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1449 | |
1450 | return results; |
1451 | } |
1452 | |
1453 | void ResourceLoadStatisticsDatabaseStore::clearExpiredUserInteractions() |
1454 | { |
1455 | ASSERT(!RunLoop::isMain()); |
1456 | |
1457 | auto expiredRecords = findExpiredUserInteractions(); |
1458 | if (expiredRecords.isEmpty()) |
1459 | return; |
1460 | |
1461 | auto expiredRecordIDs = buildList(WTF::IteratorRange<Vector<unsigned>::iterator>(expiredRecords.begin(), expiredRecords.end())); |
1462 | |
1463 | SQLiteStatement clearExpiredInteraction(m_database, makeString("UPDATE ObservedDomains SET mostRecentUserInteractionTime = 0, hadUserInteraction = 1 WHERE domainID IN (" , expiredRecordIDs, ")" )); |
1464 | if (clearExpiredInteraction.prepare() != SQLITE_OK) |
1465 | return; |
1466 | |
1467 | SQLiteStatement removeStorageAccess(m_database, makeString("DELETE FROM StorageAccessUnderTopFrameDomains " , expiredRecordIDs, ")" )); |
1468 | if (removeStorageAccess.prepare() != SQLITE_OK) |
1469 | return; |
1470 | |
1471 | if (clearExpiredInteraction.step() != SQLITE_DONE |
1472 | || removeStorageAccess.step() != SQLITE_DONE) { |
1473 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::logUserInteraction failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1474 | ASSERT_NOT_REACHED(); |
1475 | } |
1476 | } |
1477 | |
1478 | void ResourceLoadStatisticsDatabaseStore::clearGrandfathering(Vector<unsigned>&& domainIDsToClear) |
1479 | { |
1480 | ASSERT(!RunLoop::isMain()); |
1481 | |
1482 | if (domainIDsToClear.isEmpty()) |
1483 | return; |
1484 | |
1485 | auto listToClear = buildList(WTF::IteratorRange<Vector<unsigned>::iterator>(domainIDsToClear.begin(), domainIDsToClear.end())); |
1486 | |
1487 | SQLiteStatement clearGrandfatheringStatement(m_database, makeString("UPDATE ObservedDomains SET grandfathered = 0 WHERE domainID IN (" , listToClear, ")" )); |
1488 | if (clearGrandfatheringStatement.prepare() != SQLITE_OK) |
1489 | return; |
1490 | |
1491 | if (clearGrandfatheringStatement.step() != SQLITE_DONE) { |
1492 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::clearGrandfathering failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1493 | ASSERT_NOT_REACHED(); |
1494 | } |
1495 | } |
1496 | |
1497 | bool ResourceLoadStatisticsDatabaseStore::shouldRemoveAllWebsiteDataFor(const PrevalentDomainData& resourceStatistic, bool shouldCheckForGrandfathering) const |
1498 | { |
1499 | return !resourceStatistic.hadUserInteraction && (!shouldCheckForGrandfathering || !resourceStatistic.grandfathered); |
1500 | } |
1501 | |
1502 | bool ResourceLoadStatisticsDatabaseStore::shouldRemoveAllButCookiesFor(const PrevalentDomainData& resourceStatistic, bool shouldCheckForGrandfathering) const |
1503 | { |
1504 | UNUSED_PARAM(resourceStatistic); |
1505 | UNUSED_PARAM(shouldCheckForGrandfathering); |
1506 | return false; |
1507 | } |
1508 | |
1509 | HashMap<RegistrableDomain, WebsiteDataToRemove> ResourceLoadStatisticsDatabaseStore::registrableDomainsToRemoveWebsiteDataFor() |
1510 | { |
1511 | ASSERT(!RunLoop::isMain()); |
1512 | |
1513 | bool shouldCheckForGrandfathering = endOfGrandfatheringTimestamp() > WallTime::now(); |
1514 | bool shouldClearGrandfathering = !shouldCheckForGrandfathering && endOfGrandfatheringTimestamp(); |
1515 | |
1516 | if (shouldClearGrandfathering) |
1517 | clearEndOfGrandfatheringTimeStamp(); |
1518 | |
1519 | clearExpiredUserInteractions(); |
1520 | |
1521 | HashMap<RegistrableDomain, WebsiteDataToRemove> domainsToRemoveWebsiteDataFor; |
1522 | |
1523 | Vector<PrevalentDomainData> prevalentDomains = this->prevalentDomains(); |
1524 | Vector<unsigned> domainIDsToClearGrandfathering; |
1525 | for (auto& statistic : prevalentDomains) { |
1526 | if (shouldRemoveAllWebsiteDataFor(statistic, shouldCheckForGrandfathering)) |
1527 | domainsToRemoveWebsiteDataFor.add(statistic.registerableDomain, WebsiteDataToRemove::All); |
1528 | else if (shouldRemoveAllButCookiesFor(statistic, shouldCheckForGrandfathering)) |
1529 | domainsToRemoveWebsiteDataFor.add(statistic.registerableDomain, WebsiteDataToRemove::AllButCookies); |
1530 | |
1531 | if (shouldClearGrandfathering && statistic.grandfathered) |
1532 | domainIDsToClearGrandfathering.append(statistic.domainID); |
1533 | } |
1534 | |
1535 | clearGrandfathering(WTFMove(domainIDsToClearGrandfathering)); |
1536 | |
1537 | return domainsToRemoveWebsiteDataFor; |
1538 | } |
1539 | |
1540 | void ResourceLoadStatisticsDatabaseStore::pruneStatisticsIfNeeded() |
1541 | { |
1542 | ASSERT(!RunLoop::isMain()); |
1543 | |
1544 | unsigned count = 0; |
1545 | if (m_observedDomainCount.step() == SQLITE_ROW) |
1546 | count = m_observedDomainCount.getColumnInt(0); |
1547 | |
1548 | int resetResult = m_observedDomainCount.reset(); |
1549 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1550 | |
1551 | if (count <= parameters().maxStatisticsEntries) |
1552 | return; |
1553 | |
1554 | ASSERT(parameters().pruneEntriesDownTo <= parameters().maxStatisticsEntries); |
1555 | |
1556 | size_t countLeftToPrune = count - parameters().pruneEntriesDownTo; |
1557 | ASSERT(countLeftToPrune); |
1558 | |
1559 | SQLiteStatement recordsToPrune(m_database, makeString("SELECT domainID FROM ObservedDomains ORDER BY hadUserInteraction, isPrevalent, lastSeen DESC LIMIT " , String::number(countLeftToPrune))); |
1560 | if (recordsToPrune.prepare() != SQLITE_OK) { |
1561 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::pruneStatisticsIfNeeded failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1562 | ASSERT_NOT_REACHED(); |
1563 | return; |
1564 | } |
1565 | |
1566 | Vector<unsigned> entriesToPrune; |
1567 | while (recordsToPrune.step() == SQLITE_ROW) |
1568 | entriesToPrune.append(recordsToPrune.getColumnInt(0)); |
1569 | |
1570 | auto listToPrune = buildList(WTF::IteratorRange<Vector<unsigned>::iterator>(entriesToPrune.begin(), entriesToPrune.end())); |
1571 | |
1572 | SQLiteStatement pruneCommand(m_database, makeString("DELETE from ObservedDomains WHERE domainID IN (" , listToPrune, ")" )); |
1573 | if (pruneCommand.prepare() != SQLITE_OK |
1574 | || pruneCommand.step() != SQLITE_DONE) { |
1575 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::pruneStatisticsIfNeeded failed, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1576 | ASSERT_NOT_REACHED(); |
1577 | return; |
1578 | } |
1579 | } |
1580 | |
1581 | void ResourceLoadStatisticsDatabaseStore::updateLastSeen(const RegistrableDomain& domain, WallTime lastSeen) |
1582 | { |
1583 | ASSERT(!RunLoop::isMain()); |
1584 | |
1585 | if (m_updateLastSeenStatement.bindDouble(1, lastSeen.secondsSinceEpoch().value()) != SQLITE_OK |
1586 | || m_updateLastSeenStatement.bindText(2, domain.string()) != SQLITE_OK |
1587 | || m_updateLastSeenStatement.step() != SQLITE_DONE) { |
1588 | RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::updateLastSeen failed to bind, error message: %{public}s" , this, m_database.lastErrorMsg()); |
1589 | ASSERT_NOT_REACHED(); |
1590 | return; |
1591 | } |
1592 | |
1593 | int resetResult = m_updateLastSeenStatement.reset(); |
1594 | ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK); |
1595 | } |
1596 | |
1597 | void ResourceLoadStatisticsDatabaseStore::setLastSeen(const RegistrableDomain& domain, Seconds seconds) |
1598 | { |
1599 | ASSERT(!RunLoop::isMain()); |
1600 | |
1601 | ensureResourceStatisticsForRegistrableDomain(domain); |
1602 | updateLastSeen(domain, WallTime::fromRawSeconds(seconds.seconds())); |
1603 | } |
1604 | |
1605 | void ResourceLoadStatisticsDatabaseStore::setPrevalentResource(const RegistrableDomain& domain) |
1606 | { |
1607 | ASSERT(!RunLoop::isMain()); |
1608 | |
1609 | if (shouldSkip(domain)) |
1610 | return; |
1611 | |
1612 | ensureResourceStatisticsForRegistrableDomain(domain); |
1613 | setPrevalentResource(domain, ResourceLoadPrevalence::High); |
1614 | } |
1615 | |
1616 | void ResourceLoadStatisticsDatabaseStore::setVeryPrevalentResource(const RegistrableDomain& domain) |
1617 | { |
1618 | ASSERT(!RunLoop::isMain()); |
1619 | |
1620 | if (shouldSkip(domain)) |
1621 | return; |
1622 | |
1623 | ensureResourceStatisticsForRegistrableDomain(domain); |
1624 | setPrevalentResource(domain, ResourceLoadPrevalence::VeryHigh); |
1625 | } |
1626 | |
1627 | } // namespace WebKit |
1628 | |
1629 | #endif |
1630 | |