Bug 1391405: Part 5 - Add helper for retrieving the enumerable value properties of a cross-compartment object. r?bholley draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 18 Aug 2017 11:13:55 -0700
changeset 649177 7f7d5df605bc6544cb7f1c0c7e224d81b211e09c
parent 649176 eef7eb12615faaf6fa9adc87685ef74be9413a0c
child 649178 1ffb3fdc2688052d768264f105492199e3ced598
push id74977
push usermaglione.k@gmail.com
push dateFri, 18 Aug 2017 18:50:35 +0000
reviewersbholley
bugs1391405
milestone57.0a1
Bug 1391405: Part 5 - Add helper for retrieving the enumerable value properties of a cross-compartment object. r?bholley As part of the normalization process for WebExtension API calls, we need to extract and validate the full set of value properties (including properties X-rays would normally deny access to) from cross-compartment objects. This currently involves waiving X-rays, enumerating property descriptors, and unwaiving X-rays - all through X-ray wrappers and waivers - and generating a lot of expensive and short-lived wrappers in-between. This helper reads out the list of safe properties from within the object's compartment, and then copies them over to an object in the target compartment, without any X-ray overhead, or any unnecessary intermediate wrappers or compartment switches. It cuts about 40% off the overhead of our normalization code. MozReview-Commit-ID: H582oAYocaX
dom/base/ChromeUtils.cpp
dom/base/ChromeUtils.h
dom/webidl/ChromeUtils.webidl
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -172,16 +172,98 @@ ChromeUtils::GetClassName(GlobalObject& 
   if (aUnwrap) {
     obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
   }
 
   aRetval = NS_ConvertUTF8toUTF16(nsDependentCString(js::GetObjectClass(obj)->name));
 }
 
 /* static */ void
+ChromeUtils::ShallowClone(GlobalObject& aGlobal,
+                          JS::HandleObject aObj,
+                          JS::HandleObject aTarget,
+                          JS::MutableHandleObject aRetval,
+                          ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.Context();
+
+  auto cleanup = MakeScopeExit([&] () {
+    aRv.NoteJSContextException(cx);
+  });
+
+  JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
+  JS::AutoValueVector values(cx);
+
+  {
+    JS::RootedObject obj(cx, js::CheckedUnwrap(aObj));
+    if (!obj) {
+      js::ReportAccessDenied(cx);
+      return;
+    }
+    JSAutoCompartment ac(cx, obj);
+
+    if (!JS_Enumerate(cx, obj, &ids) ||
+        !values.reserve(ids.length())) {
+      return;
+    }
+
+    JS::Rooted<JS::PropertyDescriptor> desc(cx);
+    JS::RootedId id(cx);
+    for (jsid idVal : ids) {
+      id = idVal;
+      if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) {
+        continue;
+      }
+      if (desc.setter() || desc.getter()) {
+        continue;
+      }
+      values.infallibleAppend(desc.value());
+    }
+  }
+
+  JS::RootedObject obj(cx);
+  {
+    Maybe<JSAutoCompartment> ac;
+    if (aTarget) {
+      JS::RootedObject target(cx, js::CheckedUnwrap(aTarget));
+      if (!target) {
+        js::ReportAccessDenied(cx);
+        return;
+      }
+      ac.emplace(cx, target);
+    }
+
+    obj = JS_NewPlainObject(cx);
+    if (!obj) {
+      return;
+    }
+
+    JS::RootedValue value(cx);
+    JS::RootedId id(cx);
+    for (uint32_t i = 0; i < ids.length(); i++) {
+      id = ids[i];
+      value = values[i];
+
+      JS_MarkCrossZoneId(cx, id);
+      if (!JS_WrapValue(cx, &value) ||
+          !JS_SetPropertyById(cx, obj, id, value)) {
+        return;
+      }
+    }
+  }
+
+  if (aTarget && !JS_WrapObject(cx, &obj)) {
+    return;
+  }
+
+  cleanup.release();
+  aRetval.set(obj);
+}
+
+/* static */ void
 ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
                                       const dom::OriginAttributesDictionary& aAttrs,
                                       nsCString& aSuffix)
 
 {
   OriginAttributes attrs(aAttrs);
   attrs.CreateSuffix(aSuffix);
 }
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -134,14 +134,20 @@ public:
                            JS::HandleValue aVal,
                            JS::MutableHandleValue aRetval,
                            ErrorResult& aRv);
 
   static void GetClassName(GlobalObject& aGlobal,
                            JS::HandleObject aObj,
                            bool aUnwrap,
                            nsAString& aRetval);
+
+  static void ShallowClone(GlobalObject& aGlobal,
+                           JS::HandleObject aObj,
+                           JS::HandleObject aTarget,
+                           JS::MutableHandleObject aRetval,
+                           ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -85,16 +85,27 @@ interface ChromeUtils : ThreadSafeChrome
   /**
    * Gets the name of the JSClass of the object.
    *
    * if |unwrap| is true, all wrappers are unwrapped first. Unless you're
    * specifically trying to detect whether the object is a proxy, this is
    * probably what you want.
    */
   static DOMString getClassName(object obj, optional boolean unwrap = true);
+
+  /**
+   * Clones the properties of the given object into a new object in the given
+   * target compartment (or the caller compartment if no target is provided).
+   * Property values themeselves are not cloned.
+   *
+   * Ignores non-enumerable properties, properties on prototypes, and properties
+   * with getters or setters.
+   */
+  [Throws]
+  static object shallowClone(object obj, optional object? target = null);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *