JSIPCValueUtils.cpp (28307B)
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 #include "JSIPCValueUtils.h" 8 9 #include <stdint.h> 10 11 #include <utility> 12 13 #include "js/Array.h" 14 #include "js/Class.h" // ESClass 15 #include "js/Id.h" 16 #include "js/Object.h" 17 #include "js/Object.h" // JS::GetBuiltinClass 18 #include "js/RootingAPI.h" 19 #include "js/String.h" 20 #include "js/Value.h" 21 #include "js/friend/DumpFunctions.h" 22 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit 23 #include "mozilla/Assertions.h" 24 #include "mozilla/CycleCollectedJSRuntime.h" // OOMReported 25 #include "mozilla/Logging.h" 26 #include "mozilla/NotNull.h" 27 #include "mozilla/StaticPrefs_dom.h" 28 #include "mozilla/UniquePtr.h" 29 #include "mozilla/dom/BindingUtils.h" 30 #include "mozilla/dom/DOMRect.h" 31 #include "mozilla/dom/DOMRectBinding.h" 32 #include "mozilla/dom/MessagePort.h" 33 #include "mozilla/dom/StructuredCloneHolderBinding.h" 34 #include "nsContentUtils.h" 35 #include "nsFrameMessageManager.h" 36 #include "nsJSUtils.h" 37 #include "xpcpublic.h" // Logging of nsIPrincipal being unhandled. 38 39 using js::ESClass; 40 using JS::GetBuiltinClass; 41 42 namespace mozilla::dom { 43 44 bool JSActorSupportsTypedSend(const nsACString& aName) { 45 if (!StaticPrefs::dom_jsipc_send_typed()) { 46 return false; 47 } 48 49 // The Conduits and ProcessConduits actors send arguments for WebExtension 50 // calls in JS arrays, which means that WebExtensions are exposed to the 51 // idiosyncracies of nsFrameMessageManager::GetParamsForMessage(). There is 52 // even a subtest in browser_ext_sessions_window_tab_value.js that checks some 53 // specific behavior of the JSON serializer fallback. See 1960449 bug for 54 // details. Therefore, for now we don't want to use the typed serializer for 55 // those actors to reduce compatibility risk. 56 if (aName == "Conduits" || aName == "ProcessConduits") { 57 return false; 58 } 59 60 // Using the new serializer for the devtools DAMP tests causes performance 61 // regressions. Devtools uses complex messages that are difficult to type, so 62 // we're probably not losing much by giving up entirely on typing it. 63 // See bug 2007393. 64 if (aName == "DevToolsProcess" || aName == "BrowserToolboxDevToolsProcess") { 65 return false; 66 } 67 68 // Send messages from these actors untyped. Their messages are complex, so 69 // using IPDL serialization might cause problems, and the actors have a lot 70 // of privilege, so type checking won't add much safety. 71 return aName != "SpecialPowers" && aName != "MarionetteCommands"; 72 } 73 74 using Context = JSIPCValueUtils::Context; 75 76 static mozilla::LazyLogModule sSerializerLogger("JSIPCSerializer"); 77 78 #define MOZ_LOG_SERIALIZE_WARN(_arg) \ 79 MOZ_LOG(sSerializerLogger, mozilla::LogLevel::Warning, (_arg)) 80 81 static JSIPCValue NoteJSAPIFailure(Context& aCx, ErrorResult& aError, 82 const char* aWarning) { 83 MOZ_LOG(sSerializerLogger, mozilla::LogLevel::Warning, ("%s", aWarning)); 84 aError.NoteJSContextException(aCx); 85 return JSIPCValue(void_t()); 86 } 87 88 /* 89 * Conversion from JS values to JSIPCValues. 90 */ 91 92 static JSIPCValue FromJSObject(Context& aCx, JS::Handle<JSObject*> aObj, 93 ErrorResult& aError) { 94 JS::RootedVector<JS::PropertyKey> idv(aCx); 95 96 // As with TryAppendNativeProperties from StructuredClone.cpp, this ignores 97 // symbols by not having the JSITER_SYMBOLS flag. 98 if (!js::GetPropertyKeys(aCx, aObj, JSITER_OWNONLY, &idv)) { 99 return NoteJSAPIFailure(aCx, aError, "GetPropertyKeys failed"); 100 } 101 102 nsTArray<JSIPCProperty> properties; 103 JS::Rooted<JS::PropertyKey> id(aCx); 104 JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(aCx); 105 JS::Rooted<JS::Value> val(aCx); 106 for (size_t i = 0; i < idv.length(); ++i) { 107 id = idv[i]; 108 nsString stringName; 109 bool isSymbol = false; 110 if (!ConvertIdToString(aCx, id, stringName, isSymbol)) { 111 return NoteJSAPIFailure(aCx, aError, 112 "FromJSObject id string conversion failed"); 113 } 114 MOZ_ASSERT(!isSymbol); 115 116 if (!JS_GetPropertyById(aCx, aObj, id, &val)) { 117 return NoteJSAPIFailure(aCx, aError, "FromJSObject get property failed"); 118 } 119 auto ipcVal = JSIPCValueUtils::TypedFromJSVal(aCx, val, aError); 120 if (aError.Failed()) { 121 MOZ_LOG_SERIALIZE_WARN("FromJSObject value conversion failed"); 122 return ipcVal; 123 } 124 125 // If the property value has failed to serialize in non-strict mode, we want 126 // to drop the property entirely. Introducing a property with the value 127 // undefined in an object doesn't help anything, and this is also the 128 // behavior of GetParamsForMessage() in this situation, so this should 129 // increase compatibility. 130 // 131 // We always serialize to undefined when failing to serialize in non-strict 132 // mode. We also serialize to undefined if there was an error, but we've 133 // already checked aError.Failed() above. The original value could also have 134 // been undefined, so we must check !val.isUndefined(). 135 if (ipcVal.type() == JSIPCValue::Tvoid_t && !val.isUndefined()) { 136 MOZ_ASSERT(!aCx.mStrict, "serialized to undefined with non-strict"); 137 continue; 138 } 139 140 properties.EmplaceBack(JSIPCProperty(stringName, std::move(ipcVal))); 141 } 142 143 return JSIPCValue(std::move(properties)); 144 } 145 146 static JSIPCValue FromJSArray(Context& aCx, JS::Handle<JSObject*> aObj, 147 ErrorResult& aError) { 148 uint32_t len = 0; 149 if (!JS::GetArrayLength(aCx, aObj, &len)) { 150 return NoteJSAPIFailure(aCx, aError, "FromJSArray GetArrayLength failed"); 151 } 152 153 JS::Rooted<JS::Value> elt(aCx); 154 nsTArray<JSIPCValue> elements(len); 155 156 for (uint32_t i = 0; i < len; i++) { 157 if (!JS_GetElement(aCx, aObj, i, &elt)) { 158 return NoteJSAPIFailure(aCx, aError, "FromJSArray GetElement failed"); 159 } 160 161 auto ipcElt = JSIPCValueUtils::TypedFromJSVal(aCx, elt, aError); 162 if (aError.Failed()) { 163 MOZ_LOG_SERIALIZE_WARN("FromJSArray element conversion failed"); 164 return ipcElt; 165 } 166 elements.AppendElement(std::move(ipcElt)); 167 } 168 return JSIPCValue(JSIPCArray(std::move(elements))); 169 } 170 171 // This is based on JSStructuredCloneWriter::traverseSet(). 172 static JSIPCValue FromJSSet(Context& aCx, JS::Handle<JSObject*> aObj, 173 ErrorResult& aError) { 174 JS::Rooted<JS::GCVector<JS::Value>> elements( 175 aCx, JS::GCVector<JS::Value>(aCx.mCx)); 176 if (!js::GetSetObjectKeys(aCx, aObj, &elements)) { 177 return NoteJSAPIFailure(aCx, aError, "FromJSSet GetSetObjectKeys failed"); 178 } 179 180 nsTArray<JSIPCValue> ipcElements(elements.length()); 181 for (size_t i = 0; i < elements.length(); ++i) { 182 JSIPCValue ipcElement = 183 JSIPCValueUtils::TypedFromJSVal(aCx, elements[i], aError); 184 if (aError.Failed()) { 185 MOZ_LOG_SERIALIZE_WARN("FromJSSet element conversion failed"); 186 return ipcElement; 187 } 188 ipcElements.AppendElement(std::move(ipcElement)); 189 } 190 191 return JSIPCValue(JSIPCSet(std::move(ipcElements))); 192 } 193 194 static JSIPCValue FromJSMap(Context& aCx, JS::Handle<JSObject*> aObj, 195 ErrorResult& aError) { 196 JS::Rooted<JS::GCVector<JS::Value>> entries(aCx, 197 JS::GCVector<JS::Value>(aCx.mCx)); 198 if (!js::GetMapObjectKeysAndValuesInterleaved(aCx, aObj, &entries)) { 199 return NoteJSAPIFailure(aCx, aError, 200 "GetMapObjectKeysAndValuesInterleaved failed"); 201 } 202 203 // The entries vector contains a sequence of key/value pairs for each entry 204 // in the map, and always has a length that is a multiple of 2. 205 MOZ_ASSERT(entries.length() % 2 == 0); 206 nsTArray<JSIPCMapEntry> ipcEntries(entries.length() / 2); 207 for (size_t i = 0; i < entries.length(); i += 2) { 208 auto ipcKey = JSIPCValueUtils::TypedFromJSVal(aCx, entries[i], aError); 209 if (aError.Failed()) { 210 MOZ_LOG_SERIALIZE_WARN("FromJSMap key conversion failed"); 211 return ipcKey; 212 } 213 auto ipcVal = JSIPCValueUtils::TypedFromJSVal(aCx, entries[i + 1], aError); 214 if (aError.Failed()) { 215 MOZ_LOG_SERIALIZE_WARN("FromJSMap value conversion failed"); 216 return ipcVal; 217 } 218 ipcEntries.AppendElement( 219 JSIPCMapEntry(std::move(ipcKey), std::move(ipcVal))); 220 } 221 222 return JSIPCValue(std::move(ipcEntries)); 223 } 224 225 // Log information about the JS value that we had trouble serializing. 226 // This can be useful when debugging why IPDL serialization is failing on 227 // specific objects. 228 static void FallbackLogging(Context& aCx, JS::Handle<JS::Value> aVal) { 229 if (!MOZ_LOG_TEST(sSerializerLogger, LogLevel::Info)) { 230 return; 231 } 232 233 // For investigating certain kinds of problems, it is useful to also call 234 // js::DumpValue(aVal) here. Unfortunately it is not as helpful as you'd hope 235 // because it only gives information about the top level value. 236 237 nsAutoString json; 238 if (!nsContentUtils::StringifyJSON(aCx, aVal, json, 239 UndefinedIsNullStringLiteral)) { 240 JS_ClearPendingException(aCx); 241 return; 242 } 243 MOZ_LOG(sSerializerLogger, mozilla::LogLevel::Info, 244 ("JSON serialization to: %s", NS_ConvertUTF16toUTF8(json).get())); 245 } 246 247 // Use structured clone to serialize the JS value without type information. 248 // If aTopLevel is false, then this method is being called as a fallback in the 249 // middle of a fully typed IPDL serialization, so we may want to do some extra 250 // logging to understand any serialization failures. 251 static JSIPCValue UntypedFromJSVal(Context& aCx, JS::Handle<JS::Value> aVal, 252 ErrorResult& aError, 253 bool aTopLevel = false) { 254 js::AssertSameCompartment(aCx, aVal); 255 256 if (!aTopLevel) { 257 FallbackLogging(aCx, aVal); 258 } 259 260 auto data = MakeUnique<ipc::StructuredCloneData>(); 261 IgnoredErrorResult rv; 262 data->Write(aCx, aVal, JS::UndefinedHandleValue, JS::CloneDataPolicy(), rv); 263 if (!rv.Failed()) { 264 return JSIPCValue(std::move(data)); 265 } 266 267 JS_ClearPendingException(aCx); 268 269 if (!aTopLevel) { 270 MOZ_LOG_SERIALIZE_WARN("structured clone failed"); 271 } 272 273 if (aCx.mStrict) { 274 aError.ThrowInvalidStateError( 275 "structured clone failed for strict serialization"); 276 return JSIPCValue(void_t()); 277 } 278 279 // Unlike in nsFrameMessageManager::GetParamsForMessage(), we are probably 280 // right at whatever value can't be serialized, so return a dummy value in 281 // order to usefully serialize the rest of the value. 282 // 283 // The fallback serializer for GetParamsForMessage() does the equivalent of 284 // structuredClone(JSON.parse(JSON.stringify(v))), which can result in some 285 // odd behavior for parts of a value that are structured clonable but not 286 // representable in JSON. 287 // 288 // We are deliberately not fully compatible with the odd behavior of 289 // GetParamsForMessage(). See bug 1960449 for an example of how that can cause 290 // problems. Also see the second half of browser_jsat_serialize.js for 291 // examples of various corner cases with GetParamsForMessage() and how that 292 // compares to the behavior of this serializer. 293 return JSIPCValue(void_t()); 294 } 295 296 #define MOZ_LOG_UNTYPED_FALLBACK_WARN(_str) \ 297 MOZ_LOG(sSerializerLogger, mozilla::LogLevel::Warning, \ 298 ("UntypedFromJSVal fallback: %s", _str)) 299 300 // Try to serialize an ESClass::Other JS Object in a typeable way. 301 static JSIPCValue TypedFromOther(Context& aCx, JS::Handle<JS::Value> aVal, 302 ErrorResult& aError) { 303 if (!aVal.isObject()) { 304 MOZ_LOG_UNTYPED_FALLBACK_WARN("non-object ESClass::Other"); 305 return UntypedFromJSVal(aCx, aVal, aError); 306 } 307 308 JS::Rooted<JSObject*> obj(aCx, &aVal.toObject()); 309 if (!xpc::IsReflector(obj, aCx)) { 310 MOZ_LOG_UNTYPED_FALLBACK_WARN("ESClass::Other without reflector"); 311 return UntypedFromJSVal(aCx, aVal, aError); 312 } 313 314 // Checking for BrowsingContext. Based on 315 // StructuredCloneHolder::CustomWriteHandler(). 316 { 317 BrowsingContext* holder = nullptr; 318 if (NS_SUCCEEDED(UNWRAP_OBJECT(BrowsingContext, &obj, holder))) { 319 return JSIPCValue(MaybeDiscardedBrowsingContext(holder)); 320 } 321 } 322 // Checking for nsIPrincipal. Based on 323 // StructuredCloneHolder::WriteFullySerializableObjects(). 324 { 325 nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(obj); 326 nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base); 327 if (principal) { 328 // Warning: If you pass in principal directly, it gets silently converted 329 // to a bool. 330 return JSIPCValue(WrapNotNull<nsIPrincipal*>(principal)); 331 } 332 } 333 { 334 DOMRect* holder = nullptr; 335 if (NS_SUCCEEDED(UNWRAP_OBJECT(DOMRect, &obj, holder))) { 336 return JSIPCValue(JSIPCDOMRect(holder->X(), holder->Y(), holder->Width(), 337 holder->Height())); 338 } 339 } 340 341 // TODO: In the case of a StructuredCloneHolder, it should be possible to 342 // avoid the copy of the StructuredCloneHolder's buffer list into the wrapping 343 // structured clone. 344 345 // Don't warn if this is a StructuredCloneBlob: in that case, doing a 346 // structured clone is the preferred outcome. 347 if (MOZ_LOG_TEST(sSerializerLogger, LogLevel::Warning) && 348 !IS_INSTANCE_OF(StructuredCloneHolder, obj)) { 349 MOZ_LOG_UNTYPED_FALLBACK_WARN( 350 nsPrintfCString("ESClass::Other %s", JS::GetClass(obj)->name).get()); 351 } 352 353 return UntypedFromJSVal(aCx, aVal, aError); 354 } 355 356 JSIPCValue JSIPCValueUtils::TypedFromJSVal(Context& aCx, 357 JS::Handle<JS::Value> aVal, 358 ErrorResult& aError) { 359 js::AutoCheckRecursionLimit recursion(aCx); 360 if (!recursion.check(aCx)) { 361 return NoteJSAPIFailure(aCx, aError, "TypedFromJSVal recursion"); 362 } 363 js::AssertSameCompartment(aCx, aVal); 364 365 switch (aVal.type()) { 366 case JS::ValueType::Undefined: 367 return JSIPCValue(void_t()); 368 369 case JS::ValueType::Null: 370 return JSIPCValue(null_t()); 371 372 case JS::ValueType::String: { 373 nsAutoJSString stringVal; 374 if (!stringVal.init(aCx, aVal.toString())) { 375 return NoteJSAPIFailure(aCx, aError, "String init failed"); 376 } 377 return JSIPCValue(stringVal); 378 } 379 380 case JS::ValueType::Boolean: 381 return JSIPCValue(aVal.toBoolean()); 382 383 case JS::ValueType::Double: 384 return JSIPCValue(aVal.toDouble()); 385 386 case JS::ValueType::Int32: 387 return JSIPCValue(aVal.toInt32()); 388 389 case JS::ValueType::Object: { 390 JS::Rooted<JSObject*> obj(aCx, &aVal.toObject()); 391 js::ESClass cls; 392 if (!JS::GetBuiltinClass(aCx, obj, &cls)) { 393 return NoteJSAPIFailure(aCx, aError, "GetBuiltinClass failed"); 394 } 395 396 switch (cls) { 397 case ESClass::Object: 398 return FromJSObject(aCx, obj, aError); 399 400 case ESClass::Array: 401 return FromJSArray(aCx, obj, aError); 402 403 case ESClass::Set: 404 return FromJSSet(aCx, obj, aError); 405 406 case ESClass::Map: 407 return FromJSMap(aCx, obj, aError); 408 409 case ESClass::Other: 410 return TypedFromOther(aCx, aVal, aError); 411 412 default: 413 MOZ_LOG_UNTYPED_FALLBACK_WARN("Unhandled ESClass"); 414 return UntypedFromJSVal(aCx, aVal, aError); 415 } 416 } 417 418 default: 419 MOZ_LOG_UNTYPED_FALLBACK_WARN("Unhandled JS::ValueType"); 420 return UntypedFromJSVal(aCx, aVal, aError); 421 } 422 } 423 424 // If we are trying to structured clone an entire JS value, we need the JSON 425 // serialization fallback implemented by GetParamsForMessage(). There are some 426 // places that attempt to send, for instance, an object where one property is a 427 // function, and they want the object to be sent with the function property 428 // removed. This behavior is (hopefully) not needed when we are doing a deeper 429 // serialization using JSIPCValue, because that supports only sending the 430 // parts of the object that are serializable, when in non-strict mode. 431 static JSIPCValue UntypedFromJSValWithJSONFallback( 432 Context& aCx, JS::Handle<JS::Value> aVal, JS::Handle<JS::Value> aTransfer, 433 ErrorResult& aError) { 434 auto scd = MakeUnique<ipc::StructuredCloneData>(); 435 if (!nsFrameMessageManager::GetParamsForMessage(aCx, aVal, aTransfer, *scd)) { 436 aError.ThrowDataCloneError("UntypedFromJSValWithJSONFallback"); 437 return JSIPCValue(void_t()); 438 } 439 return JSIPCValue(std::move(scd)); 440 } 441 442 JSIPCValue JSIPCValueUtils::FromJSVal(Context& aCx, JS::Handle<JS::Value> aVal, 443 bool aSendTyped, ErrorResult& aError) { 444 if (aSendTyped) { 445 return TypedFromJSVal(aCx, aVal, aError); 446 } 447 if (aCx.mStrict) { 448 return UntypedFromJSVal(aCx, aVal, aError, /* aTopLevel = */ true); 449 } 450 return UntypedFromJSValWithJSONFallback(aCx, aVal, JS::UndefinedHandleValue, 451 aError); 452 } 453 454 JSIPCValue JSIPCValueUtils::FromJSVal(Context& aCx, JS::Handle<JS::Value> aVal, 455 JS::Handle<JS::Value> aTransferable, 456 bool aSendTyped, ErrorResult& aError) { 457 bool hasTransferable = 458 !aTransferable.isNull() && !aTransferable.isUndefined(); 459 if (!aSendTyped || hasTransferable) { 460 if (hasTransferable) { 461 // We might be able to make this case more typeable, but as of November 462 // 2024, we never send a message with a transferable that we care about 463 // typing from child to parent, outside of testing. Support for 464 // transferables may be required in the future if we start typing JSActor 465 // messages from trusted processes. 466 MOZ_LOG_SERIALIZE_WARN( 467 "Falling back to structured clone due to transferable"); 468 } 469 MOZ_ASSERT(!aCx.mStrict, "We could support this, but we don't"); 470 return UntypedFromJSValWithJSONFallback(aCx, aVal, aTransferable, aError); 471 } 472 return TypedFromJSVal(aCx, aVal, aError); 473 } 474 475 bool JSIPCValueUtils::PrepareForSending(SCDHolder& aHolder, 476 JSIPCValue& aValue) { 477 switch (aValue.type()) { 478 case JSIPCValue::Tvoid_t: 479 case JSIPCValue::TnsString: 480 case JSIPCValue::Tnull_t: 481 case JSIPCValue::Tbool: 482 case JSIPCValue::Tdouble: 483 case JSIPCValue::Tint32_t: 484 case JSIPCValue::TnsIPrincipal: 485 case JSIPCValue::TMaybeDiscardedBrowsingContext: 486 case JSIPCValue::TJSIPCDOMRect: 487 return true; 488 489 case JSIPCValue::TArrayOfJSIPCProperty: { 490 auto& properties = aValue.get_ArrayOfJSIPCProperty(); 491 for (auto& p : properties) { 492 if (!PrepareForSending(aHolder, p.value())) { 493 return false; 494 } 495 } 496 return true; 497 } 498 499 case JSIPCValue::TJSIPCArray: 500 for (auto& e : aValue.get_JSIPCArray().elements()) { 501 if (!PrepareForSending(aHolder, e)) { 502 return false; 503 } 504 } 505 return true; 506 507 case JSIPCValue::TJSIPCSet: 508 for (auto& e : aValue.get_JSIPCSet().elements()) { 509 if (!PrepareForSending(aHolder, e)) { 510 return false; 511 } 512 } 513 return true; 514 515 case JSIPCValue::TArrayOfJSIPCMapEntry: 516 for (auto& e : aValue.get_ArrayOfJSIPCMapEntry()) { 517 if (!PrepareForSending(aHolder, e.key())) { 518 return false; 519 } 520 if (!PrepareForSending(aHolder, e.value())) { 521 return false; 522 } 523 } 524 return true; 525 526 case JSIPCValue::TStructuredCloneData: { 527 UniquePtr<ipc::StructuredCloneData>* scd = aHolder.mSCDs.AppendElement( 528 std::move(aValue.get_StructuredCloneData())); 529 UniquePtr<ClonedMessageData> msgData = MakeUnique<ClonedMessageData>(); 530 if (!(*scd)->BuildClonedMessageData(*msgData)) { 531 MOZ_LOG_SERIALIZE_WARN("BuildClonedMessageData failed"); 532 return false; 533 } 534 aValue = std::move(msgData); 535 return true; 536 } 537 538 case JSIPCValue::TClonedMessageData: 539 MOZ_ASSERT(false, "ClonedMessageData in PrepareForSending"); 540 return false; 541 542 default: 543 MOZ_ASSERT_UNREACHABLE("Invalid unhandled case"); 544 return false; 545 } 546 } 547 548 static void ToJSObject(JSContext* aCx, nsTArray<JSIPCProperty>&& aProperties, 549 JS::MutableHandle<JS::Value> aOut, ErrorResult& aError) { 550 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); 551 if (!obj) { 552 aError.NoteJSContextException(aCx); 553 return; 554 } 555 JS::Rooted<JS::PropertyKey> id(aCx); 556 JS::Rooted<JS::Value> jsStringName(aCx); 557 JS::Rooted<JS::Value> newVal(aCx); 558 559 for (auto&& prop : aProperties) { 560 if (!xpc::NonVoidStringToJsval(aCx, prop.name(), &jsStringName)) { 561 aError.NoteJSContextException(aCx); 562 return; 563 } 564 if (!JS_ValueToId(aCx, jsStringName, &id)) { 565 aError.NoteJSContextException(aCx); 566 return; 567 } 568 JSIPCValueUtils::ToJSVal(aCx, std::move(prop.value()), &newVal, aError); 569 if (aError.Failed()) { 570 return; 571 } 572 if (!JS_DefinePropertyById(aCx, obj, id, newVal, JSPROP_ENUMERATE)) { 573 aError.NoteJSContextException(aCx); 574 return; 575 } 576 } 577 578 aOut.setObject(*obj); 579 } 580 581 static void ToJSArray(JSContext* aCx, nsTArray<JSIPCValue>&& aElements, 582 JS::MutableHandle<JS::Value> aOut, ErrorResult& aError) { 583 JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, aElements.Length())); 584 if (!array) { 585 aError.NoteJSContextException(aCx); 586 return; 587 } 588 589 JS::Rooted<JS::Value> value(aCx); 590 for (uint32_t i = 0; i < aElements.Length(); i++) { 591 JSIPCValueUtils::ToJSVal(aCx, std::move(aElements.ElementAt(i)), &value, 592 aError); 593 if (aError.Failed()) { 594 return; 595 } 596 if (!JS_DefineElement(aCx, array, i, value, JSPROP_ENUMERATE)) { 597 aError.NoteJSContextException(aCx); 598 return; 599 } 600 } 601 602 aOut.setObject(*array); 603 } 604 605 static void ToJSSet(JSContext* aCx, nsTArray<JSIPCValue>&& aElements, 606 JS::MutableHandle<JS::Value> aOut, ErrorResult& aError) { 607 JS::Rooted<JSObject*> setObject(aCx, JS::NewSetObject(aCx)); 608 if (!setObject) { 609 aError.NoteJSContextException(aCx); 610 return; 611 } 612 613 JS::Rooted<JS::Value> value(aCx); 614 for (auto&& e : aElements) { 615 JSIPCValueUtils::ToJSVal(aCx, std::move(e), &value, aError); 616 if (aError.Failed()) { 617 return; 618 } 619 if (!JS::SetAdd(aCx, setObject, value)) { 620 aError.NoteJSContextException(aCx); 621 return; 622 } 623 } 624 625 aOut.setObject(*setObject); 626 } 627 628 static void ToJSMap(JSContext* aCx, nsTArray<JSIPCMapEntry>&& aEntries, 629 JS::MutableHandle<JS::Value> aOut, ErrorResult& aError) { 630 JS::Rooted<JSObject*> mapObject(aCx, JS::NewMapObject(aCx)); 631 if (!mapObject) { 632 aError.NoteJSContextException(aCx); 633 return; 634 } 635 636 JS::Rooted<JS::Value> key(aCx); 637 JS::Rooted<JS::Value> value(aCx); 638 for (auto&& e : aEntries) { 639 JSIPCValueUtils::ToJSVal(aCx, std::move(e.key()), &key, aError); 640 if (aError.Failed()) { 641 return; 642 } 643 JSIPCValueUtils::ToJSVal(aCx, std::move(e.value()), &value, aError); 644 if (aError.Failed()) { 645 return; 646 } 647 if (!JS::MapSet(aCx, mapObject, key, value)) { 648 aError.NoteJSContextException(aCx); 649 return; 650 } 651 } 652 653 aOut.setObject(*mapObject); 654 } 655 656 // Copied from JSActorManager.cpp. 657 #define CHILD_DIAGNOSTIC_ASSERT(test, msg) \ 658 do { \ 659 if (XRE_IsParentProcess()) { \ 660 MOZ_ASSERT(test, msg); \ 661 } else { \ 662 MOZ_DIAGNOSTIC_ASSERT(test, msg); \ 663 } \ 664 } while (0) 665 666 static void UntypedToJSVal(JSContext* aCx, ipc::StructuredCloneData& aData, 667 JS::MutableHandle<JS::Value> aOut, 668 ErrorResult& aError) { 669 JS::Rooted<JS::Value> dataValue(aCx); 670 aData.Read(aCx, &dataValue, aError); 671 // StructuredCloneHolder populates an array of ports for MessageEvent.ports 672 // which we don't need, but which StructuredCloneHolder's destructor will 673 // assert on for thread safety reasons (that do not apply in this case) if 674 // we do not consume the array. It's possible for the Read call above to 675 // populate this array even in event of an error, so we must consume the 676 // array before processing the error. 677 nsTArray<RefPtr<MessagePort>> ports = aData.TakeTransferredPorts(); 678 // Cast to void so that the ports will actually be moved, and then 679 // discarded. 680 (void)ports; 681 if (aError.Failed()) { 682 CHILD_DIAGNOSTIC_ASSERT(CycleCollectedJSRuntime::Get()->OOMReported(), 683 "Should not receive non-decodable data"); 684 return; 685 } 686 687 aOut.set(dataValue); 688 } 689 690 void JSIPCValueUtils::ToJSVal(JSContext* aCx, JSIPCValue&& aIn, 691 JS::MutableHandle<JS::Value> aOut, 692 ErrorResult& aError) { 693 js::AutoCheckRecursionLimit recursion(aCx); 694 if (!recursion.check(aCx)) { 695 aError.NoteJSContextException(aCx); 696 return; 697 } 698 699 switch (aIn.type()) { 700 case JSIPCValue::Tvoid_t: 701 aOut.setUndefined(); 702 return; 703 704 case JSIPCValue::Tnull_t: 705 aOut.setNull(); 706 return; 707 708 case JSIPCValue::TnsString: { 709 JS::Rooted<JS::Value> stringVal(aCx); 710 if (!xpc::StringToJsval(aCx, aIn.get_nsString(), &stringVal)) { 711 aError.NoteJSContextException(aCx); 712 return; 713 } 714 aOut.set(stringVal); 715 return; 716 } 717 718 case JSIPCValue::Tbool: 719 aOut.setBoolean(aIn.get_bool()); 720 return; 721 722 case JSIPCValue::Tdouble: 723 aOut.setDouble(aIn.get_double()); 724 return; 725 726 case JSIPCValue::Tint32_t: 727 aOut.setInt32(aIn.get_int32_t()); 728 return; 729 730 case JSIPCValue::TnsIPrincipal: { 731 JS::Rooted<JS::Value> result(aCx); 732 nsCOMPtr<nsIPrincipal> principal = aIn.get_nsIPrincipal(); 733 if (!ToJSValue(aCx, principal, &result)) { 734 aError.NoteJSContextException(aCx); 735 return; 736 } 737 aOut.set(result); 738 return; 739 } 740 741 case JSIPCValue::TMaybeDiscardedBrowsingContext: { 742 JS::Rooted<JS::Value> result(aCx, JS::NullValue()); 743 const MaybeDiscardedBrowsingContext& bc = 744 aIn.get_MaybeDiscardedBrowsingContext(); 745 746 // Succeed with the value null if the BC is discarded, to match the 747 // behavior of BrowsingContext::ReadStructuredClone(). 748 if (!bc.IsNullOrDiscarded()) { 749 if (!ToJSValue(aCx, bc.get(), &result)) { 750 aError.NoteJSContextException(aCx); 751 return; 752 } 753 if (!result.isObject()) { 754 aError.ThrowInvalidStateError( 755 "Non-object when wrapping BrowsingContext"); 756 return; 757 } 758 } 759 aOut.set(result); 760 return; 761 } 762 763 case JSIPCValue::TJSIPCDOMRect: { 764 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); 765 if (!global) { 766 aError.ThrowInvalidStateError("No global"); 767 return; 768 } 769 const JSIPCDOMRect& idr = aIn.get_JSIPCDOMRect(); 770 RefPtr<DOMRect> domRect = 771 new DOMRect(global, idr.x(), idr.y(), idr.width(), idr.height()); 772 JS::Rooted<JS::Value> result(aCx, JS::NullValue()); 773 if (!ToJSValue(aCx, domRect, &result)) { 774 aError.NoteJSContextException(aCx); 775 return; 776 } 777 aOut.set(result); 778 return; 779 } 780 781 case JSIPCValue::TArrayOfJSIPCProperty: 782 return ToJSObject(aCx, std::move(aIn.get_ArrayOfJSIPCProperty()), aOut, 783 aError); 784 785 case JSIPCValue::TJSIPCArray: 786 return ToJSArray(aCx, std::move(aIn.get_JSIPCArray().elements()), aOut, 787 aError); 788 789 case JSIPCValue::TJSIPCSet: 790 return ToJSSet(aCx, std::move(aIn.get_JSIPCSet().elements()), aOut, 791 aError); 792 793 case JSIPCValue::TArrayOfJSIPCMapEntry: 794 return ToJSMap(aCx, std::move(aIn.get_ArrayOfJSIPCMapEntry()), aOut, 795 aError); 796 797 case JSIPCValue::TStructuredCloneData: { 798 return UntypedToJSVal(aCx, *aIn.get_StructuredCloneData(), aOut, aError); 799 } 800 801 case JSIPCValue::TClonedMessageData: { 802 ipc::StructuredCloneData data; 803 data.BorrowFromClonedMessageData(*aIn.get_ClonedMessageData()); 804 return UntypedToJSVal(aCx, data, aOut, aError); 805 } 806 807 default: 808 MOZ_ASSERT_UNREACHABLE("Invalid unhandled case"); 809 aError.ThrowInvalidStateError("Invalid unhandled case"); 810 return; 811 } 812 } 813 814 } // namespace mozilla::dom