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_SyncedContextInlines_h
8 #define mozilla_dom_SyncedContextInlines_h
10 #include "mozilla/dom/SyncedContext.h"
11 #include "mozilla/dom/BrowsingContextGroup.h"
12 #include "mozilla/dom/ContentParent.h"
13 #include "mozilla/dom/ContentChild.h"
14 #include "nsReadableUtils.h"
15 #include "mozilla/HalIPCUtils.h"
19 namespace syncedcontext
{
22 struct IsMozillaMaybe
: std::false_type
{};
24 struct IsMozillaMaybe
<Maybe
<T
>> : std::true_type
{};
26 // A super-sketchy logging only function for generating a human-readable version
27 // of the value of a synced context field.
29 void FormatFieldValue(nsACString
& aStr
, const T
& aValue
) {
30 if constexpr (std::is_same_v
<bool, T
>) {
32 aStr
.AppendLiteral("true");
34 aStr
.AppendLiteral("false");
36 } else if constexpr (std::is_same_v
<nsID
, T
>) {
37 aStr
.Append(nsIDToCString(aValue
).get());
38 } else if constexpr (std::is_integral_v
<T
>) {
39 aStr
.AppendInt(aValue
);
40 } else if constexpr (std::is_enum_v
<T
>) {
41 aStr
.AppendInt(static_cast<std::underlying_type_t
<T
>>(aValue
));
42 } else if constexpr (std::is_floating_point_v
<T
>) {
43 aStr
.AppendFloat(aValue
);
44 } else if constexpr (std::is_base_of_v
<nsAString
, T
>) {
45 AppendUTF16toUTF8(aValue
, aStr
);
46 } else if constexpr (std::is_base_of_v
<nsACString
, T
>) {
48 } else if constexpr (IsMozillaMaybe
<T
>::value
) {
50 aStr
.AppendLiteral("Some(");
51 FormatFieldValue(aStr
, aValue
.ref());
52 aStr
.AppendLiteral(")");
54 aStr
.AppendLiteral("Nothing");
57 aStr
.AppendLiteral("???");
61 // Sketchy logging-only logic to generate a human-readable output of the actions
62 // a synced context transaction is going to perform.
63 template <typename Context
>
64 nsAutoCString
FormatTransaction(
65 typename Transaction
<Context
>::IndexSet aModified
,
66 const typename
Context::FieldValues
& aOldValues
,
67 const typename
Context::FieldValues
& aNewValues
) {
69 Context::FieldValues::EachIndex([&](auto idx
) {
70 if (aModified
.contains(idx
)) {
71 result
.Append(Context::FieldIndexToName(idx
));
72 result
.AppendLiteral("(");
73 FormatFieldValue(result
, aOldValues
.Get(idx
));
74 result
.AppendLiteral("->");
75 FormatFieldValue(result
, aNewValues
.Get(idx
));
76 result
.AppendLiteral(") ");
82 template <typename Context
>
83 nsCString
FormatValidationError(
84 typename Transaction
<Context
>::IndexSet aFailedFields
, const char* prefix
) {
85 MOZ_ASSERT(!aFailedFields
.isEmpty());
86 return nsDependentCString
{prefix
} +
87 StringJoin(", "_ns
, aFailedFields
,
88 [](nsACString
& dest
, const auto& idx
) {
89 dest
.Append(Context::FieldIndexToName(idx
));
93 template <typename Context
>
94 nsresult Transaction
<Context
>::Commit(Context
* aOwner
) {
95 if (NS_WARN_IF(aOwner
->IsDiscarded())) {
96 return NS_ERROR_DOM_INVALID_STATE_ERR
;
99 IndexSet failedFields
= Validate(aOwner
, nullptr);
100 if (!failedFields
.isEmpty()) {
101 nsCString error
= FormatValidationError
<Context
>(
102 failedFields
, "CanSet failed for field(s): ");
103 MOZ_CRASH_UNSAFE_PRINTF("%s", error
.get());
106 if (mModified
.isEmpty()) {
110 if (XRE_IsContentProcess()) {
111 ContentChild
* cc
= ContentChild::GetSingleton();
113 // Increment the field epoch for fields affected by this transaction.
114 uint64_t epoch
= cc
->NextBrowsingContextFieldEpoch();
115 EachIndex([&](auto idx
) {
116 if (mModified
.contains(idx
)) {
117 FieldEpoch(idx
, aOwner
) = epoch
;
121 // Tell our derived class to send the correct "Commit" IPC message.
122 aOwner
->SendCommitTransaction(cc
, *this, epoch
);
124 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
126 // Tell our derived class to send the correct "Commit" IPC messages.
127 BrowsingContextGroup
* group
= aOwner
->Group();
128 group
->EachParent([&](ContentParent
* aParent
) {
129 aOwner
->SendCommitTransaction(aParent
, *this,
130 aParent
->GetBrowsingContextFieldEpoch());
134 Apply(aOwner
, /* aFromIPC */ false);
138 template <typename Context
>
139 mozilla::ipc::IPCResult Transaction
<Context
>::CommitFromIPC(
140 const MaybeDiscarded
<Context
>& aOwner
, ContentParent
* aSource
) {
141 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
142 if (aOwner
.IsNullOrDiscarded()) {
143 MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug
,
144 ("IPC: Trying to send a message to dead or detached context"));
147 Context
* owner
= aOwner
.get();
149 // Validate that the set from content is allowed before continuing.
150 IndexSet failedFields
= Validate(owner
, aSource
);
151 if (!failedFields
.isEmpty()) {
152 nsCString error
= FormatValidationError
<Context
>(
154 "Invalid Transaction from Child - CanSet failed for field(s): ");
155 // data-review+ at https://bugzilla.mozilla.org/show_bug.cgi?id=1618992#c7
156 return IPC_FAIL_UNSAFE_PRINTF(aSource
, "%s", error
.get());
159 // Validate may have dropped some fields from the transaction, check it's not
160 // empty before continuing.
161 if (mModified
.isEmpty()) {
165 BrowsingContextGroup
* group
= owner
->Group();
166 group
->EachOtherParent(aSource
, [&](ContentParent
* aParent
) {
167 owner
->SendCommitTransaction(aParent
, *this,
168 aParent
->GetBrowsingContextFieldEpoch());
171 Apply(owner
, /* aFromIPC */ true);
175 template <typename Context
>
176 mozilla::ipc::IPCResult Transaction
<Context
>::CommitFromIPC(
177 const MaybeDiscarded
<Context
>& aOwner
, uint64_t aEpoch
,
178 ContentChild
* aSource
) {
179 MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
180 if (aOwner
.IsNullOrDiscarded()) {
181 MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug
,
182 ("ChildIPC: Trying to send a message to dead or detached context"));
185 Context
* owner
= aOwner
.get();
187 // Clear any fields which have been obsoleted by the epoch.
188 EachIndex([&](auto idx
) {
189 if (mModified
.contains(idx
) && FieldEpoch(idx
, owner
) > aEpoch
) {
191 Context::GetSyncLog(), LogLevel::Debug
,
192 ("Transaction::Obsoleted(#%" PRIx64
", %" PRIu64
">%" PRIu64
"): %s",
193 owner
->Id(), FieldEpoch(idx
, owner
), aEpoch
,
194 Context::FieldIndexToName(idx
)));
199 if (mModified
.isEmpty()) {
203 Apply(owner
, /* aFromIPC */ true);
207 template <typename Context
>
208 void Transaction
<Context
>::Apply(Context
* aOwner
, bool aFromIPC
) {
209 MOZ_ASSERT(!mModified
.isEmpty());
212 Context::GetSyncLog(), LogLevel::Debug
,
213 ("Transaction::Apply(#%" PRIx64
", %s): %s", aOwner
->Id(),
214 aFromIPC
? "ipc" : "local",
215 FormatTransaction
<Context
>(mModified
, aOwner
->mFields
.mValues
, mValues
)
218 EachIndex([&](auto idx
) {
219 if (mModified
.contains(idx
)) {
220 auto& txnField
= mValues
.Get(idx
);
221 auto& ownerField
= aOwner
->mFields
.mValues
.Get(idx
);
222 std::swap(ownerField
, txnField
);
224 aOwner
->DidSet(idx
, std::move(txnField
));
230 template <typename Context
>
231 void Transaction
<Context
>::CommitWithoutSyncing(Context
* aOwner
) {
233 Context::GetSyncLog(), LogLevel::Debug
,
234 ("Transaction::CommitWithoutSyncing(#%" PRIx64
"): %s", aOwner
->Id(),
235 FormatTransaction
<Context
>(mModified
, aOwner
->mFields
.mValues
, mValues
)
238 EachIndex([&](auto idx
) {
239 if (mModified
.contains(idx
)) {
240 aOwner
->mFields
.mValues
.Get(idx
) = std::move(mValues
.Get(idx
));
246 inline CanSetResult
AsCanSetResult(CanSetResult aValue
) { return aValue
; }
247 inline CanSetResult
AsCanSetResult(bool aValue
) {
248 return aValue
? CanSetResult::Allow
: CanSetResult::Deny
;
251 template <typename Context
>
252 typename Transaction
<Context
>::IndexSet Transaction
<Context
>::Validate(
253 Context
* aOwner
, ContentParent
* aSource
) {
254 IndexSet failedFields
;
255 Transaction
<Context
> revertTxn
;
257 EachIndex([&](auto idx
) {
258 if (!mModified
.contains(idx
)) {
262 switch (AsCanSetResult(aOwner
->CanSet(idx
, mValues
.Get(idx
), aSource
))) {
263 case CanSetResult::Allow
:
265 case CanSetResult::Deny
:
268 case CanSetResult::Revert
:
269 revertTxn
.mValues
.Get(idx
) = aOwner
->mFields
.mValues
.Get(idx
);
270 revertTxn
.mModified
+= idx
;
275 // If any changes need to be reverted, log them, remove them from this
276 // transaction, and optionally send a message to the source process.
277 if (!revertTxn
.mModified
.isEmpty()) {
278 // NOTE: Logging with modified IndexSet from revert transaction, and values
279 // from this transaction, so we log the failed values we're going to revert.
281 Context::GetSyncLog(), LogLevel::Debug
,
282 ("Transaction::PartialRevert(#%" PRIx64
", pid %" PRIPID
"): %s",
283 aOwner
->Id(), aSource
? aSource
->OtherPid() : base::kInvalidProcessId
,
284 FormatTransaction
<Context
>(revertTxn
.mModified
, mValues
,
288 mModified
-= revertTxn
.mModified
;
291 aOwner
->SendCommitTransaction(aSource
, revertTxn
,
292 aSource
->GetBrowsingContextFieldEpoch());
298 template <typename Context
>
299 void Transaction
<Context
>::Write(IPC::MessageWriter
* aWriter
,
300 mozilla::ipc::IProtocol
* aActor
) const {
301 // Record which field indices will be included, and then write those fields
303 typename
IndexSet::serializedType modified
= mModified
.serialize();
304 WriteIPDLParam(aWriter
, aActor
, modified
);
305 EachIndex([&](auto idx
) {
306 if (mModified
.contains(idx
)) {
307 WriteIPDLParam(aWriter
, aActor
, mValues
.Get(idx
));
312 template <typename Context
>
313 bool Transaction
<Context
>::Read(IPC::MessageReader
* aReader
,
314 mozilla::ipc::IProtocol
* aActor
) {
315 // Read in which field indices were sent by the remote, followed by the fields
316 // identified by those indices.
317 typename
IndexSet::serializedType modified
=
318 typename
IndexSet::serializedType
{};
319 if (!ReadIPDLParam(aReader
, aActor
, &modified
)) {
322 mModified
.deserialize(modified
);
325 EachIndex([&](auto idx
) {
326 if (ok
&& mModified
.contains(idx
)) {
327 ok
= ReadIPDLParam(aReader
, aActor
, &mValues
.Get(idx
));
333 template <typename Base
, size_t Count
>
334 void FieldValues
<Base
, Count
>::Write(IPC::MessageWriter
* aWriter
,
335 mozilla::ipc::IProtocol
* aActor
) const {
336 // XXX The this-> qualification is necessary to work around a bug in older gcc
337 // versions causing an ICE.
338 EachIndex([&](auto idx
) { WriteIPDLParam(aWriter
, aActor
, this->Get(idx
)); });
341 template <typename Base
, size_t Count
>
342 bool FieldValues
<Base
, Count
>::Read(IPC::MessageReader
* aReader
,
343 mozilla::ipc::IProtocol
* aActor
) {
345 EachIndex([&](auto idx
) {
347 // XXX The this-> qualification is necessary to work around a bug in older
348 // gcc versions causing an ICE.
349 ok
= ReadIPDLParam(aReader
, aActor
, &this->Get(idx
));
355 } // namespace syncedcontext
357 } // namespace mozilla
359 #endif // !defined(mozilla_dom_SyncedContextInlines_h)