1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_ContentBlockingLog_h
8 #define mozilla_dom_ContentBlockingLog_h
10 #include "mozilla/AntiTrackingCommon.h"
11 #include "mozilla/JSONWriter.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/StaticPrefs.h"
14 #include "mozilla/Tuple.h"
15 #include "mozilla/UniquePtr.h"
16 #include "nsReadableUtils.h"
18 #include "nsWindowSizes.h"
23 class ContentBlockingLog final
{
24 typedef AntiTrackingCommon::StorageAccessGrantedReason
25 StorageAccessGrantedReason
;
29 uint32_t mRepeatCount
;
31 Maybe
<AntiTrackingCommon::StorageAccessGrantedReason
> mReason
;
32 nsTArray
<nsCString
> mTrackingFullHashes
;
35 struct OriginDataEntry
{
36 OriginDataEntry() : mHasTrackingContentLoaded(false) {}
38 bool mHasTrackingContentLoaded
;
39 Maybe
<bool> mHasCookiesLoaded
;
40 nsTArray
<LogEntry
> mLogs
;
44 OriginEntry() { mData
= MakeUnique
<OriginDataEntry
>(); }
47 UniquePtr
<OriginDataEntry
> mData
;
50 typedef nsTArray
<OriginEntry
> OriginDataTable
;
52 struct StringWriteFunc
: public JSONWriteFunc
{
54 mBuffer
; // The lifetime of the struct must be bound to the buffer
55 explicit StringWriteFunc(nsACString
& aBuffer
) : mBuffer(aBuffer
) {}
57 void Write(const char* aStr
) override
{ mBuffer
.Append(aStr
); }
62 bool Equals(const OriginDataTable::elem_type
& aLeft
,
63 const OriginDataTable::elem_type
& aRight
) const {
64 return aLeft
.mOrigin
.Equals(aRight
.mOrigin
);
67 bool Equals(const OriginDataTable::elem_type
& aLeft
,
68 const nsACString
& aRight
) const {
69 return aLeft
.mOrigin
.Equals(aRight
);
74 static const nsLiteralCString kDummyOriginHash
;
76 ContentBlockingLog() = default;
77 ~ContentBlockingLog() = default;
80 const nsACString
& aOrigin
, uint32_t aType
, bool aBlocked
,
81 const Maybe
<AntiTrackingCommon::StorageAccessGrantedReason
>& aReason
,
82 const nsTArray
<nsCString
>& aTrackingFullHashes
) {
83 DebugOnly
<bool> isCookiesBlockedTracker
=
84 aType
== nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
;
85 MOZ_ASSERT_IF(aBlocked
, aReason
.isNothing());
86 MOZ_ASSERT_IF(!isCookiesBlockedTracker
, aReason
.isNothing());
87 MOZ_ASSERT_IF(isCookiesBlockedTracker
&& !aBlocked
, aReason
.isSome());
89 if (aOrigin
.IsVoid()) {
92 auto index
= mLog
.IndexOf(aOrigin
, 0, Comparator());
93 if (index
!= OriginDataTable::NoIndex
) {
94 OriginEntry
& entry
= mLog
[index
];
99 if (aType
== nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT
) {
100 entry
.mData
->mHasTrackingContentLoaded
= aBlocked
;
103 if (aType
== nsIWebProgressListener::STATE_COOKIES_LOADED
) {
104 if (entry
.mData
->mHasCookiesLoaded
.isSome()) {
105 entry
.mData
->mHasCookiesLoaded
.ref() = aBlocked
;
107 entry
.mData
->mHasCookiesLoaded
.emplace(aBlocked
);
111 if (!entry
.mData
->mLogs
.IsEmpty()) {
112 auto& last
= entry
.mData
->mLogs
.LastElement();
113 if (last
.mType
== aType
&& last
.mBlocked
== aBlocked
) {
115 // Don't record recorded events. This helps compress our log.
116 // We don't care about if the the reason is the same, just keep the
118 // Note: {aReason, aTrackingFullHashes} are not compared here and we
119 // simply keep the first ones.
121 for (const auto& hash
: aTrackingFullHashes
) {
122 MOZ_ASSERT(last
.mTrackingFullHashes
.Contains(hash
));
128 if (entry
.mData
->mLogs
.Length() ==
130 StaticPrefs::browser_contentblocking_originlog_length())) {
131 // Cap the size at the maximum length adjustable by the pref
132 entry
.mData
->mLogs
.RemoveElementAt(0);
134 entry
.mData
->mLogs
.AppendElement(
135 LogEntry
{aType
, 1u, aBlocked
, aReason
,
136 nsTArray
<nsCString
>(aTrackingFullHashes
)});
140 // The entry has not been found.
142 OriginEntry
* entry
= mLog
.AppendElement();
143 if (NS_WARN_IF(!entry
|| !entry
->mData
)) {
147 entry
->mOrigin
= aOrigin
;
149 if (aType
== nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT
) {
150 entry
->mData
->mHasTrackingContentLoaded
= aBlocked
;
151 } else if (aType
== nsIWebProgressListener::STATE_COOKIES_LOADED
) {
152 MOZ_ASSERT(entry
->mData
->mHasCookiesLoaded
.isNothing());
153 entry
->mData
->mHasCookiesLoaded
.emplace(aBlocked
);
155 entry
->mData
->mLogs
.AppendElement(
156 LogEntry
{aType
, 1u, aBlocked
, aReason
,
157 nsTArray
<nsCString
>(aTrackingFullHashes
)});
161 void ReportOrigins();
162 void ReportLog(nsIPrincipal
* aFirstPartyPrincipal
);
164 nsAutoCString
Stringify() {
165 nsAutoCString buffer
;
167 JSONWriter
w(MakeUnique
<StringWriteFunc
>(buffer
));
170 for (const OriginEntry
& entry
: mLog
) {
175 w
.StartArrayProperty(entry
.mOrigin
.get(), w
.SingleLineStyle
);
177 if (entry
.mData
->mHasTrackingContentLoaded
) {
178 w
.StartArrayElement(w
.SingleLineStyle
);
180 w
.IntElement(nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT
);
181 w
.BoolElement(true); // blocked
182 w
.IntElement(1); // repeat count
186 if (entry
.mData
->mHasCookiesLoaded
.isSome()) {
187 w
.StartArrayElement(w
.SingleLineStyle
);
189 w
.IntElement(nsIWebProgressListener::STATE_COOKIES_LOADED
);
190 w
.BoolElement(entry
.mData
->mHasCookiesLoaded
.value()); // blocked
191 w
.IntElement(1); // repeat count
195 for (const LogEntry
& item
: entry
.mData
->mLogs
) {
196 w
.StartArrayElement(w
.SingleLineStyle
);
198 w
.IntElement(item
.mType
);
199 w
.BoolElement(item
.mBlocked
);
200 w
.IntElement(item
.mRepeatCount
);
201 if (item
.mReason
.isSome()) {
202 w
.IntElement(item
.mReason
.value());
215 bool HasBlockedAnyOfType(uint32_t aType
) const {
216 // Note: nothing inside this loop should return false, the goal for the
217 // loop is to scan the log to see if we find a matching entry, and if so
218 // we would return true, otherwise in the end of the function outside of
219 // the loop we take the common `return false;` statement.
220 for (const OriginEntry
& entry
: mLog
) {
225 if (aType
== nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT
) {
226 if (entry
.mData
->mHasTrackingContentLoaded
) {
229 } else if (aType
== nsIWebProgressListener::STATE_COOKIES_LOADED
) {
230 if (entry
.mData
->mHasCookiesLoaded
.isSome() &&
231 entry
.mData
->mHasCookiesLoaded
.value()) {
235 for (const auto& item
: entry
.mData
->mLogs
) {
236 if (((item
.mType
& aType
) != 0) && item
.mBlocked
) {
245 void AddSizeOfExcludingThis(nsWindowSizes
& aSizes
) const {
246 aSizes
.mDOMOtherSize
+=
247 mLog
.ShallowSizeOfExcludingThis(aSizes
.mState
.mMallocSizeOf
);
249 // Now add the sizes of each origin log queue.
250 for (const OriginEntry
& entry
: mLog
) {
252 aSizes
.mDOMOtherSize
+= aSizes
.mState
.mMallocSizeOf(entry
.mData
.get()) +
253 entry
.mData
->mLogs
.ShallowSizeOfExcludingThis(
254 aSizes
.mState
.mMallocSizeOf
);
260 OriginDataTable mLog
;
264 } // namespace mozilla