new file mode 100644
--- /dev/null
+++ b/js/src/builtin/MeasureObject.cpp
@@ -0,0 +1,805 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/MeasureObject.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/perfstream/Measure.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Vector.h"
+
+#include <string.h>
+
+#include "jsapi.h"
+#include "jsarray.h"
+#include "jscntxt.h"
+#include "jsprf.h"
+#include "jsstr.h"
+
+#include "gc/Heap.h"
+#include "gc/Rooting.h"
+#include "js/CharacterEncoding.h"
+#include "js/Class.h"
+#include "js/Measure.h"
+#include "js/Utility.h"
+#include "js/Vector.h"
+#include "vm/GlobalObject.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+using mozilla::Maybe;
+using mozilla::perfstream::Description;
+using mozilla::perfstream::Group;
+using mozilla::perfstream::GroupPtr;
+using mozilla::perfstream::GroupVector;
+using mozilla::perfstream::Measure;
+using mozilla::perfstream::MeasurePtr;
+using mozilla::perfstream::MeasureVector;
+using mozilla::Nothing;
+using mozilla::Some;
+using mozilla::UniqueFreePtr;
+
+namespace js {
+
+
+/* Shared utility functions. */
+
+static UniqueChars
+ToUTF8(JSContext* cx, HandleValue v)
+{
+ RootedString str(cx, ToString<CanGC>(cx, v));
+ if (!str)
+ return nullptr;
+ return UniqueChars(JS_EncodeStringToUTF8(cx, str));
+}
+
+static bool
+UTF8ToString(JSContext* cx, const char* utf8, MutableHandleValue result)
+{
+ JS::ConstUTF8CharsZ chars(utf8, strlen(utf8));
+ RootedString string(cx, JS_NewStringCopyUTF8Z(cx, chars));
+ if (!string)
+ return false;
+
+ result.setString(string);
+ return true;
+}
+
+static bool
+ReportNoPath(JSContext* cx, HandleValue path)
+{
+ RootedString source(cx, ValueToSource(cx, path));
+ if (!source)
+ return false;
+ UniqueChars utf8(JS_EncodeStringToUTF8(cx, source));
+ if (!source)
+ return false;
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_MEASURE_NO_PATH, utf8.get());
+ return false;
+}
+
+/*
+ * Check the type of a path argument to a Measure or MeasureGroup constructor,
+ * retrieve its elements, and convert them to strings. On success, return a
+ * vector of null-terminated UTF-8 strings.
+ *
+ * On failure, report the problem on cx and return Nothing().
+ */
+static Maybe<js::Vector<UniqueChars>>
+GetPathElementsAsUTF8(JSContext* cx, HandleValue path)
+{
+ if (!path.isObject() || !path.toObject().is<ArrayObject>()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, path, nullptr, "not an array",
+ nullptr);
+ return Nothing();
+ }
+
+ // Get the array's elements as a vector of Values.
+ RootedArrayObject pathArray(cx, &path.toObject().as<ArrayObject>());
+ uint32_t length;
+ if (!GetLengthProperty(cx, pathArray, &length))
+ return Nothing();
+
+ Rooted<ValueVector> pathValues(cx, ValueVector(cx));
+ if (!pathValues.resize(length) || !GetElements(cx, pathArray, length, pathValues.begin()))
+ return Nothing();
+
+ js::Vector<UniqueChars> pathUTF8(cx);
+ if (!pathUTF8.reserve(pathValues.length()))
+ return Nothing();
+
+ for (size_t i = 0; i < pathValues.length(); i++) {
+ UniqueChars utf8 = ToUTF8(cx, pathValues[i]);
+ if (!utf8)
+ return Nothing();
+ pathUTF8.infallibleAppend(Move(utf8));
+ }
+
+ return Some(Move(pathUTF8));
+}
+
+/*
+ * Follow the first length elements of path from start and return the group
+ * there. pathValue is the JavaScript representation of the path, for use in
+ * error messages.
+ *
+ * If there is no such group, or some other sort of error arises, return nullptr
+ * and report an error on cx.
+ */
+static GroupPtr
+FollowGroupPath(JSContext* cx, const Group& start,
+ Vector<UniqueChars>& path, size_t length,
+ HandleValue pathValue)
+{
+ GroupPtr here = start.Clone();
+
+ // Note that length may not be the entire path, so using C++ iteration isn't
+ // appropriate here.
+ for (size_t i = 0; i < length; i++) {
+ auto next = here->GetSubgroup(path[i].get());
+ if (!next) {
+ ReportNoPath(cx, pathValue);
+ return nullptr;
+ }
+
+ here = Move(*next);
+ }
+
+ return here;
+}
+
+static bool
+GetMetadata(JSContext* cx, const Description& description, HandleValue name, MutableHandleValue result)
+{
+ RootedString nameString(cx, ToString<CanGC>(cx, name));
+ if (!nameString)
+ return false;
+ UniqueChars nameUtf8(JS_EncodeStringToUTF8(cx, nameString));
+ if (!nameUtf8)
+ return false;
+ size_t nameLength = strlen(nameUtf8.get());
+
+ for (const char* const* entry = description.mMetadata; *entry; entry++) {
+ if (strncmp(*entry, nameUtf8.get(), nameLength) == 0 &&
+ strncmp(*entry + nameLength, ": ", 2) == 0)
+ {
+ const char* value = *entry + nameLength + 2;
+ return UTF8ToString(cx, value, result);
+ }
+ }
+
+ result.setUndefined();
+ return true;
+}
+
+static bool
+CommonToString(JSContext* cx, const char* className, const Description& description,
+ MutableHandleValue result)
+{
+ UniqueChars cString = JS_smprintf("[object %s %s]", className, description.mIdentifier);
+ if (!cString)
+ return false;
+ return UTF8ToString(cx, cString.get(), result);
+}
+
+// Generic |this| and argument checking for MeasureObject, MeasureGroupObject.
+template<typename ThisObject>
+class MeasureArgs MOZ_STACK_CLASS {
+ public:
+ MeasureArgs(JSContext* cx, CallArgs args, const char *fnName)
+ : cx_(cx),
+ args(args),
+ fnName(fnName),
+ object_(cx),
+ prototype_(cx)
+ { }
+
+ // Check that JS call's |this| value is a usable instance, and not a
+ // prototype. Otherwise, throw a JavaScript exception on |cx_|.
+ bool check() { return checkThis(); }
+
+ // As with |check()|, but throw if we have not been passed `n` arguments.
+ bool requireAtLeast(size_t n) {
+ return checkThis() && args.requireAtLeast(cx_, fnName, n);
+ }
+
+ // Return the JavaScript |this| object, properly typed.
+ ThisObject& object() const { return *object_; }
+
+ // Return the JavaScript |this| object's prototype.
+ Handle<typename ThisObject::Prototype*> prototype() const { return prototype_; }
+
+ JSContext* cx() const { return cx_; }
+
+ // Pass through some methods from our CallArgs member.
+ MutableHandleValue operator[](unsigned i) const { return args[i]; }
+ MutableHandleValue rval() const { return args.rval(); }
+
+ private:
+ JSContext* cx_;
+ CallArgs args;
+ const char* fnName;
+ Rooted<ThisObject*> object_;
+ Rooted<typename ThisObject::Prototype*> prototype_;
+
+ // Check that args.this() is a usable ThisObject instance, and not some other
+ // kind of value or prototype. On failure, return false and report an error
+ // on cx_.
+ MOZ_MUST_USE bool checkThis() {
+ const Value& thisValue = args.thisv();
+
+ if (!thisValue.isObject()) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ InformalValueTypeName(thisValue));
+ return false;
+ }
+
+ JSObject& thisObject = thisValue.toObject();
+ if (!thisObject.is<ThisObject>()) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ ThisObject::class_.name, fnName,
+ thisObject.getClass()->name);
+ return false;
+ }
+
+ object_ = &thisObject.as<ThisObject>();
+
+ // ThisObject is never a proxy class, so this should always succeed.
+ RootedObject untypedProto(cx_);
+ if (!GetPrototype(cx_, object_, &untypedProto))
+ MOZ_CRASH("Getting prototype of Measure or MeasureGroup should never fail");
+ if (!untypedProto->is<typename ThisObject::Prototype>()) {
+ ReportValueError(cx_, JSMSG_BAD_PROTOTYPE_TYPE, -1, args.thisv(), nullptr);
+ return false;
+ }
+ prototype_ = &untypedProto->as<typename ThisObject::Prototype>();
+
+ return true;
+ }
+};
+
+
+/* The Measure.prototype object. */
+
+const ClassOps MeasurePrototypeObject::classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize,
+};
+
+const Class MeasurePrototypeObject::class_ = {
+ "MeasurePrototype",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &classOps
+};
+
+/* static */ MeasurePrototypeObject*
+MeasurePrototypeObject::create(JSContext* cx, Handle<GlobalObject*> global, GroupPtr&& root)
+{
+ Rooted<NativeObject*> obj(cx, GlobalObject::createBlankPrototype(cx, global, &class_));
+ if (!obj)
+ return nullptr;
+
+ // No fallible operations permitted beyond this point, to make sure we won't
+ // leak root, and that our finalize operation sees only fully-initialized
+ // instances.
+ Rooted<MeasurePrototypeObject*> proto(cx, &obj->as<MeasurePrototypeObject>());
+ RootedValue rootPrivate(cx, PrivateValue(root.release()));
+ proto->initReservedSlot(RootGroupSlot, rootPrivate);
+
+ return proto;
+}
+
+/* static */ void
+MeasurePrototypeObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onActiveCooperatingThread());
+
+ // To make sure that we free the memory with the same allocator used to
+ // create the Group in the first place, free group by creating a GroupPtr
+ // and then dropping it.
+ Group& group = obj->as<MeasurePrototypeObject>().rootGroup();
+ GroupPtr unique(&group);
+}
+
+
+/* The MeasureGroup object. */
+
+const ClassSpec MeasureGroupObject::classSpec = {
+ GenericCreateConstructor<construct, 0, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype,
+ nullptr,
+ nullptr,
+ methods,
+ properties,
+};
+
+const ClassOps MeasureGroupObject::classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize,
+};
+
+const Class MeasureGroupObject::class_ = {
+ "MeasureGroup",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &classOps,
+ &classSpec
+};
+
+const JSPropertySpec MeasureGroupObject::properties[] = {
+ JS_PSG("identifier", identifierGetter, 0),
+ JS_PS_END
+};
+
+const JSFunctionSpec MeasureGroupObject::methods[] = {
+ JS_FN("metadata", metadataMethod, 0, 0),
+ JS_FN("measures", measuresMethod, 0, 0),
+ JS_FN("subgroups", subgroupsMethod, 0, 0),
+ JS_FN(js_toString_str, toStringMethod, 0, 0),
+ JS_FS_END
+};
+
+/* static */ MeasureGroupObject*
+MeasureGroupObject::create(JSContext* cx,
+ Handle<MeasureGroupObject*> groupProto,
+ Handle<MeasurePrototypeObject*> measureProto,
+ mozilla::perfstream::GroupPtr&& group)
+{
+ assertSameCompartment(cx, groupProto, measureProto);
+
+ JSObject *obj = NewObjectWithGivenProto(cx, &class_, groupProto);
+ if (!obj)
+ return nullptr;
+
+ // No fallible operations permitted beyond this point, lest we leak group or
+ // allow our finalize method to see an incompletely initialized instance.
+ Rooted<MeasureGroupObject*> groupObj(cx, &obj->as<MeasureGroupObject>());
+ groupObj->initReservedSlot(GroupSlot, PrivateValue(group.release()));
+ groupObj->initReservedSlot(MeasureProtoSlot, ObjectValue(*measureProto));
+
+ return groupObj;
+}
+
+/* static */ void
+MeasureGroupObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onActiveCooperatingThread());
+
+ // To make sure that we free the memory with the same allocator used to
+ // create the Group in the first place, free group by creating a GroupPtr
+ // and then dropping it.
+ Group& group = obj->as<MeasureGroupObject>().measureGroup();
+ GroupPtr unique(&group);
+}
+
+/* static */ bool
+MeasureGroupObject::DeclareConstructor(JSContext* cx,
+ Handle<GlobalObject*> global,
+ Handle<MeasurePrototypeObject*> measureProto,
+ mozilla::perfstream::GroupPtr&& root)
+{
+ // Find Object.prototype, to serve as MeasureGroup.prototype's prototype.
+ RootedNativeObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
+ if (!objProto)
+ return false;
+
+ // Create the prototype and constructor, link them together, define them on
+ // the global, and so on.
+ RootedNativeObject groupProtoObj(cx, InitClass(cx, global, objProto,
+ &class_, construct, 1, properties, methods,
+ nullptr, nullptr));
+ if (!groupProtoObj)
+ return false;
+
+ // No fallible operations permitted beyond this point, to ensure our
+ // finalize method sees only fully initialized instances, and to avoid
+ // leaking root.
+ Rooted<MeasureGroupObject*> groupProto(cx, &groupProtoObj->as<MeasureGroupObject>());
+ groupProto->initReservedSlot(GroupSlot, PrivateValue(root.release()));
+ groupProto->initReservedSlot(MeasureProtoSlot, ObjectValue(*measureProto));
+
+ return true;
+}
+
+/*
+ * With no arguments, the MeasureGroup constructor returns a MeasureGroup
+ * referring to the root of the measure tree. Passed an array of strings, the
+ * constructor treats that as a path from the root down to some group, and
+ * returns a MeasureGroupObject representing that.
+ */
+/* static */ bool
+MeasureGroupObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Retrieve the root Group for the lookup from MeasureGroup.prototype.
+ RootedObject callee(cx, &args.callee());
+ RootedValue protoVal(cx);
+ if (!GetProperty(cx, callee, callee, cx->names().prototype, &protoVal))
+ return false;
+ Rooted<MeasureGroupObject*> groupProto(cx, &protoVal.toObject().as<MeasureGroupObject>());
+ Rooted<MeasurePrototypeObject*> measureProto(cx, &groupProto->measureProto());
+ const Group& rootGroup = groupProto->measureGroup();
+
+ GroupPtr group;
+
+ // The no-argument case: construct a MeasureGroupObject referring to the
+ // root of the measure tree.
+ if (args.get(0).isUndefined()) {
+ group = rootGroup.Clone();
+ } else {
+ // The one-argument case: follow path elements down from the root to some
+ // interior Group.
+ auto maybePath = GetPathElementsAsUTF8(cx, args[0]);
+ if (!maybePath)
+ return false;
+ js::Vector<UniqueChars> pathUTF8(Move(*maybePath));
+
+ group = FollowGroupPath(cx, rootGroup, pathUTF8, pathUTF8.length(), args[0]);
+ }
+
+ if (!group)
+ return false;
+
+ Rooted<MeasureGroupObject*> groupObj(cx);
+ groupObj = MeasureGroupObject::create(cx, groupProto, measureProto, Move(group));
+ if (!groupObj)
+ return false;
+
+ args.rval().setObject(*groupObj);
+ return true;
+}
+
+/* static */ MOZ_MUST_USE bool
+MeasureGroupObject::identifierGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureGroupObject> args(cx, CallArgsFromVp(argc, vp),
+ "identifier getter");
+ if (!args.check())
+ return false;
+
+ Description description = args.object().measureGroup().GetDescription();
+ return UTF8ToString(cx, description.mIdentifier, args.rval());
+}
+
+/* static */ bool
+MeasureGroupObject::metadataMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureGroupObject> args(cx, CallArgsFromVp(argc, vp),
+ "metadata");
+ if (!args.requireAtLeast(1))
+ return false;
+
+ Description description = args.object().measureGroup().GetDescription();
+ return GetMetadata(cx, description, args[0], args.rval());
+}
+
+/* static */ bool
+MeasureGroupObject::measuresMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureGroupObject> args(cx, CallArgsFromVp(argc, vp),
+ "measures");
+ if (!args.check())
+ return false;
+
+ const Group& group = args.object().measureGroup();
+ Maybe<MeasureVector> maybeMeasures = group.GetMeasures();
+ if (!maybeMeasures)
+ return false;
+ MeasureVector measures(Move(*maybeMeasures));
+
+ Rooted<ValueVector> values(cx, ValueVector(cx));
+ if (!values.reserve(measures.length()))
+ return false;
+
+ Rooted<MeasurePrototypeObject*> measureProto(cx, &args.object().measureProto());
+ for (auto& measure : measures) {
+ RootedObject obj(cx, MeasureObject::create(cx, measureProto, Move(measure)));
+ if (!obj)
+ return false;
+
+ values.infallibleAppend(ObjectValue(*obj.get()));
+ }
+
+ RootedObject array(cx, NewDenseCopiedArray(cx, values.length(), values.begin()));
+ if (!array)
+ return false;
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+/* static */ bool
+MeasureGroupObject::subgroupsMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureGroupObject> args(cx, CallArgsFromVp(argc, vp),
+ "subgroups");
+ if (!args.check())
+ return false;
+
+ const Group& group = args.object().measureGroup();
+ Maybe<GroupVector> maybeSubgroups = group.GetSubgroups();
+ if (!maybeSubgroups)
+ return false;
+ GroupVector subgroups(Move(*maybeSubgroups));
+
+ Rooted<ValueVector> values(cx, ValueVector(cx));
+ if (!values.reserve(subgroups.length()))
+ return false;
+
+ Rooted<MeasurePrototypeObject*> measureProto(cx, &args.object().measureProto());
+ for (auto& subgroup : subgroups) {
+ RootedObject obj(cx, MeasureGroupObject::create(cx,
+ args.prototype(),
+ measureProto,
+ Move(subgroup)));
+ if (!obj)
+ return false;
+
+ values.infallibleAppend(ObjectValue(*obj.get()));
+ }
+
+ RootedObject array(cx, NewDenseCopiedArray(cx, values.length(), values.begin()));
+ if (!array)
+ return false;
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+/* static */ bool
+MeasureGroupObject::toStringMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureGroupObject> args(cx, CallArgsFromVp(argc, vp),
+ js_toString_str);
+ if (!args.check())
+ return false;
+
+ Description description = args.object().measureGroup().GetDescription();
+ return CommonToString(cx, "MeasureGroup", description, args.rval());
+}
+
+
+/* The Measure object. */
+
+const ClassOps MeasureObject::classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize,
+};
+
+const Class MeasureObject::class_ = {
+ "Measure",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &classOps
+};
+
+const JSPropertySpec MeasureObject::properties[] = {
+ JS_PSGS("enabled", enabledGetter, enabledSetter, 0),
+ JS_PSG("identifier", identifierGetter, 0),
+ JS_PS_END
+};
+
+const JSFunctionSpec MeasureObject::methods[] = {
+ JS_FN("metadata", metadataMethod, 0, 0),
+ JS_FN(js_toString_str, toStringMethod, 0, 0),
+ JS_FS_END
+};
+
+/* static */ MeasureObject*
+MeasureObject::create(JSContext* cx,
+ HandleObject proto,
+ MeasurePtr&& measure)
+{
+ assertSameCompartment(cx, proto);
+
+ JSObject *obj = NewObjectWithGivenProto(cx, &class_, proto);
+ if (!obj)
+ return nullptr;
+
+ // No fallible operations beyond this point, to ensure our finalize function
+ // only sees fully-initialized instances.
+ Rooted<MeasureObject*> measureObj(cx, &obj->as<MeasureObject>());
+ measureObj->initReservedSlot(MeasureSlot, PrivateValue(measure.release()));
+
+ return measureObj;
+}
+
+/* static */ void
+MeasureObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onActiveCooperatingThread());
+
+ // To make sure that we free the memory with the same allocator used to
+ // create the Measure in the first place, free measure by creating a
+ // MeasurePtr and then dropping it.
+ Measure& measure = obj->as<MeasureObject>().measure();
+ MeasurePtr unique(&measure);
+}
+
+/* static */ bool
+MeasureObject::DeclareConstructor(JSContext* cx,
+ Handle<GlobalObject*> global,
+ Handle<MeasurePrototypeObject*> measureProto)
+{
+ // Create the Measure constructor function.
+ RootedAtom atom(cx, Atomize(cx, class_.name, strlen(class_.name)));
+ if (!atom)
+ return false;
+ RootedFunction constructorFun(cx, NewNativeConstructor(cx, construct, 1, atom));
+ if (!constructorFun)
+ return false;
+
+ // Cross-link the constructor and prototype, and add methods and accessors.
+ if (!LinkConstructorAndPrototype(cx, constructorFun, measureProto) ||
+ !DefinePropertiesAndFunctions(cx, measureProto, properties, methods))
+ return false;
+
+ // Define the Measure constructor on the global. This is the sole
+ // externally-visible side effect, so it's last.
+ RootedId id(cx, AtomToId(atom));
+ RootedValue constructorValue(cx, ObjectValue(*constructorFun));
+ return DefineDataProperty(cx, global, id, constructorValue, 0 /* attrs */);
+}
+
+/*
+ * The Measure constructor takes an array of strings, treats that as a path from
+ * the root Group down to some leaf Measure, and constructs a MeasureObject
+ * representing that Measure.
+ */
+/* static */ bool
+MeasureObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "Measure", 1))
+ return false;
+
+ // Find the root Group for the lookup by asking MeasureGroup.prototype.
+ // Measure.prototype holds a reference to that.
+ RootedObject callee(cx, &args.callee());
+ RootedValue protoVal(cx);
+ if (!GetProperty(cx, callee, callee, cx->names().prototype, &protoVal))
+ return false;
+ Rooted<MeasurePrototypeObject*> proto(cx, &protoVal.toObject().as<MeasurePrototypeObject>());
+
+ auto maybePath = GetPathElementsAsUTF8(cx, args[0]);
+ if (!maybePath)
+ return false;
+ js::Vector<UniqueChars> pathUTF8(Move(*maybePath));
+
+ // Traverse all path components but the last, to get the parent group.
+ GroupPtr parent = FollowGroupPath(cx, proto->rootGroup(),
+ pathUTF8, pathUTF8.length() - 1,
+ args[0]);
+ if (!parent)
+ return false;
+
+ /* Look up the Measure under the final path component. */
+ Maybe<MeasurePtr> maybeMeasure = parent->GetMeasure(pathUTF8.back().get());
+ if (!maybeMeasure)
+ return ReportNoPath(cx, args[0]);
+
+ RootedObject obj(cx, MeasureObject::create(cx, proto, Move(*maybeMeasure)));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/* static */ bool
+MeasureObject::enabledSetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureObject> args(cx, CallArgsFromVp(argc, vp),
+ "enabled setter");
+ if (!args.requireAtLeast(1))
+ return false;
+
+ Measure& measure = args.object().measure();
+ if (ToBoolean(args[0]))
+ measure.Enable();
+ else
+ measure.Disable();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ bool
+MeasureObject::enabledGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureObject> args(cx, CallArgsFromVp(argc, vp),
+ "enabled getter");
+ if (!args.check())
+ return false;
+
+ args.rval().setBoolean(args.object().measure().IsEnabled());
+ return true;
+}
+
+/* static */ bool
+MeasureObject::identifierGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureObject> args(cx, CallArgsFromVp(argc, vp),
+ "identifier getter");
+ if (!args.check())
+ return false;
+
+ Description description = args.object().measure().GetDescription();
+ return UTF8ToString(cx, description.mIdentifier, args.rval());
+}
+
+/* static */ bool
+MeasureObject::metadataMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureObject> args(cx, CallArgsFromVp(argc, vp),
+ "metadata");
+ if (!args.requireAtLeast(1))
+ return false;
+
+ Description description = args.object().measure().GetDescription();
+ return GetMetadata(cx, description, args[0], args.rval());
+}
+
+
+/* static */ bool
+MeasureObject::toStringMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+ MeasureArgs<MeasureObject> args(cx, CallArgsFromVp(argc, vp),
+ js_toString_str);
+ if (!args.check())
+ return false;
+
+ Description description = args.object().measure().GetDescription();
+ return CommonToString(cx, "Measure", description, args.rval());
+}
+
+} // namespace js
+
+
+/* Public interfaces. */
+
+namespace JS {
+
+extern JS_PUBLIC_API(bool)
+DefineMeasureConstructors(JSContext* cx, HandleObject obj, GroupPtr&& root)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+
+ Rooted<MeasurePrototypeObject*> measureProto(cx);
+ measureProto = MeasurePrototypeObject::create(cx, global, root->Clone());
+ if (!measureProto)
+ return false;
+
+ if (!MeasureGroupObject::DeclareConstructor(cx, global, measureProto, root->Clone()) ||
+ !MeasureObject::DeclareConstructor(cx, global, measureProto))
+ return false;
+
+ return true;
+}
+
+} // namespace JS