tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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