Avoid copying ResourceLoadStatistics objects
[WebKit-https.git] / Source / WebCore / loader / ResourceLoadObserver.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 "ResourceLoadObserver.h"
28
29 #include "Document.h"
30 #include "Frame.h"
31 #include "Logging.h"
32 #include "MainFrame.h"
33 #include "NetworkStorageSession.h"
34 #include "Page.h"
35 #include "PlatformStrategies.h"
36 #include "PublicSuffix.h"
37 #include "ResourceLoadStatistics.h"
38 #include "ResourceLoadStatisticsStore.h"
39 #include "ResourceRequest.h"
40 #include "ResourceResponse.h"
41 #include "SecurityOrigin.h"
42 #include "Settings.h"
43 #include "SharedBuffer.h"
44 #include "URL.h"
45 #include <wtf/CrossThreadCopier.h>
46 #include <wtf/CurrentTime.h>
47 #include <wtf/NeverDestroyed.h>
48 #include <wtf/WorkQueue.h>
49 #include <wtf/text/StringBuilder.h>
50
51 namespace WebCore {
52
53 static Seconds timestampResolution { 1_h };
54
55 ResourceLoadObserver& ResourceLoadObserver::sharedObserver()
56 {
57     static NeverDestroyed<ResourceLoadObserver> resourceLoadObserver;
58     return resourceLoadObserver;
59 }
60
61 void ResourceLoadObserver::setStatisticsStore(Ref<ResourceLoadStatisticsStore>&& store)
62 {
63     if (m_store && m_queue)
64         m_queue = nullptr;
65     m_store = WTFMove(store);
66 }
67     
68 void ResourceLoadObserver::setStatisticsQueue(Ref<WTF::WorkQueue>&& queue)
69 {
70     ASSERT(!m_queue);
71     m_queue = WTFMove(queue);
72 }
73     
74 void ResourceLoadObserver::clearInMemoryStore()
75 {
76     if (!m_store)
77         return;
78     
79     ASSERT(m_queue);
80     m_queue->dispatch([this] {
81         m_store->clearInMemory();
82     });
83 }
84     
85 void ResourceLoadObserver::clearInMemoryAndPersistentStore()
86 {
87     if (!m_store)
88         return;
89     
90     ASSERT(m_queue);
91     m_queue->dispatch([this] {
92         m_store->clearInMemoryAndPersistent();
93     });
94 }
95
96 void ResourceLoadObserver::clearInMemoryAndPersistentStore(std::chrono::system_clock::time_point modifiedSince)
97 {
98     // For now, be conservative and clear everything regardless of modifiedSince
99     UNUSED_PARAM(modifiedSince);
100     clearInMemoryAndPersistentStore();
101 }
102
103 static inline bool is3xxRedirect(const ResourceResponse& response)
104 {
105     return response.httpStatusCode() >= 300 && response.httpStatusCode() <= 399;
106 }
107
108 bool ResourceLoadObserver::shouldLog(Page* page)
109 {
110     // FIXME: Err on the safe side until we have sorted out what to do in worker contexts
111     if (!page)
112         return false;
113
114     return Settings::resourceLoadStatisticsEnabled() && !page->usesEphemeralSession() && m_store;
115 }
116
117 void ResourceLoadObserver::logFrameNavigation(const Frame& frame, const Frame& topFrame, const ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
118 {
119     ASSERT(frame.document());
120     ASSERT(topFrame.document());
121     ASSERT(topFrame.page());
122     
123     if (!shouldLog(topFrame.page()))
124         return;
125
126     bool isRedirect = is3xxRedirect(redirectResponse);
127     bool isMainFrame = frame.isMainFrame();
128     auto& sourceURL = frame.document()->url();
129     auto& targetURL = newRequest.url();
130     auto& mainFrameURL = topFrame.document()->url();
131     
132     if (!targetURL.isValid() || !mainFrameURL.isValid())
133         return;
134
135     auto targetHost = targetURL.host();
136     auto mainFrameHost = mainFrameURL.host();
137
138     if (targetHost.isEmpty() || mainFrameHost.isEmpty() || targetHost == mainFrameHost || targetHost == sourceURL.host())
139         return;
140
141     auto targetPrimaryDomain = primaryDomain(targetURL);
142     auto mainFramePrimaryDomain = primaryDomain(mainFrameURL);
143     auto sourcePrimaryDomain = primaryDomain(sourceURL);
144     
145     if (targetPrimaryDomain == mainFramePrimaryDomain || targetPrimaryDomain == sourcePrimaryDomain)
146         return;
147     
148     ASSERT(m_queue);
149     m_queue->dispatch([this, isMainFrame, isRedirect, sourcePrimaryDomain = sourcePrimaryDomain.isolatedCopy(), mainFramePrimaryDomain = mainFramePrimaryDomain.isolatedCopy(), targetPrimaryDomain = targetPrimaryDomain.isolatedCopy()] {
150         bool shouldFireDataModificationHandler = false;
151         
152         {
153             auto locker = holdLock(m_store->statisticsLock());
154             ResourceLoadStatistics targetStatistics(targetPrimaryDomain);
155
156             // Always fire if we have previously removed data records for this domain
157             shouldFireDataModificationHandler = targetStatistics.dataRecordsRemoved > 0;
158
159             if (isMainFrame)
160                 targetStatistics.topFrameHasBeenNavigatedToBefore = true;
161             else {
162                 targetStatistics.subframeHasBeenLoadedBefore = true;
163
164                 auto subframeUnderTopFrameOriginsResult = targetStatistics.subframeUnderTopFrameOrigins.add(mainFramePrimaryDomain);
165                 if (subframeUnderTopFrameOriginsResult.isNewEntry)
166                     shouldFireDataModificationHandler = true;
167             }
168
169             if (isRedirect) {
170                 auto& redirectingOriginResourceStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(sourcePrimaryDomain);
171
172                 if (m_store->isPrevalentResource(targetPrimaryDomain))
173                     redirectingOriginResourceStatistics.redirectedToOtherPrevalentResourceOrigins.add(targetPrimaryDomain);
174
175                 if (isMainFrame) {
176                     ++targetStatistics.topFrameHasBeenRedirectedTo;
177                     ++redirectingOriginResourceStatistics.topFrameHasBeenRedirectedFrom;
178                 } else {
179                     ++targetStatistics.subframeHasBeenRedirectedTo;
180                     ++redirectingOriginResourceStatistics.subframeHasBeenRedirectedFrom;
181                     redirectingOriginResourceStatistics.subframeUniqueRedirectsTo.add(targetPrimaryDomain);
182
183                     ++targetStatistics.subframeSubResourceCount;
184                 }
185             } else {
186                 if (sourcePrimaryDomain.isNull() || sourcePrimaryDomain.isEmpty() || sourcePrimaryDomain == "nullOrigin") {
187                     if (isMainFrame)
188                         ++targetStatistics.topFrameInitialLoadCount;
189                     else
190                         ++targetStatistics.subframeSubResourceCount;
191                 } else {
192                     auto& sourceOriginResourceStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(sourcePrimaryDomain);
193
194                     if (isMainFrame) {
195                         ++sourceOriginResourceStatistics.topFrameHasBeenNavigatedFrom;
196                         ++targetStatistics.topFrameHasBeenNavigatedTo;
197                     } else {
198                         ++sourceOriginResourceStatistics.subframeHasBeenNavigatedFrom;
199                         ++targetStatistics.subframeHasBeenNavigatedTo;
200                     }
201                 }
202             }
203
204             m_store->setResourceStatisticsForPrimaryDomain(targetPrimaryDomain, WTFMove(targetStatistics));
205         } // Release lock
206         
207         if (shouldFireDataModificationHandler)
208             m_store->fireDataModificationHandler();
209     });
210 }
211     
212 void ResourceLoadObserver::logSubresourceLoading(const Frame* frame, const ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
213 {
214     ASSERT(frame->page());
215
216     if (!shouldLog(frame->page()))
217         return;
218
219     bool isRedirect = is3xxRedirect(redirectResponse);
220     const URL& sourceURL = redirectResponse.url();
221     const URL& targetURL = newRequest.url();
222     const URL& mainFrameURL = frame ? frame->mainFrame().document()->url() : URL();
223     
224     auto targetHost = targetURL.host();
225     auto mainFrameHost = mainFrameURL.host();
226
227     if (targetHost.isEmpty() || mainFrameHost.isEmpty() || targetHost == mainFrameHost || (isRedirect && targetHost == sourceURL.host()))
228         return;
229
230     auto targetPrimaryDomain = primaryDomain(targetURL);
231     auto mainFramePrimaryDomain = primaryDomain(mainFrameURL);
232     auto sourcePrimaryDomain = primaryDomain(sourceURL);
233     
234     if (targetPrimaryDomain == mainFramePrimaryDomain || (isRedirect && targetPrimaryDomain == sourcePrimaryDomain))
235         return;
236     
237     ASSERT(m_queue);
238     m_queue->dispatch([this, isRedirect, sourcePrimaryDomain = sourcePrimaryDomain.isolatedCopy(), mainFramePrimaryDomain = mainFramePrimaryDomain.isolatedCopy(), targetPrimaryDomain = targetPrimaryDomain.isolatedCopy()] {
239         bool shouldFireDataModificationHandler = false;
240         
241         {
242         auto locker = holdLock(m_store->statisticsLock());
243         auto& targetStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(targetPrimaryDomain);
244
245         // Always fire if we have previously removed data records for this domain
246         shouldFireDataModificationHandler = targetStatistics.dataRecordsRemoved > 0;
247
248         auto subresourceUnderTopFrameOriginsResult = targetStatistics.subresourceUnderTopFrameOrigins.add(mainFramePrimaryDomain);
249         if (subresourceUnderTopFrameOriginsResult.isNewEntry)
250             shouldFireDataModificationHandler = true;
251
252         if (isRedirect) {
253             auto& redirectingOriginStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(sourcePrimaryDomain);
254             
255             // We just inserted to the store, so we need to reget 'targetStatistics'
256             auto& updatedTargetStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(targetPrimaryDomain);
257
258             if (m_store->isPrevalentResource(targetPrimaryDomain))
259                 redirectingOriginStatistics.redirectedToOtherPrevalentResourceOrigins.add(targetPrimaryDomain);
260             
261             ++redirectingOriginStatistics.subresourceHasBeenRedirectedFrom;
262             ++updatedTargetStatistics.subresourceHasBeenRedirectedTo;
263
264             auto subresourceUniqueRedirectsToResult = redirectingOriginStatistics.subresourceUniqueRedirectsTo.add(targetPrimaryDomain);
265             if (subresourceUniqueRedirectsToResult.isNewEntry)
266                 shouldFireDataModificationHandler = true;
267
268             ++updatedTargetStatistics.subresourceHasBeenSubresourceCount;
269
270             auto totalVisited = std::max(m_originsVisitedMap.size(), 1U);
271             
272             updatedTargetStatistics.subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited = static_cast<double>(updatedTargetStatistics.subresourceHasBeenSubresourceCount) / totalVisited;
273         } else {
274             ++targetStatistics.subresourceHasBeenSubresourceCount;
275
276             auto totalVisited = std::max(m_originsVisitedMap.size(), 1U);
277             
278             targetStatistics.subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited = static_cast<double>(targetStatistics.subresourceHasBeenSubresourceCount) / totalVisited;
279         }
280         } // Release lock
281         
282         if (shouldFireDataModificationHandler)
283             m_store->fireDataModificationHandler();
284     });
285 }
286
287 void ResourceLoadObserver::logWebSocketLoading(const Frame* frame, const URL& targetURL)
288 {
289     // FIXME: Web sockets can run in detached frames. Decide how to count such connections.
290     // See LayoutTests/http/tests/websocket/construct-in-detached-frame.html
291     if (!frame)
292         return;
293
294     if (!shouldLog(frame->page()))
295         return;
296
297     auto& mainFrameURL = frame->mainFrame().document()->url();
298
299     auto targetHost = targetURL.host();
300     auto mainFrameHost = mainFrameURL.host();
301     
302     if (targetHost.isEmpty() || mainFrameHost.isEmpty() || targetHost == mainFrameHost)
303         return;
304     
305     auto targetPrimaryDomain = primaryDomain(targetURL);
306     auto mainFramePrimaryDomain = primaryDomain(mainFrameURL);
307     
308     if (targetPrimaryDomain == mainFramePrimaryDomain)
309         return;
310
311     ASSERT(m_queue);
312     m_queue->dispatch([this, targetPrimaryDomain = targetPrimaryDomain.isolatedCopy(), mainFramePrimaryDomain = mainFramePrimaryDomain.isolatedCopy()] {
313         bool shouldFireDataModificationHandler = false;
314         
315         {
316         auto locker = holdLock(m_store->statisticsLock());
317         auto& targetStatistics = m_store->ensureResourceStatisticsForPrimaryDomain(targetPrimaryDomain);
318
319         // Always fire if we have previously removed data records for this domain
320         shouldFireDataModificationHandler = targetStatistics.dataRecordsRemoved > 0;
321         
322         auto subresourceUnderTopFrameOriginsResult = targetStatistics.subresourceUnderTopFrameOrigins.add(mainFramePrimaryDomain);
323         if (subresourceUnderTopFrameOriginsResult.isNewEntry)
324             shouldFireDataModificationHandler = true;
325
326         ++targetStatistics.subresourceHasBeenSubresourceCount;
327         
328         auto totalVisited = std::max(m_originsVisitedMap.size(), 1U);
329         
330         targetStatistics.subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited = static_cast<double>(targetStatistics.subresourceHasBeenSubresourceCount) / totalVisited;
331         } // Release lock
332         
333         if (shouldFireDataModificationHandler)
334             m_store->fireDataModificationHandler();
335     });
336 }
337
338 static WallTime reduceTimeResolution(WallTime time)
339 {
340     return WallTime::fromRawSeconds(std::floor(time.secondsSinceEpoch() / timestampResolution) * timestampResolution.seconds());
341 }
342
343 void ResourceLoadObserver::logUserInteractionWithReducedTimeResolution(const Document& document)
344 {
345     ASSERT(document.page());
346
347     if (!shouldLog(document.page()))
348         return;
349
350     auto& url = document.url();
351     if (url.isBlankURL() || url.isEmpty())
352         return;
353
354     ASSERT(m_queue);
355     m_queue->dispatch([this, primaryDomainString = primaryDomain(url).isolatedCopy()] {
356         {
357         auto locker = holdLock(m_store->statisticsLock());
358         auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomainString);
359         WallTime newTime = reduceTimeResolution(WallTime::now());
360         if (newTime == statistics.mostRecentUserInteractionTime())
361             return;
362
363         statistics.hadUserInteraction = true;
364         statistics.mostRecentUserInteraction = newTime.secondsSinceEpoch().value();
365         }
366         
367         m_store->fireDataModificationHandler();
368     });
369 }
370
371 void ResourceLoadObserver::logUserInteraction(const URL& url)
372 {
373     if (url.isBlankURL() || url.isEmpty())
374         return;
375
376     ASSERT(m_queue);
377     m_queue->dispatch([this, primaryDomainString = primaryDomain(url).isolatedCopy()] {
378         {
379         auto locker = holdLock(m_store->statisticsLock());
380         auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomainString);
381         statistics.hadUserInteraction = true;
382         statistics.mostRecentUserInteraction = WTF::currentTime();
383         }
384         
385         m_store->fireShouldPartitionCookiesHandler({ primaryDomainString }, { }, false);
386     });
387 }
388
389 void ResourceLoadObserver::clearUserInteraction(const URL& url)
390 {
391     if (url.isBlankURL() || url.isEmpty())
392         return;
393
394     auto locker = holdLock(m_store->statisticsLock());
395     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
396     
397     statistics.hadUserInteraction = false;
398     statistics.mostRecentUserInteraction = 0;
399 }
400
401 bool ResourceLoadObserver::hasHadUserInteraction(const URL& url)
402 {
403     if (url.isBlankURL() || url.isEmpty())
404         return false;
405
406     auto locker = holdLock(m_store->statisticsLock());
407     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
408     
409     return m_store->hasHadRecentUserInteraction(statistics);
410 }
411
412 void ResourceLoadObserver::setPrevalentResource(const URL& url)
413 {
414     if (url.isBlankURL() || url.isEmpty())
415         return;
416
417     auto locker = holdLock(m_store->statisticsLock());
418     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
419     
420     statistics.isPrevalentResource = true;
421 }
422
423 bool ResourceLoadObserver::isPrevalentResource(const URL& url)
424 {
425     if (url.isBlankURL() || url.isEmpty())
426         return false;
427
428     auto locker = holdLock(m_store->statisticsLock());
429     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
430     
431     return statistics.isPrevalentResource;
432 }
433     
434 void ResourceLoadObserver::clearPrevalentResource(const URL& url)
435 {
436     if (url.isBlankURL() || url.isEmpty())
437         return;
438
439     auto locker = holdLock(m_store->statisticsLock());
440     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
441     
442     statistics.isPrevalentResource = false;
443 }
444     
445 void ResourceLoadObserver::setGrandfathered(const URL& url, bool value)
446 {
447     if (url.isBlankURL() || url.isEmpty())
448         return;
449     
450     auto locker = holdLock(m_store->statisticsLock());
451     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
452     
453     statistics.grandfathered = value;
454 }
455     
456 bool ResourceLoadObserver::isGrandfathered(const URL& url)
457 {
458     if (url.isBlankURL() || url.isEmpty())
459         return false;
460     
461     auto locker = holdLock(m_store->statisticsLock());
462     auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primaryDomain(url));
463     
464     return statistics.grandfathered;
465 }
466
467 void ResourceLoadObserver::setSubframeUnderTopFrameOrigin(const URL& subframe, const URL& topFrame)
468 {
469     if (subframe.isBlankURL() || subframe.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
470         return;
471     
472     ASSERT(m_queue);
473     m_queue->dispatch([this, primaryTopFrameDomainString = primaryDomain(topFrame).isolatedCopy(), primarySubFrameDomainString = primaryDomain(subframe).isolatedCopy()] {
474         auto locker = holdLock(m_store->statisticsLock());
475         auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primarySubFrameDomainString);
476         statistics.subframeUnderTopFrameOrigins.add(primaryTopFrameDomainString);
477     });
478 }
479
480 void ResourceLoadObserver::setSubresourceUnderTopFrameOrigin(const URL& subresource, const URL& topFrame)
481 {
482     if (subresource.isBlankURL() || subresource.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
483         return;
484     
485     ASSERT(m_queue);
486     m_queue->dispatch([this, primaryTopFrameDomainString = primaryDomain(topFrame).isolatedCopy(), primarySubresourceDomainString = primaryDomain(subresource).isolatedCopy()] {
487         auto locker = holdLock(m_store->statisticsLock());
488         auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomainString);
489         statistics.subresourceUnderTopFrameOrigins.add(primaryTopFrameDomainString);
490     });
491 }
492
493 void ResourceLoadObserver::setSubresourceUniqueRedirectTo(const URL& subresource, const URL& hostNameRedirectedTo)
494 {
495     if (subresource.isBlankURL() || subresource.isEmpty() || hostNameRedirectedTo.isBlankURL() || hostNameRedirectedTo.isEmpty())
496         return;
497     
498     ASSERT(m_queue);
499     m_queue->dispatch([this, primaryRedirectDomainString = primaryDomain(hostNameRedirectedTo).isolatedCopy(), primarySubresourceDomainString = primaryDomain(subresource).isolatedCopy()] {
500         auto locker = holdLock(m_store->statisticsLock());
501         auto& statistics = m_store->ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomainString);
502         statistics.subresourceUniqueRedirectsTo.add(primaryRedirectDomainString);
503     });
504 }
505
506 void ResourceLoadObserver::setTimeToLiveUserInteraction(Seconds seconds)
507 {
508     m_store->setTimeToLiveUserInteraction(seconds);
509 }
510
511 void ResourceLoadObserver::setTimeToLiveCookiePartitionFree(Seconds seconds)
512 {
513     m_store->setTimeToLiveCookiePartitionFree(seconds);
514 }
515
516 void ResourceLoadObserver::setMinimumTimeBetweeenDataRecordsRemoval(Seconds seconds)
517 {
518     m_store->setMinimumTimeBetweeenDataRecordsRemoval(seconds);
519 }
520     
521 void ResourceLoadObserver::setReducedTimestampResolution(Seconds seconds)
522 {
523     if (seconds > 0_s)
524         timestampResolution = seconds;
525 }
526
527 void ResourceLoadObserver::setGrandfatheringTime(Seconds seconds)
528 {
529     m_store->setMinimumTimeBetweeenDataRecordsRemoval(seconds);
530 }
531     
532 void ResourceLoadObserver::fireDataModificationHandler()
533 {
534     // Helper function used by testing system. Should only be called from the main thread.
535     ASSERT(isMainThread());
536     m_queue->dispatch([this] {
537         m_store->fireDataModificationHandler();
538     });
539 }
540
541 void ResourceLoadObserver::fireShouldPartitionCookiesHandler()
542 {
543     // Helper function used by testing system. Should only be called from the main thread.
544     ASSERT(isMainThread());
545     m_queue->dispatch([this] {
546         m_store->fireShouldPartitionCookiesHandler();
547     });
548 }
549
550 void ResourceLoadObserver::fireShouldPartitionCookiesHandler(const Vector<String>& domainsToRemove, const Vector<String>& domainsToAdd, bool clearFirst)
551 {
552     // Helper function used by testing system. Should only be called from the main thread.
553     ASSERT(isMainThread());
554     m_queue->dispatch([this, domainsToRemove = CrossThreadCopier<Vector<String>>::copy(domainsToRemove), domainsToAdd = CrossThreadCopier<Vector<String>>::copy(domainsToAdd), clearFirst] {
555         m_store->fireShouldPartitionCookiesHandler(domainsToRemove, domainsToAdd, clearFirst);
556     });
557 }
558
559 void ResourceLoadObserver::fireTelemetryHandler()
560 {
561     // Helper function used by testing system. Should only be called from the main thread.
562     ASSERT(isMainThread());
563     m_store->fireTelemetryHandler();
564 }
565     
566 String ResourceLoadObserver::primaryDomain(const URL& url)
567 {
568     return primaryDomain(url.host());
569 }
570
571 String ResourceLoadObserver::primaryDomain(const String& host)
572 {
573     if (host.isNull() || host.isEmpty())
574         return ASCIILiteral("nullOrigin");
575
576 #if ENABLE(PUBLIC_SUFFIX_LIST)
577     String primaryDomain = topPrivatelyControlledDomain(host);
578     // We will have an empty string here if there is no TLD. Use the host as a fallback.
579     if (!primaryDomain.isEmpty())
580         return primaryDomain;
581 #endif
582
583     return host;
584 }
585
586 String ResourceLoadObserver::statisticsForOrigin(const String& origin)
587 {
588     return m_store ? m_store->statisticsForOrigin(origin) : emptyString();
589 }
590
591 }