Resource Load Statistics: Add alternate classification method
[WebKit-https.git] / Source / WebKit2 / UIProcess / WebResourceLoadStatisticsStore.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. 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 "WebResourceLoadStatisticsStore.h"
28
29 #include "WebProcessMessages.h"
30 #include "WebProcessPool.h"
31 #include "WebProcessProxy.h"
32 #include "WebResourceLoadStatisticsStoreMessages.h"
33 #include "WebsiteDataFetchOption.h"
34 #include "WebsiteDataType.h"
35 #include <WebCore/KeyedCoding.h>
36 #include <WebCore/ResourceLoadObserver.h>
37 #include <WebCore/ResourceLoadStatistics.h>
38 #include <wtf/CurrentTime.h>
39 #include <wtf/MainThread.h>
40 #include <wtf/MathExtras.h>
41 #include <wtf/RunLoop.h>
42 #include <wtf/threads/BinarySemaphore.h>
43
44 using namespace WebCore;
45
46 namespace WebKit {
47
48 static auto minimumTimeBetweeenDataRecordsRemoval = 60;
49 static OptionSet<WebKit::WebsiteDataType> dataTypesToRemove;
50 static auto notifyPages = false;
51 static auto shouldClassifyResourcesBeforeDataRecordsRemoval = true;
52
53 Ref<WebResourceLoadStatisticsStore> WebResourceLoadStatisticsStore::create(const String& resourceLoadStatisticsDirectory)
54 {
55     return adoptRef(*new WebResourceLoadStatisticsStore(resourceLoadStatisticsDirectory));
56 }
57
58 WebResourceLoadStatisticsStore::WebResourceLoadStatisticsStore(const String& resourceLoadStatisticsDirectory)
59     : m_resourceLoadStatisticsStore(ResourceLoadStatisticsStore::create())
60     , m_statisticsQueue(WorkQueue::create("WebResourceLoadStatisticsStore Process Data Queue"))
61     , m_statisticsStoragePath(resourceLoadStatisticsDirectory)
62 {
63 }
64
65 WebResourceLoadStatisticsStore::~WebResourceLoadStatisticsStore()
66 {
67 }
68
69 void WebResourceLoadStatisticsStore::setNotifyPagesWhenDataRecordsWereScanned(bool always)
70 {
71     notifyPages = always;
72 }
73
74 void WebResourceLoadStatisticsStore::setShouldClassifyResourcesBeforeDataRecordsRemoval(bool value)
75 {
76     shouldClassifyResourcesBeforeDataRecordsRemoval = value;
77 }
78
79 void WebResourceLoadStatisticsStore::setMinimumTimeBetweeenDataRecordsRemoval(double seconds)
80 {
81     if (seconds >= 0)
82         minimumTimeBetweeenDataRecordsRemoval = seconds;
83 }
84
85 void WebResourceLoadStatisticsStore::classifyResource(ResourceLoadStatistics& resourceStatistic)
86 {
87     if (!resourceStatistic.isPrevalentResource
88         && m_resourceLoadStatisticsClassifier.hasPrevalentResourceCharacteristics(resourceStatistic))
89         resourceStatistic.isPrevalentResource = true;
90 }
91
92 void WebResourceLoadStatisticsStore::removeDataRecords()
93 {
94     if (m_dataRecordsRemovalPending)
95         return;
96
97     Vector<String> prevalentResourceDomains = coreStore().prevalentResourceDomainsWithoutUserInteraction();
98     if (!prevalentResourceDomains.size())
99         return;
100
101     double now = currentTime();
102     if (m_lastTimeDataRecordsWereRemoved
103         && now < m_lastTimeDataRecordsWereRemoved + minimumTimeBetweeenDataRecordsRemoval)
104         return;
105
106     m_dataRecordsRemovalPending = true;
107     m_lastTimeDataRecordsWereRemoved = now;
108
109     if (dataTypesToRemove.isEmpty()) {
110         dataTypesToRemove |= WebsiteDataType::Cookies;
111         dataTypesToRemove |= WebsiteDataType::DiskCache;
112         dataTypesToRemove |= WebsiteDataType::MemoryCache;
113         dataTypesToRemove |= WebsiteDataType::OfflineWebApplicationCache;
114         dataTypesToRemove |= WebsiteDataType::SessionStorage;
115         dataTypesToRemove |= WebsiteDataType::LocalStorage;
116         dataTypesToRemove |= WebsiteDataType::WebSQLDatabases;
117         dataTypesToRemove |= WebsiteDataType::IndexedDBDatabases;
118         dataTypesToRemove |= WebsiteDataType::MediaKeys;
119         dataTypesToRemove |= WebsiteDataType::HSTSCache;
120         dataTypesToRemove |= WebsiteDataType::SearchFieldRecentSearches;
121 #if ENABLE(NETSCAPE_PLUGIN_API)
122         dataTypesToRemove |= WebsiteDataType::PlugInData;
123 #endif
124 #if ENABLE(MEDIA_STREAM)
125         dataTypesToRemove |= WebsiteDataType::MediaDeviceIdentifier;
126 #endif
127     }
128
129     // Switch to the main thread to get the default website data store
130     RunLoop::main().dispatch([prevalentResourceDomains = WTFMove(prevalentResourceDomains), this] () mutable {
131         WebProcessProxy::deleteWebsiteDataForTopPrivatelyOwnedDomainsInAllPersistentDataStores(dataTypesToRemove, prevalentResourceDomains, notifyPages, [this]() mutable {
132             m_dataRecordsRemovalPending = false;
133         });
134     });
135 }
136
137 void WebResourceLoadStatisticsStore::processStatisticsAndDataRecords()
138 {
139     if (shouldClassifyResourcesBeforeDataRecordsRemoval) {
140         coreStore().processStatistics([this] (ResourceLoadStatistics& resourceStatistic) {
141             classifyResource(resourceStatistic);
142         });
143     }
144     removeDataRecords();
145     
146     auto encoder = coreStore().createEncoderFromData();
147     
148     writeEncoderToDisk(*encoder.get(), "full_browsing_session");
149 }
150
151 void WebResourceLoadStatisticsStore::resourceLoadStatisticsUpdated(const Vector<WebCore::ResourceLoadStatistics>& origins)
152 {
153     coreStore().mergeStatistics(origins);
154     processStatisticsAndDataRecords();
155 }
156
157 void WebResourceLoadStatisticsStore::setResourceLoadStatisticsEnabled(bool enabled)
158 {
159     if (enabled == m_resourceLoadStatisticsEnabled)
160         return;
161
162     m_resourceLoadStatisticsEnabled = enabled;
163
164     readDataFromDiskIfNeeded();
165 }
166
167 bool WebResourceLoadStatisticsStore::resourceLoadStatisticsEnabled() const
168 {
169     return m_resourceLoadStatisticsEnabled;
170 }
171
172
173 void WebResourceLoadStatisticsStore::registerSharedResourceLoadObserver()
174 {
175     ResourceLoadObserver::sharedObserver().setStatisticsStore(m_resourceLoadStatisticsStore.copyRef());
176     m_resourceLoadStatisticsStore->setNotificationCallback([this] {
177         if (m_resourceLoadStatisticsStore->isEmpty())
178             return;
179         processStatisticsAndDataRecords();
180     });
181 }
182
183 void WebResourceLoadStatisticsStore::readDataFromDiskIfNeeded()
184 {
185     if (!m_resourceLoadStatisticsEnabled)
186         return;
187
188     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
189         coreStore().clear();
190
191         auto decoder = createDecoderFromDisk("full_browsing_session");
192         if (!decoder)
193             return;
194
195         coreStore().readDataFromDecoder(*decoder);
196     });
197 }
198
199 void WebResourceLoadStatisticsStore::processWillOpenConnection(WebProcessProxy&, IPC::Connection& connection)
200 {
201     connection.addWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName(), m_statisticsQueue.get(), this);
202 }
203
204 void WebResourceLoadStatisticsStore::processDidCloseConnection(WebProcessProxy&, IPC::Connection& connection)
205 {
206     connection.removeWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName());
207 }
208
209 void WebResourceLoadStatisticsStore::applicationWillTerminate()
210 {
211     BinarySemaphore semaphore;
212     m_statisticsQueue->dispatch([this, &semaphore] {
213         // Make sure any ongoing work in our queue is finished before we terminate.
214         semaphore.signal();
215     });
216     semaphore.wait(WallTime::infinity());
217 }
218
219 String WebResourceLoadStatisticsStore::persistentStoragePath(const String& label) const
220 {
221     if (m_statisticsStoragePath.isEmpty())
222         return emptyString();
223
224     // TODO Decide what to call this file
225     return pathByAppendingComponent(m_statisticsStoragePath, label + "_resourceLog.plist");
226 }
227
228 void WebResourceLoadStatisticsStore::writeEncoderToDisk(KeyedEncoder& encoder, const String& label) const
229 {
230     RefPtr<SharedBuffer> rawData = encoder.finishEncoding();
231     if (!rawData)
232         return;
233
234     String resourceLog = persistentStoragePath(label);
235     if (resourceLog.isEmpty())
236         return;
237
238     if (!m_statisticsStoragePath.isEmpty())
239         makeAllDirectories(m_statisticsStoragePath);
240
241     auto handle = openFile(resourceLog, OpenForWrite);
242     if (!handle)
243         return;
244     
245     int64_t writtenBytes = writeToFile(handle, rawData->data(), rawData->size());
246     closeFile(handle);
247
248     if (writtenBytes != static_cast<int64_t>(rawData->size()))
249         WTFLogAlways("WebResourceLoadStatisticsStore: We only wrote %d out of %d bytes to disk", static_cast<unsigned>(writtenBytes), rawData->size());
250 }
251
252 std::unique_ptr<KeyedDecoder> WebResourceLoadStatisticsStore::createDecoderFromDisk(const String& label) const
253 {
254     String resourceLog = persistentStoragePath(label);
255     if (resourceLog.isEmpty())
256         return nullptr;
257
258     RefPtr<SharedBuffer> rawData = SharedBuffer::createWithContentsOfFile(resourceLog);
259     if (!rawData)
260         return nullptr;
261
262     return KeyedDecoder::decoder(reinterpret_cast<const uint8_t*>(rawData->data()), rawData->size());
263 }
264
265 } // namespace WebKit