ToJSValue.h (17319B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_ToJSValue_h 8 #define mozilla_dom_ToJSValue_h 9 10 #include <cstddef> // for size_t 11 #include <cstdint> // for int32_t, int64_t, uint32_t, uint64_t 12 #include <type_traits> // for is_base_of, enable_if_t, enable_if, is_pointer, is_same, void_t 13 14 #include "ErrorList.h" // for nsresult 15 #include "js/Array.h" // for NewArrayObject 16 #include "js/GCVector.h" // for RootedVector, MutableWrappedPtrOperations 17 #include "js/PropertyAndElement.h" // JS_DefineUCProperty 18 #include "js/RootingAPI.h" // for MutableHandle, Rooted, Handle, Heap 19 #include "js/Value.h" // for Value 20 #include "js/ValueArray.h" // for HandleValueArray 21 #include "jsapi.h" // for CurrentGlobalOrNull 22 #include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1 23 #include "mozilla/UniquePtr.h" // for UniquePtr 24 #include "mozilla/dom/BindingUtils.h" // for MaybeWrapValue, MaybeWrapObjectOrNullValue, XPCOMObjectToJsval, GetOrCreateDOMReflector 25 #include "mozilla/dom/CallbackObject.h" // for CallbackObject 26 #include "mozilla/dom/Record.h" 27 #include "nsID.h" // for NS_GET_IID, nsIID 28 #include "nsISupports.h" // for nsISupports 29 #include "nsStringFwd.h" // for nsAString 30 #include "nsTArrayForwardDeclare.h" 31 #include "xpcObjectHelper.h" // for xpcObjectHelper 32 33 namespace mozilla::dom { 34 35 class CallbackObject; 36 class Promise; 37 class WindowProxyHolder; 38 template <typename TypedArrayType> 39 class TypedArrayCreator; 40 41 // If ToJSValue returns false, it must set an exception on the 42 // JSContext. 43 44 // Accept strings. 45 [[nodiscard]] bool ToJSValue(JSContext* aCx, const nsAString& aArgument, 46 JS::MutableHandle<JS::Value> aValue); 47 48 // Treats the input as UTF-8, and throws otherwise. 49 [[nodiscard]] bool ToJSValue(JSContext* aCx, const nsACString& aArgument, 50 JS::MutableHandle<JS::Value> aValue); 51 52 // Accept booleans. But be careful here: if we just have a function that takes 53 // a boolean argument, then any pointer that doesn't match one of our other 54 // signatures/templates will get treated as a boolean, which is clearly not 55 // desirable. So make this a template that only gets used if the argument type 56 // is actually boolean 57 template <typename T> 58 [[nodiscard]] std::enable_if_t<std::is_same<T, bool>::value, bool> ToJSValue( 59 JSContext* aCx, T aArgument, JS::MutableHandle<JS::Value> aValue) { 60 // Make sure we're called in a compartment 61 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 62 63 aValue.setBoolean(aArgument); 64 return true; 65 } 66 67 // Accept integer types 68 inline bool ToJSValue(JSContext* aCx, int32_t aArgument, 69 JS::MutableHandle<JS::Value> aValue) { 70 // Make sure we're called in a compartment 71 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 72 73 aValue.setInt32(aArgument); 74 return true; 75 } 76 77 inline bool ToJSValue(JSContext* aCx, uint32_t aArgument, 78 JS::MutableHandle<JS::Value> aValue) { 79 // Make sure we're called in a compartment 80 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 81 82 aValue.setNumber(aArgument); 83 return true; 84 } 85 86 inline bool ToJSValue(JSContext* aCx, int64_t aArgument, 87 JS::MutableHandle<JS::Value> aValue) { 88 // Make sure we're called in a compartment 89 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 90 91 aValue.setNumber(double(aArgument)); 92 return true; 93 } 94 95 inline bool ToJSValue(JSContext* aCx, uint64_t aArgument, 96 JS::MutableHandle<JS::Value> aValue) { 97 // Make sure we're called in a compartment 98 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 99 100 aValue.setNumber(double(aArgument)); 101 return true; 102 } 103 104 // accept floating point types 105 inline bool ToJSValue(JSContext* aCx, float aArgument, 106 JS::MutableHandle<JS::Value> aValue) { 107 // Make sure we're called in a compartment 108 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 109 110 aValue.setNumber(aArgument); 111 return true; 112 } 113 114 inline bool ToJSValue(JSContext* aCx, double aArgument, 115 JS::MutableHandle<JS::Value> aValue) { 116 // Make sure we're called in a compartment 117 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 118 119 aValue.set(JS_NumberValue(aArgument)); 120 return true; 121 } 122 123 // Accept CallbackObjects 124 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, CallbackObject& aArgument, 125 JS::MutableHandle<JS::Value> aValue) { 126 // Make sure we're called in a compartment 127 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 128 129 aValue.setObjectOrNull(aArgument.Callback(aCx)); 130 131 return MaybeWrapValue(aCx, aValue); 132 } 133 134 // Accept objects that inherit from nsWrapperCache (e.g. most 135 // DOM objects). 136 template <class T> 137 [[nodiscard]] std::enable_if_t<std::is_base_of<nsWrapperCache, T>::value, bool> 138 ToJSValue(JSContext* aCx, T& aArgument, JS::MutableHandle<JS::Value> aValue) { 139 // Make sure we're called in a compartment 140 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 141 142 return GetOrCreateDOMReflector(aCx, aArgument, aValue); 143 } 144 145 // Accept non-refcounted DOM objects that do not inherit from 146 // nsWrapperCache. Refcounted ones would be too much of a footgun: 147 // you could convert them to JS twice and get two different objects. 148 namespace binding_detail { 149 template <class T> 150 [[nodiscard]] std::enable_if_t< 151 std::is_base_of<NonRefcountedDOMObject, T>::value, bool> 152 ToJSValueFromPointerHelper(JSContext* aCx, T* aArgument, 153 JS::MutableHandle<JS::Value> aValue) { 154 // Make sure we're called in a compartment 155 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 156 157 // This is a cut-down version of 158 // WrapNewBindingNonWrapperCachedObject that doesn't need to deal 159 // with nearly as many cases. 160 if (!aArgument) { 161 aValue.setNull(); 162 return true; 163 } 164 165 JS::Rooted<JSObject*> obj(aCx); 166 if (!aArgument->WrapObject(aCx, nullptr, &obj)) { 167 return false; 168 } 169 170 aValue.setObject(*obj); 171 return true; 172 } 173 } // namespace binding_detail 174 175 // We can take a non-refcounted non-wrapper-cached DOM object that lives in a 176 // UniquePtr. 177 template <class T> 178 [[nodiscard]] std::enable_if_t< 179 std::is_base_of<NonRefcountedDOMObject, T>::value, bool> 180 ToJSValue(JSContext* aCx, UniquePtr<T>&& aArgument, 181 JS::MutableHandle<JS::Value> aValue) { 182 if (!binding_detail::ToJSValueFromPointerHelper(aCx, aArgument.get(), 183 aValue)) { 184 return false; 185 } 186 187 // JS object took ownership 188 (void)aArgument.release(); 189 return true; 190 } 191 192 // Accept typed arrays built from appropriate nsTArray values 193 template <typename T> 194 [[nodiscard]] 195 typename std::enable_if<std::is_base_of<AllTypedArraysBase, T>::value, 196 bool>::type 197 ToJSValue(JSContext* aCx, const TypedArrayCreator<T>& aArgument, 198 JS::MutableHandle<JS::Value> aValue) { 199 // Make sure we're called in a compartment 200 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 201 202 JSObject* obj = aArgument.Create(aCx); 203 if (!obj) { 204 return false; 205 } 206 aValue.setObject(*obj); 207 return true; 208 } 209 210 namespace binding_detail { 211 // Helper type alias for picking a script-exposable non-wrappercached XPIDL 212 // interface to expose to JS code. Falls back to `nsISupports` if the specific 213 // interface type is ambiguous. 214 template <typename T, typename = void> 215 struct GetScriptableInterfaceType { 216 using Type = nsISupports; 217 218 static_assert(std::is_base_of_v<nsISupports, T>, 219 "T must inherit from nsISupports"); 220 }; 221 template <typename T> 222 struct GetScriptableInterfaceType< 223 T, std::void_t<typename T::ScriptableInterfaceType>> { 224 using Type = typename T::ScriptableInterfaceType; 225 226 static_assert(std::is_base_of_v<Type, T>, 227 "T must inherit from ScriptableInterfaceType"); 228 static_assert(std::is_base_of_v<nsISupports, Type>, 229 "ScriptableInterfaceType must inherit from nsISupports"); 230 }; 231 232 template <typename T> 233 using ScriptableInterfaceType = typename GetScriptableInterfaceType<T>::Type; 234 } // namespace binding_detail 235 236 // Accept objects that inherit from nsISupports but not nsWrapperCache (e.g. 237 // DOM File). 238 template <class T> 239 [[nodiscard]] std::enable_if_t<!std::is_base_of<nsWrapperCache, T>::value && 240 !std::is_base_of<CallbackObject, T>::value && 241 std::is_base_of<nsISupports, T>::value, 242 bool> 243 ToJSValue(JSContext* aCx, T& aArgument, JS::MutableHandle<JS::Value> aValue) { 244 // Make sure we're called in a compartment 245 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 246 247 xpcObjectHelper helper(ToSupports(&aArgument)); 248 JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx)); 249 const nsIID& iid = NS_GET_IID(binding_detail::ScriptableInterfaceType<T>); 250 return XPCOMObjectToJsval(aCx, scope, helper, &iid, true, aValue); 251 } 252 253 [[nodiscard]] bool ToJSValue(JSContext* aCx, const WindowProxyHolder& aArgument, 254 JS::MutableHandle<JS::Value> aValue); 255 256 // Accept nsRefPtr/nsCOMPtr 257 template <typename T> 258 [[nodiscard]] bool ToJSValue(JSContext* aCx, const nsCOMPtr<T>& aArgument, 259 JS::MutableHandle<JS::Value> aValue) { 260 return ToJSValue(aCx, *aArgument.get(), aValue); 261 } 262 263 template <typename T> 264 [[nodiscard]] bool ToJSValue(JSContext* aCx, const RefPtr<T>& aArgument, 265 JS::MutableHandle<JS::Value> aValue) { 266 return ToJSValue(aCx, *aArgument.get(), aValue); 267 } 268 269 template <typename T> 270 [[nodiscard]] bool ToJSValue(JSContext* aCx, const NonNull<T>& aArgument, 271 JS::MutableHandle<JS::Value> aValue) { 272 return ToJSValue(aCx, *aArgument.get(), aValue); 273 } 274 275 template <typename T> 276 [[nodiscard]] bool ToJSValue(JSContext* aCx, const OwningNonNull<T>& aArgument, 277 JS::MutableHandle<JS::Value> aValue) { 278 return ToJSValue(aCx, *aArgument.get(), aValue); 279 } 280 281 // Accept WebIDL dictionaries 282 template <class T> 283 [[nodiscard]] std::enable_if_t<is_dom_dictionary<T>, bool> ToJSValue( 284 JSContext* aCx, const T& aArgument, JS::MutableHandle<JS::Value> aValue) { 285 return aArgument.ToObjectInternal(aCx, aValue); 286 } 287 288 // Accept existing JS values (which may not be same-compartment with us 289 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, const JS::Value& aArgument, 290 JS::MutableHandle<JS::Value> aValue) { 291 aValue.set(aArgument); 292 return MaybeWrapValue(aCx, aValue); 293 } 294 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, 295 JS::Handle<JS::Value> aArgument, 296 JS::MutableHandle<JS::Value> aValue) { 297 aValue.set(aArgument); 298 return MaybeWrapValue(aCx, aValue); 299 } 300 301 // Accept existing JS values on the Heap (which may not be same-compartment with 302 // us 303 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, 304 const JS::Heap<JS::Value>& aArgument, 305 JS::MutableHandle<JS::Value> aValue) { 306 aValue.set(aArgument); 307 return MaybeWrapValue(aCx, aValue); 308 } 309 310 // Accept existing rooted JS values (which may not be same-compartment with us 311 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, 312 const JS::Rooted<JS::Value>& aArgument, 313 JS::MutableHandle<JS::Value> aValue) { 314 aValue.set(aArgument); 315 return MaybeWrapValue(aCx, aValue); 316 } 317 318 // Accept existing rooted JS objects (which may not be same-compartment with 319 // us). 320 [[nodiscard]] inline bool ToJSValue(JSContext* aCx, 321 const JS::Rooted<JSObject*>& aArgument, 322 JS::MutableHandle<JS::Value> aValue) { 323 aValue.setObjectOrNull(aArgument); 324 return MaybeWrapObjectOrNullValue(aCx, aValue); 325 } 326 327 // Accept nsresult, for use in rejections, and create an XPCOM 328 // exception object representing that nsresult. 329 [[nodiscard]] bool ToJSValue(JSContext* aCx, nsresult aArgument, 330 JS::MutableHandle<JS::Value> aValue); 331 332 // Accept ErrorResult, for use in rejections, and create an exception 333 // representing the failure. Note, the ErrorResult must indicate a failure 334 // with aArgument.Failure() returning true. 335 [[nodiscard]] bool ToJSValue(JSContext* aCx, ErrorResult&& aArgument, 336 JS::MutableHandle<JS::Value> aValue); 337 338 // Accept owning WebIDL unions. 339 template <typename T> 340 [[nodiscard]] std::enable_if_t<is_dom_owning_union<T>, bool> ToJSValue( 341 JSContext* aCx, const T& aArgument, JS::MutableHandle<JS::Value> aValue) { 342 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); 343 return aArgument.ToJSVal(aCx, global, aValue); 344 } 345 346 // Accept pointers to other things we accept 347 template <typename T> 348 [[nodiscard]] std::enable_if_t<std::is_pointer<T>::value, bool> ToJSValue( 349 JSContext* aCx, T aArgument, JS::MutableHandle<JS::Value> aValue) { 350 return ToJSValue(aCx, *aArgument, aValue); 351 } 352 353 // Accept Promise objects, which need special handling. 354 [[nodiscard]] bool ToJSValue(JSContext* aCx, Promise& aArgument, 355 JS::MutableHandle<JS::Value> aValue); 356 357 // Accept arrays (and nested arrays) of other things we accept 358 template <typename T> 359 [[nodiscard]] bool ToJSValue(JSContext* aCx, T* aArguments, size_t aLength, 360 JS::MutableHandle<JS::Value> aValue); 361 362 template <typename T> 363 [[nodiscard]] bool ToJSValue(JSContext* aCx, const nsTArray<T>& aArgument, 364 JS::MutableHandle<JS::Value> aValue) { 365 return ToJSValue(aCx, aArgument.Elements(), aArgument.Length(), aValue); 366 } 367 368 template <typename T> 369 [[nodiscard]] bool ToJSValue(JSContext* aCx, const FallibleTArray<T>& aArgument, 370 JS::MutableHandle<JS::Value> aValue) { 371 return ToJSValue(aCx, aArgument.Elements(), aArgument.Length(), aValue); 372 } 373 374 template <typename T, int N> 375 [[nodiscard]] bool ToJSValue(JSContext* aCx, const T (&aArgument)[N], 376 JS::MutableHandle<JS::Value> aValue) { 377 return ToJSValue(aCx, aArgument, N, aValue); 378 } 379 380 // Accept arrays of other things we accept 381 template <typename T> 382 [[nodiscard]] bool ToJSValue(JSContext* aCx, T* aArguments, size_t aLength, 383 JS::MutableHandle<JS::Value> aValue) { 384 // Make sure we're called in a compartment 385 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 386 387 JS::RootedVector<JS::Value> v(aCx); 388 if (!v.resize(aLength)) { 389 return false; 390 } 391 for (size_t i = 0; i < aLength; ++i) { 392 if (!ToJSValue(aCx, aArguments[i], v[i])) { 393 return false; 394 } 395 } 396 JSObject* arrayObj = JS::NewArrayObject(aCx, v); 397 if (!arrayObj) { 398 return false; 399 } 400 aValue.setObject(*arrayObj); 401 return true; 402 } 403 404 // Accept tuple of other things we accept. The result will be a JS array object. 405 template <typename... Elements> 406 [[nodiscard]] bool ToJSValue(JSContext* aCx, 407 const std::tuple<Elements...>& aArguments, 408 JS::MutableHandle<JS::Value> aValue) { 409 // Make sure we're called in a compartment 410 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); 411 412 JS::RootedVector<JS::Value> v(aCx); 413 if (!v.resize(sizeof...(Elements))) { 414 return false; 415 } 416 bool ok = true; 417 size_t i = 0; 418 auto Callable = [aCx, &ok, &v, &i](auto& aElem) { 419 ok = ok && ToJSValue(aCx, aElem, v[i++]); 420 }; 421 std::apply([Callable](auto&&... args) { (Callable(args), ...); }, aArguments); 422 423 if (!ok) { 424 return false; 425 } 426 JSObject* arrayObj = JS::NewArrayObject(aCx, v); 427 if (!arrayObj) { 428 return false; 429 } 430 aValue.setObject(*arrayObj); 431 return true; 432 } 433 434 // Accept records of other things we accept. N.B. This assumes that 435 // keys are either UTF-8 or UTF-16-ish. See Bug 1706058. 436 template <typename K, typename V> 437 [[nodiscard]] bool ToJSValue(JSContext* aCx, const Record<K, V>& aArgument, 438 JS::MutableHandle<JS::Value> aValue) { 439 JS::Rooted<JSObject*> recordObj(aCx, JS_NewPlainObject(aCx)); 440 if (!recordObj) { 441 return false; 442 } 443 444 for (auto& entry : aArgument.Entries()) { 445 JS::Rooted<JS::Value> value(aCx); 446 if (!ToJSValue(aCx, entry.mValue, &value)) { 447 return false; 448 } 449 450 if constexpr (std::is_same_v<nsCString, decltype(entry.mKey)>) { 451 NS_ConvertUTF8toUTF16 expandedKey(entry.mKey); 452 if (!JS_DefineUCProperty(aCx, recordObj, expandedKey.BeginReading(), 453 expandedKey.Length(), value, JSPROP_ENUMERATE)) { 454 return false; 455 } 456 } else { 457 if (!JS_DefineUCProperty(aCx, recordObj, entry.mKey.BeginReading(), 458 entry.mKey.Length(), value, JSPROP_ENUMERATE)) { 459 return false; 460 } 461 } 462 } 463 464 aValue.setObject(*recordObj); 465 return true; 466 } 467 468 template <typename T> 469 [[nodiscard]] bool ToJSValue(JSContext* aCx, const Nullable<T>& aArgument, 470 JS::MutableHandle<JS::Value> aValue) { 471 if (aArgument.IsNull()) { 472 aValue.setNull(); 473 return true; 474 } 475 476 return ToJSValue(aCx, aArgument.Value(), aValue); 477 } 478 479 } // namespace mozilla::dom 480 481 #endif /* mozilla_dom_ToJSValue_h */