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 return IPC_FAIL(aSource
, error
.get());
158 // Validate may have dropped some fields from the transaction, check it's not
159 // empty before continuing.
160 if (mModified
.isEmpty()) {
164 BrowsingContextGroup
* group
= owner
->Group();
165 group
->EachOtherParent(aSource
, [&](ContentParent
* aParent
) {
166 owner
->SendCommitTransaction(aParent
, *this,
167 aParent
->GetBrowsingContextFieldEpoch());
170 Apply(owner
, /* aFromIPC */ true);
174 template <typename Context
>
175 mozilla::ipc::IPCResult Transaction
<Context
>::CommitFromIPC(
176 const MaybeDiscarded
<Context
>& aOwner
, uint64_t aEpoch
,
177 ContentChild
* aSource
) {
178 MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
179 if (aOwner
.IsNullOrDiscarded()) {
180 MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug
,
181 ("ChildIPC: Trying to send a message to dead or detached context"));
184 Context
* owner
= aOwner
.get();
186 // Clear any fields which have been obsoleted by the epoch.
187 EachIndex([&](auto idx
) {
188 if (mModified
.contains(idx
) && FieldEpoch(idx
, owner
) > aEpoch
) {
190 Context::GetSyncLog(), LogLevel::Debug
,
191 ("Transaction::Obsoleted(#%" PRIx64
", %" PRIu64
">%" PRIu64
"): %s",
192 owner
->Id(), FieldEpoch(idx
, owner
), aEpoch
,
193 Context::FieldIndexToName(idx
)));
198 if (mModified
.isEmpty()) {
202 Apply(owner
, /* aFromIPC */ true);
206 template <typename Context
>
207 void Transaction
<Context
>::Apply(Context
* aOwner
, bool aFromIPC
) {
208 MOZ_ASSERT(!mModified
.isEmpty());
211 Context::GetSyncLog(), LogLevel::Debug
,
212 ("Transaction::Apply(#%" PRIx64
", %s): %s", aOwner
->Id(),
213 aFromIPC
? "ipc" : "local",
214 FormatTransaction
<Context
>(mModified
, aOwner
->mFields
.mValues
, mValues
)
217 EachIndex([&](auto idx
) {
218 if (mModified
.contains(idx
)) {
219 auto& txnField
= mValues
.Get(idx
);
220 auto& ownerField
= aOwner
->mFields
.mValues
.Get(idx
);
221 std::swap(ownerField
, txnField
);
223 aOwner
->DidSet(idx
, std::move(txnField
));
229 template <typename Context
>
230 void Transaction
<Context
>::CommitWithoutSyncing(Context
* aOwner
) {
232 Context::GetSyncLog(), LogLevel::Debug
,
233 ("Transaction::CommitWithoutSyncing(#%" PRIx64
"): %s", aOwner
->Id(),
234 FormatTransaction
<Context
>(mModified
, aOwner
->mFields
.mValues
, mValues
)
237 EachIndex([&](auto idx
) {
238 if (mModified
.contains(idx
)) {
239 aOwner
->mFields
.mValues
.Get(idx
) = std::move(mValues
.Get(idx
));
245 inline CanSetResult
AsCanSetResult(CanSetResult aValue
) { return aValue
; }
246 inline CanSetResult
AsCanSetResult(bool aValue
) {
247 return aValue
? CanSetResult::Allow
: CanSetResult::Deny
;
250 template <typename Context
>
251 typename Transaction
<Context
>::IndexSet Transaction
<Context
>::Validate(
252 Context
* aOwner
, ContentParent
* aSource
) {
253 IndexSet failedFields
;
254 Transaction
<Context
> revertTxn
;
256 EachIndex([&](auto idx
) {
257 if (!mModified
.contains(idx
)) {
261 switch (AsCanSetResult(aOwner
->CanSet(idx
, mValues
.Get(idx
), aSource
))) {
262 case CanSetResult::Allow
:
264 case CanSetResult::Deny
:
267 case CanSetResult::Revert
:
268 revertTxn
.mValues
.Get(idx
) = aOwner
->mFields
.mValues
.Get(idx
);
269 revertTxn
.mModified
+= idx
;
274 // If any changes need to be reverted, log them, remove them from this
275 // transaction, and optionally send a message to the source process.
276 if (!revertTxn
.mModified
.isEmpty()) {
277 // NOTE: Logging with modified IndexSet from revert transaction, and values
278 // from this transaction, so we log the failed values we're going to revert.
280 Context::GetSyncLog(), LogLevel::Debug
,
281 ("Transaction::PartialRevert(#%" PRIx64
", pid %" PRIPID
"): %s",
282 aOwner
->Id(), aSource
? aSource
->OtherPid() : base::kInvalidProcessId
,
283 FormatTransaction
<Context
>(revertTxn
.mModified
, mValues
,
287 mModified
-= revertTxn
.mModified
;
290 aOwner
->SendCommitTransaction(aSource
, revertTxn
,
291 aSource
->GetBrowsingContextFieldEpoch());
297 template <typename Context
>
298 void Transaction
<Context
>::Write(IPC::MessageWriter
* aWriter
,
299 mozilla::ipc::IProtocol
* aActor
) const {
300 // Record which field indices will be included, and then write those fields
302 typename
IndexSet::serializedType modified
= mModified
.serialize();
303 WriteIPDLParam(aWriter
, aActor
, modified
);
304 EachIndex([&](auto idx
) {
305 if (mModified
.contains(idx
)) {
306 WriteIPDLParam(aWriter
, aActor
, mValues
.Get(idx
));
311 template <typename Context
>
312 bool Transaction
<Context
>::Read(IPC::MessageReader
* aReader
,
313 mozilla::ipc::IProtocol
* aActor
) {
314 // Read in which field indices were sent by the remote, followed by the fields
315 // identified by those indices.
316 typename
IndexSet::serializedType modified
=
317 typename
IndexSet::serializedType
{};
318 if (!ReadIPDLParam(aReader
, aActor
, &modified
)) {
321 mModified
.deserialize(modified
);
324 EachIndex([&](auto idx
) {
325 if (ok
&& mModified
.contains(idx
)) {
326 ok
= ReadIPDLParam(aReader
, aActor
, &mValues
.Get(idx
));
332 template <typename Base
, size_t Count
>
333 void FieldValues
<Base
, Count
>::Write(IPC::MessageWriter
* aWriter
,
334 mozilla::ipc::IProtocol
* aActor
) const {
335 // XXX The this-> qualification is necessary to work around a bug in older gcc
336 // versions causing an ICE.
337 EachIndex([&](auto idx
) { WriteIPDLParam(aWriter
, aActor
, this->Get(idx
)); });
340 template <typename Base
, size_t Count
>
341 bool FieldValues
<Base
, Count
>::Read(IPC::MessageReader
* aReader
,
342 mozilla::ipc::IProtocol
* aActor
) {
344 EachIndex([&](auto idx
) {
346 // XXX The this-> qualification is necessary to work around a bug in older
347 // gcc versions causing an ICE.
348 ok
= ReadIPDLParam(aReader
, aActor
, &this->Get(idx
));
354 } // namespace syncedcontext
356 } // namespace mozilla
358 #endif // !defined(mozilla_dom_SyncedContextInlines_h)