SyncedContextInlines.h (11809B)
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/. */ 6 7 #ifndef mozilla_dom_SyncedContextInlines_h 8 #define mozilla_dom_SyncedContextInlines_h 9 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" 16 17 namespace mozilla { 18 namespace dom { 19 namespace syncedcontext { 20 21 template <typename T> 22 struct IsMozillaMaybe : std::false_type {}; 23 template <typename T> 24 struct IsMozillaMaybe<Maybe<T>> : std::true_type {}; 25 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"); 35 } 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"); 55 } 56 } else { 57 aStr.AppendLiteral("???"); 58 } 59 } 60 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(") "); 77 } 78 }); 79 return result; 80 } 81 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 }); 91 } 92 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; 97 } 98 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()); 104 } 105 106 if (mModified.isEmpty()) { 107 return NS_OK; 108 } 109 110 if (XRE_IsContentProcess()) { 111 ContentChild* cc = ContentChild::GetSingleton(); 112 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; 118 } 119 }); 120 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()); 125 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()); 131 }); 132 } 133 134 Apply(aOwner, /* aFromIPC */ false); 135 return NS_OK; 136 } 137 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(); 146 } 147 Context* owner = aOwner.get(); 148 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()); 157 } 158 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(); 163 } 164 165 BrowsingContextGroup* group = owner->Group(); 166 group->EachOtherParent(aSource, [&](ContentParent* aParent) { 167 owner->SendCommitTransaction(aParent, *this, 168 aParent->GetBrowsingContextFieldEpoch()); 169 }); 170 171 Apply(owner, /* aFromIPC */ true); 172 return IPC_OK(); 173 } 174 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(); 184 } 185 Context* owner = aOwner.get(); 186 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; 196 } 197 }); 198 199 if (mModified.isEmpty()) { 200 return IPC_OK(); 201 } 202 203 Apply(owner, /* aFromIPC */ true); 204 return IPC_OK(); 205 } 206 207 template <typename Context> 208 void Transaction<Context>::Apply(Context* aOwner, bool aFromIPC) { 209 MOZ_ASSERT(!mModified.isEmpty()); 210 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())); 217 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)); 225 } 226 }); 227 mModified.clear(); 228 } 229 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())); 237 238 EachIndex([&](auto idx) { 239 if (mModified.contains(idx)) { 240 aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx)); 241 } 242 }); 243 mModified.clear(); 244 } 245 246 inline CanSetResult AsCanSetResult(CanSetResult aValue) { return aValue; } 247 inline CanSetResult AsCanSetResult(bool aValue) { 248 return aValue ? CanSetResult::Allow : CanSetResult::Deny; 249 } 250 251 template <typename Context> 252 typename Transaction<Context>::IndexSet Transaction<Context>::Validate( 253 Context* aOwner, ContentParent* aSource) { 254 IndexSet failedFields; 255 Transaction<Context> revertTxn; 256 257 EachIndex([&](auto idx) { 258 if (!mModified.contains(idx)) { 259 return; 260 } 261 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; 272 } 273 }); 274 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(Context::GetSyncLog(), LogLevel::Debug, 281 ("Transaction::PartialRevert(#%" PRIx64 ", childid %d, pid %" PRIPID 282 "): %s", 283 aOwner->Id(), aSource ? aSource->OtherChildID() : -1, 284 aSource ? aSource->OtherPid() : base::kInvalidProcessId, 285 FormatTransaction<Context>(revertTxn.mModified, mValues, 286 revertTxn.mValues) 287 .get())); 288 289 mModified -= revertTxn.mModified; 290 291 if (aSource) { 292 aOwner->SendCommitTransaction(aSource, revertTxn, 293 aSource->GetBrowsingContextFieldEpoch()); 294 } 295 } 296 return failedFields; 297 } 298 299 template <typename Context> 300 void Transaction<Context>::Write(IPC::MessageWriter* aWriter) const { 301 // Record which field indices will be included, and then write those fields 302 // out. 303 typename IndexSet::serializedType modified = mModified.serialize(); 304 IPC::WriteParam(aWriter, modified); 305 EachIndex([&](auto idx) { 306 if (mModified.contains(idx)) { 307 IPC::WriteParam(aWriter, mValues.Get(idx)); 308 } 309 }); 310 } 311 312 template <typename Context> 313 bool Transaction<Context>::Read(IPC::MessageReader* aReader) { 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 (!IPC::ReadParam(aReader, &modified)) { 319 return false; 320 } 321 mModified.deserialize(modified); 322 323 bool ok = true; 324 EachIndex([&](auto idx) { 325 if (ok && mModified.contains(idx)) { 326 ok = IPC::ReadParam(aReader, &mValues.Get(idx)); 327 } 328 }); 329 return ok; 330 } 331 332 template <typename Base, size_t Count> 333 void FieldValues<Base, Count>::Write(IPC::MessageWriter* aWriter) const { 334 // XXX The this-> qualification is necessary to work around a bug in older gcc 335 // versions causing an ICE. 336 EachIndex([&](auto idx) { IPC::WriteParam(aWriter, this->Get(idx)); }); 337 } 338 339 template <typename Base, size_t Count> 340 bool FieldValues<Base, Count>::Read(IPC::MessageReader* aReader) { 341 bool ok = true; 342 EachIndex([&](auto idx) { 343 if (ok) { 344 // XXX The this-> qualification is necessary to work around a bug in older 345 // gcc versions causing an ICE. 346 ok = IPC::ReadParam(aReader, &this->Get(idx)); 347 } 348 }); 349 return ok; 350 } 351 352 } // namespace syncedcontext 353 } // namespace dom 354 } // namespace mozilla 355 356 #endif // !defined(mozilla_dom_SyncedContextInlines_h)