Commit 9472eff9 authored by Michael Comella's avatar Michael Comella
Browse files

Bug 1762482 - update PerformanceMeasure to User Timing L3. r=sefeng,smaug a=RyanVM

parent a6178608
Loading
Loading
Loading
Loading
+184 −30
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include "PerformanceWorker.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PerformanceEntryEvent.h"
#include "mozilla/dom/PerformanceNavigationBinding.h"
@@ -33,6 +34,12 @@

namespace mozilla::dom {

enum class Performance::ResolveTimestampAttribute {
  Start,
  End,
  Duration,
};

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

@@ -387,7 +394,7 @@ void Performance::ClearMarks(const Optional<nsAString>& aName) {
  ClearUserEntries(aName, u"mark"_ns);
}

DOMHighResTimeStamp Performance::ResolveTimestampFromName(
DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithString(
    const nsAString& aName, ErrorResult& aRv) {
  AutoTArray<RefPtr<PerformanceEntry>, 1> arr;
  Optional<nsAString> typeParam;
@@ -400,7 +407,9 @@ DOMHighResTimeStamp Performance::ResolveTimestampFromName(
  }

  if (!IsPerformanceTimingAttribute(aName)) {
    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
    nsPrintfCString errorMsg("Given mark name, %s, is unknown",
                             NS_ConvertUTF16toUTF8(aName).get());
    aRv.ThrowSyntaxError(errorMsg);
    return 0;
  }

@@ -413,41 +422,190 @@ DOMHighResTimeStamp Performance::ResolveTimestampFromName(
  return ts - CreationTime();
}

void Performance::Measure(const nsAString& aName,
                          const Optional<nsAString>& aStartMark,
DOMHighResTimeStamp Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
    const ResolveTimestampAttribute aAttribute,
    const DOMHighResTimeStamp aTimestamp, ErrorResult& aRv) {
  if (aTimestamp < 0) {
    nsAutoCString attributeName;
    switch (aAttribute) {
      case ResolveTimestampAttribute::Start:
        attributeName = "start";
        break;
      case ResolveTimestampAttribute::End:
        attributeName = "end";
        break;
      case ResolveTimestampAttribute::Duration:
        attributeName = "duration";
        break;
    }

    nsPrintfCString errorMsg("Given attribute %s cannot be negative",
                             attributeName.get());
    aRv.ThrowTypeError(errorMsg);
  }
  return aTimestamp;
}

DOMHighResTimeStamp Performance::ConvertMarkToTimestamp(
    const ResolveTimestampAttribute aAttribute,
    const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv) {
  if (aMarkNameOrTimestamp.IsString()) {
    return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp.GetAsString(),
                                            aRv);
  }

  return ConvertMarkToTimestampWithDOMHighResTimeStamp(
      aAttribute, aMarkNameOrTimestamp.GetAsDouble(), aRv);
}

DOMHighResTimeStamp Performance::ResolveEndTimeForMeasure(
    const Optional<nsAString>& aEndMark,
                          ErrorResult& aRv) {
  // We add nothing when 'privacy.resistFingerprinting' is on.
  if (nsContentUtils::ShouldResistFingerprinting()) {
    return;
    const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv) {
  DOMHighResTimeStamp endTime;
  if (aEndMark.WasPassed()) {
    endTime = ConvertMarkToTimestampWithString(aEndMark.Value(), aRv);
  } else if (aOptions && aOptions->mEnd.WasPassed()) {
    endTime = ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
                                     aOptions->mEnd.Value(), aRv);
  } else if (aOptions && aOptions->mStart.WasPassed() &&
             aOptions->mDuration.WasPassed()) {
    const DOMHighResTimeStamp start = ConvertMarkToTimestamp(
        ResolveTimestampAttribute::Start, aOptions->mStart.Value(), aRv);
    if (aRv.Failed()) {
      return 0;
    }

    const DOMHighResTimeStamp duration =
        ConvertMarkToTimestampWithDOMHighResTimeStamp(
            ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
            aRv);
    if (aRv.Failed()) {
      return 0;
    }

    endTime = start + duration;
  } else {
    endTime = Now();
  }

  return endTime;
}

DOMHighResTimeStamp Performance::ResolveStartTimeForMeasure(
    const Maybe<const nsAString&>& aStartMark,
    const Maybe<const PerformanceMeasureOptions&>& aOptions, ErrorResult& aRv) {
  DOMHighResTimeStamp startTime;
  DOMHighResTimeStamp endTime;
  if (aOptions && aOptions->mStart.WasPassed()) {
    startTime = ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
                                       aOptions->mStart.Value(), aRv);
  } else if (aOptions && aOptions->mDuration.WasPassed() &&
             aOptions->mEnd.WasPassed()) {
    const DOMHighResTimeStamp duration =
        ConvertMarkToTimestampWithDOMHighResTimeStamp(
            ResolveTimestampAttribute::Duration, aOptions->mDuration.Value(),
            aRv);
    if (aRv.Failed()) {
      return 0;
    }

  if (aStartMark.WasPassed()) {
    startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    const DOMHighResTimeStamp end = ConvertMarkToTimestamp(
        ResolveTimestampAttribute::End, aOptions->mEnd.Value(), aRv);
    if (aRv.Failed()) {
      return 0;
    }

    startTime = end - duration;
  } else if (aStartMark) {
    startTime = ConvertMarkToTimestampWithString(*aStartMark, aRv);
  } else {
    // Navigation start is used in this case, but since DOMHighResTimeStamp is
    // in relation to navigation start, this will be zero if a name is not
    // passed.
    startTime = 0;
  }

  return startTime;
}

already_AddRefed<PerformanceMeasure> Performance::Measure(
    JSContext* aCx, const nsAString& aName,
    const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
    const Optional<nsAString>& aEndMark, ErrorResult& aRv) {
  // When resisting fingerprinting, we don't add marks to the buffer. Since
  // measure relies on relationships between marks in the buffer, this method
  // will throw if we look for user-entered marks so we return a dummy measure
  // instead of continuing. We could instead return real values for performance
  // timing attributes and dummy values for user-entered marks but this adds
  // complexity that doesn't seem worth the effort because these fingerprinting
  // protections may not longer be necessary (since performance.now() already
  // has reduced precision).
  if (nsContentUtils::ShouldResistFingerprinting()) {
    return do_AddRef(new PerformanceMeasure(GetParentObject(), aName, 0, 0,
                                            JS::NullHandleValue));
  }

  // Maybe is more readable than using the union type directly.
  Maybe<const PerformanceMeasureOptions&> options;
  if (aStartOrMeasureOptions.IsPerformanceMeasureOptions()) {
    options.emplace(aStartOrMeasureOptions.GetAsPerformanceMeasureOptions());
  }

  const bool isOptionsNotEmpty =
      options.isSome() &&
      (!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
       options->mEnd.WasPassed() || options->mDuration.WasPassed());
  if (isOptionsNotEmpty) {
    if (aEndMark.WasPassed()) {
    endTime = ResolveTimestampFromName(aEndMark.Value(), aRv);
      aRv.ThrowTypeError(
          "Cannot provide separate endMark argument if "
          "PerformanceMeasureOptions argument is given");
      return nullptr;
    }

    if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
      aRv.ThrowTypeError(
          "PerformanceMeasureOptions must have start and/or end member");
      return nullptr;
    }

    if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
        options->mEnd.WasPassed()) {
      aRv.ThrowTypeError(
          "PerformanceMeasureOptions cannot have all of the following members: "
          "start, duration, and end");
      return nullptr;
    }
  }

