no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / docshell / base / SyncedContextInlines.h
blobe386e75b35753ec686dd0fa74f0ed392302d194d
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"
17 namespace mozilla {
18 namespace dom {
19 namespace syncedcontext {
21 template <typename T>
22 struct IsMozillaMaybe : std::false_type {};
23 template <typename T>
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.
28 template <typename T>
29 void FormatFieldValue(nsACString& aStr, const T& aValue) {
30 if constexpr (std::is_same_v<bool, T>) {
31 if (aValue) {
32 aStr.AppendLiteral("true");
33 } else {
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>) {
47 aStr.Append(aValue);
48 } else if constexpr (IsMozillaMaybe<T>::value) {
49 if (aValue) {
50 aStr.AppendLiteral("Some(");
51 FormatFieldValue(aStr, aValue.ref());
52 aStr.AppendLiteral(")");
53 } else {
54 aStr.AppendLiteral("Nothing");
56 } else {
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) {
68 nsAutoCString result;
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(") ");
78 });
79 return result;
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));
90 });
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()) {
107 return NS_OK;
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);
123 } else {
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);
135 return NS_OK;
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"));
145 return IPC_OK();
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>(
153 failedFields,
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()) {
162 return IPC_OK();
165 BrowsingContextGroup* group = owner->Group();
166 group->EachOtherParent(aSource, [&](ContentParent* aParent) {
167 owner->SendCommitTransaction(aParent, *this,
168 aParent->GetBrowsingContextFieldEpoch());
171 Apply(owner, /* aFromIPC */ true);
172 return IPC_OK();
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"));
183 return IPC_OK();
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) {
190 MOZ_LOG(
191 Context::GetSyncLog(), LogLevel::Debug,
192 ("Transaction::Obsoleted(#%" PRIx64 ", %" PRIu64 ">%" PRIu64 "): %s",
193 owner->Id(), FieldEpoch(idx, owner), aEpoch,
194 Context::FieldIndexToName(idx)));
195 mModified -= idx;
199 if (mModified.isEmpty()) {
200 return IPC_OK();
203 Apply(owner, /* aFromIPC */ true);
204 return IPC_OK();
207 template <typename Context>
208 void Transaction<Context>::Apply(Context* aOwner, bool aFromIPC) {
209 MOZ_ASSERT(!mModified.isEmpty());
211 MOZ_LOG(
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)
216 .get()));
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);
223 aOwner->DidSet(idx);
224 aOwner->DidSet(idx, std::move(txnField));
227 mModified.clear();
230 template <typename Context>
231 void Transaction<Context>::CommitWithoutSyncing(Context* aOwner) {
232 MOZ_LOG(
233 Context::GetSyncLog(), LogLevel::Debug,
234 ("Transaction::CommitWithoutSyncing(#%" PRIx64 "): %s", aOwner->Id(),
235 FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
236 .get()));
238 EachIndex([&](auto idx) {
239 if (mModified.contains(idx)) {
240 aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx));
243 mModified.clear();
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)) {
259 return;
262 switch (AsCanSetResult(aOwner->CanSet(idx, mValues.Get(idx), aSource))) {
263 case CanSetResult::Allow:
264 break;
265 case CanSetResult::Deny:
266 failedFields += idx;
267 break;
268 case CanSetResult::Revert:
269 revertTxn.mValues.Get(idx) = aOwner->mFields.mValues.Get(idx);
270 revertTxn.mModified += idx;
271 break;
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.
280 MOZ_LOG(
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,
285 revertTxn.mValues)
286 .get()));
288 mModified -= revertTxn.mModified;
290 if (aSource) {
291 aOwner->SendCommitTransaction(aSource, revertTxn,
292 aSource->GetBrowsingContextFieldEpoch());
295 return failedFields;
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
302 // out.
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)) {
320 return false;
322 mModified.deserialize(modified);
324 bool ok = true;
325 EachIndex([&](auto idx) {
326 if (ok && mModified.contains(idx)) {
327 ok = ReadIPDLParam(aReader, aActor, &mValues.Get(idx));
330 return ok;
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) {
344 bool ok = true;
345 EachIndex([&](auto idx) {
346 if (ok) {
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));
352 return ok;
355 } // namespace syncedcontext
356 } // namespace dom
357 } // namespace mozilla
359 #endif // !defined(mozilla_dom_SyncedContextInlines_h)