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
--- 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).
*