  const DOMHighResTimeStamp endTime =
      ResolveEndTimeForMeasure(aEndMark, options, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
      return;
    return nullptr;
  }

  // Convert to Maybe for consistency with options.
  Maybe<const nsAString&> startMark;
  if (aStartOrMeasureOptions.IsString()) {
    startMark.emplace(aStartOrMeasureOptions.GetAsString());
  }
  const DOMHighResTimeStamp startTime =
      ResolveStartTimeForMeasure(startMark, options, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  JS::Rooted<JS::Value> detail(aCx);
  if (options && !options->mDetail.isNullOrUndefined()) {
    StructuredSerializeOptions serializeOptions;
    JS::Rooted<JS::Value> valueToClone(aCx, options->mDetail);
    nsContentUtils::StructuredClone(aCx, GetParentObject(), valueToClone,
                                    serializeOptions, &detail, aRv);
    if (aRv.Failed()) {
      return nullptr;
    }
  } else {
    endTime = Now();
    detail.setNull();
  }

  RefPtr<PerformanceMeasure> performanceMeasure =
      new PerformanceMeasure(GetParentObject(), aName, startTime, endTime);
  RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
      GetParentObject(), aName, startTime, endTime, detail);
  InsertUserEntry(performanceMeasure);

  if (profiler_thread_is_being_profiled_for_markers()) {
@@ -456,12 +614,6 @@ void Performance::Measure(const nsAString& aName,
    TimeStamp endTimeStamp =
        CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime);

    // Convert to Maybe values so that Optional types do not need to be used in
    // the profiler.
    Maybe<nsString> startMark;
    if (aStartMark.WasPassed()) {
      startMark.emplace(aStartMark.Value());
    }
    Maybe<nsString> endMark;
    if (aEndMark.WasPassed()) {
      endMark.emplace(aEndMark.Value());
@@ -477,6 +629,8 @@ void Performance::Measure(const nsAString& aName,
                        UserTimingMarker{}, aName, /* aIsMeasure */ true,
                        startMark, endMark);
  }

  return performanceMeasure.forget();
}

void Performance::ClearMeasures(const Optional<nsAString>& aName) {
+30 −5
Original line number Diff line number Diff line
@@ -21,9 +21,13 @@ class ErrorResult;

namespace dom {

class OwningStringOrDouble;
class StringOrPerformanceMeasureOptions;
class PerformanceEntry;
class PerformanceMark;
struct PerformanceMarkOptions;
struct PerformanceMeasureOptions;
class PerformanceMeasure;
class PerformanceNavigation;
class PerformancePaintTiming;
class PerformanceObserver;
@@ -84,7 +88,9 @@ class Performance : public DOMEventTargetHelper {

  void ClearMarks(const Optional<nsAString>& aName);

  void Measure(const nsAString& aName, const Optional<nsAString>& aStartMark,
  already_AddRefed<PerformanceMeasure> Measure(
      JSContext* aCx, const nsAString& aName,
      const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
      const Optional<nsAString>& aEndMark, ErrorResult& aRv);

  void ClearMeasures(const Optional<nsAString>& aName);
@@ -161,9 +167,6 @@ class Performance : public DOMEventTargetHelper {
  void ClearUserEntries(const Optional<nsAString>& aEntryName,
                        const nsAString& aEntryType);

  DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
                                               ErrorResult& aRv);

  virtual void DispatchBufferFullEvent() = 0;

  virtual DOMHighResTimeStamp CreationTime() const = 0;
@@ -206,6 +209,28 @@ class Performance : public DOMEventTargetHelper {
 private:
  MOZ_ALWAYS_INLINE bool CanAddResourceTimingEntry();
  void BufferEvent();

  // The attributes of a PerformanceMeasureOptions that we call
  // ResolveTimestamp* on.
  enum class ResolveTimestampAttribute;

  DOMHighResTimeStamp ConvertMarkToTimestampWithString(const nsAString& aName,
                                                       ErrorResult& aRv);
  DOMHighResTimeStamp ConvertMarkToTimestampWithDOMHighResTimeStamp(
      const ResolveTimestampAttribute aAttribute, const double aTimestamp,
      ErrorResult& aRv);
  DOMHighResTimeStamp ConvertMarkToTimestamp(
      const ResolveTimestampAttribute aAttribute,
      const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv);

  DOMHighResTimeStamp ResolveEndTimeForMeasure(
      const Optional<nsAString>& aEndMark,
      const Maybe<const PerformanceMeasureOptions&>& aOptions,
      ErrorResult& aRv);
  DOMHighResTimeStamp ResolveStartTimeForMeasure(
      const Maybe<const nsAString&>& aStartMark,
      const Maybe<const PerformanceMeasureOptions&>& aOptions,
      ErrorResult& aRv);
};

}  // namespace dom
+4 −3
Original line number Diff line number Diff line
@@ -100,9 +100,10 @@ JSObject* PerformanceMark::WrapObject(JSContext* aCx,

void PerformanceMark::GetDetail(JSContext* aCx,
                                JS::MutableHandle<JS::Value> aRetval) {
  // Return a copy so that the PerformanceMark.detail reference always returns
  // the same value. However, the contents of detail can be mutated. The spec
  // isn't clear if this is okay but it matches Chrome's behavior.
  // Return a copy so that this method always returns the value it is set to
  // (i.e. it'll return the same value even if the caller assigns to it). Note
  // that if detail is an object, its contents can be mutated and this is
  // expected.
  aRetval.set(mDetail);
}

+34 −3
Original line number Diff line number Diff line
@@ -13,18 +13,49 @@ using namespace mozilla::dom;
PerformanceMeasure::PerformanceMeasure(nsISupports* aParent,
                                       const nsAString& aName,
                                       DOMHighResTimeStamp aStartTime,
                                       DOMHighResTimeStamp aEndTime)
                                       DOMHighResTimeStamp aEndTime,
                                       const JS::Handle<JS::Value>& aDetail)
    : PerformanceEntry(aParent, aName, u"measure"_ns),
      mStartTime(aStartTime),
      mDuration(aEndTime - aStartTime) {}
      mDuration(aEndTime - aStartTime),
      mDetail(aDetail) {
  mozilla::HoldJSObjects(this);
}

PerformanceMeasure::~PerformanceMeasure() { mozilla::DropJSObjects(this); }

NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMeasure)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMeasure,
                                                PerformanceEntry)
  tmp->mDetail.setUndefined();
  mozilla::DropJSObjects(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMeasure,
                                                  PerformanceEntry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

PerformanceMeasure::~PerformanceMeasure() = default;
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMeasure,
                                               PerformanceEntry)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDetail)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(PerformanceMeasure,
                                               PerformanceEntry)

JSObject* PerformanceMeasure::WrapObject(JSContext* aCx,
                                         JS::Handle<JSObject*> aGivenProto) {
  return PerformanceMeasure_Binding::Wrap(aCx, this, aGivenProto);
}

void PerformanceMeasure::GetDetail(JSContext* aCx,
                                   JS::MutableHandle<JS::Value> aRetval) {
  // Return a copy so that this method always returns the value it is set to
  // (i.e. it'll return the same value even if the caller assigns to it). Note
  // that if detail is an object, its contents can be mutated and this is
  // expected.
  aRetval.set(mDetail);
}

size_t PerformanceMeasure::SizeOfIncludingThis(
    mozilla::MallocSizeOf aMallocSizeOf) const {
  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+11 −1
Original line number Diff line number Diff line
@@ -14,9 +14,14 @@ namespace mozilla::dom {
// http://www.w3.org/TR/user-timing/#performancemeasure
class PerformanceMeasure final : public PerformanceEntry {
 public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMeasure,
                                                         PerformanceEntry);

  PerformanceMeasure(nsISupports* aParent, const nsAString& aName,
                     DOMHighResTimeStamp aStartTime,
                     DOMHighResTimeStamp aEndTime);
                     DOMHighResTimeStamp aEndTime,
                     const JS::Handle<JS::Value>& aDetail);

  virtual JSObject* WrapObject(JSContext* aCx,
                               JS::Handle<JSObject*> aGivenProto) override;
@@ -25,6 +30,8 @@ class PerformanceMeasure final : public PerformanceEntry {

  virtual DOMHighResTimeStamp Duration() const override { return mDuration; }

  void GetDetail(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);

  size_t SizeOfIncludingThis(
      mozilla::MallocSizeOf aMallocSizeOf) const override;

@@ -32,6 +39,9 @@ class PerformanceMeasure final : public PerformanceEntry {
  virtual ~PerformanceMeasure();
  DOMHighResTimeStamp mStartTime;
  DOMHighResTimeStamp mDuration;

 private:
  JS::Heap<JS::Value> mDetail;
};

}  // namespace mozilla::dom
Loading