Bug 1238042 - Expose the checkerboard reports out to JS. r=ehsan,botond
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 22 Jan 2016 20:27:28 -0500
changeset 281309 2a392116c7ca9ccddd2d322d69e3c6b292bddd48
parent 281308 bd49c46ef0fd3c751f9f25382428d0d51a879328
child 281310 e165659ef8034eef24ef46b6c955a555ce89855b
push id29934
push userphilringnalda@gmail.com
push dateSun, 24 Jan 2016 01:56:15 +0000
treeherdermozilla-central@d0df4221d105 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, botond
bugs1238042
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1238042 - Expose the checkerboard reports out to JS. r=ehsan,botond
dom/webidl/CheckerboardReportService.webidl
dom/webidl/moz.build
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/util/CheckerboardReportService.cpp
gfx/layers/apz/util/CheckerboardReportService.h
gfx/layers/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CheckerboardReportService.webidl
@@ -0,0 +1,48 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+/*
+ * This file declares data structures used to communicate checkerboard reports
+ * from C++ code to about:checkerboard (see bug 1238042). These dictionaries
+ * are NOT exposed to standard web content.
+ */
+
+enum CheckerboardReason {
+  "severe",
+  "recent"
+};
+
+// Individual checkerboard report. Contains fields for the severity of the
+// checkerboard event, the timestamp at which it was reported, the detailed
+// log of the event, and the reason this report was saved (currently either
+// "severe" or "recent").
+dictionary CheckerboardReport {
+  unsigned long severity;
+  DOMTimeStamp timestamp; // milliseconds since epoch
+  DOMString log;
+  CheckerboardReason reason;
+};
+
+// The guard function only allows creation of this interface on the
+// about:checkerboard page, and only if it's in the parent process.
+[Func="mozilla::dom::CheckerboardReportService::IsEnabled",
+ Constructor]
+interface CheckerboardReportService {
+  /**
+   * Gets the available checkerboard reports.
+   */
+  sequence<CheckerboardReport> getReports();
+
+  /**
+   * Gets the state of the apz.record_checkerboarding pref.
+   */
+  boolean isRecordingEnabled();
+
+  /**
+   * Sets the state of the apz.record_checkerboarding pref.
+   */
+  void setRecordingEnabled(boolean aEnabled);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -70,16 +70,17 @@ WEBIDL_FILES = [
     'CameraUtil.webidl',
     'CanvasCaptureMediaStream.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
     'CDATASection.webidl',
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
+    'CheckerboardReportService.webidl',
     'ChildNode.webidl',
     'ChromeNodeList.webidl',
     'ChromeNotifications.webidl',
     'ChromeUtils.webidl',
     'Client.webidl',
     'Clients.webidl',
     'ClipboardEvent.webidl',
     'CommandEvent.webidl',
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -31,16 +31,18 @@
 #include "mozilla/BasicEvents.h"        // for Modifiers, MODIFIER_*
 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
 #include "mozilla/EventForwards.h"      // for nsEventStatus_*
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitorAutoEnter, etc
 #include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "mozilla/Telemetry.h"          // for Telemetry
 #include "mozilla/TimeStamp.h"          // for TimeDuration, TimeStamp
+#include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage
+             // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
 #include "mozilla/dom/KeyframeEffect.h" // for ComputedTimingFunction
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for Point, RoundedToInt, etc
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/layers/APZCTreeManager.h"  // for ScrollableLayerGuid
@@ -3129,20 +3131,25 @@ AsyncPanZoomController::ReportCheckerboa
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK,
       mCheckerboardEvent->GetPeak());
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_DURATION,
       (uint32_t)mCheckerboardEvent->GetDuration().ToMilliseconds());
 
     mPotentialCheckerboardTracker.CheckerboardDone();
 
     if (recordTrace) {
-      // TODO: save the info somewhere for
-      // about:checkerboard. For now we just print it.
-      std::stringstream log(mCheckerboardEvent->GetLog());
-      print_stderr(log);
+      // if the pref is enabled, also send it to the storage class. it may be
+      // chosen for public display on about:checkerboard, the hall of fame for
+      // checkerboard events.
+      uint32_t severity = mCheckerboardEvent->GetSeverity();
+      std::string log = mCheckerboardEvent->GetLog();
+      NS_DispatchToMainThread(NS_NewRunnableFunction([severity, log]() {
+          RefPtr<CheckerboardEventStorage> storage = CheckerboardEventStorage::GetInstance();
+          storage->ReportCheckerboard(severity, log);
+      }));
     }
     mCheckerboardEvent = nullptr;
   }
 }
 
 bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "CheckerboardReportService.h"
+
+#include "gfxPrefs.h" // for gfxPrefs
+#include "jsapi.h" // for JS_Now
+#include "MainThreadUtils.h" // for NS_IsMainThread
+#include "mozilla/Assertions.h" // for MOZ_ASSERT
+#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown
+#include "mozilla/dom/CheckerboardReportServiceBinding.h" // for dom::CheckerboardReports
+#include "nsContentUtils.h" // for nsContentUtils
+
+namespace mozilla {
+namespace layers {
+
+/*static*/ StaticRefPtr<CheckerboardEventStorage> CheckerboardEventStorage::sInstance;
+
+/*static*/ already_AddRefed<CheckerboardEventStorage>
+CheckerboardEventStorage::GetInstance()
+{
+  // The instance in the parent process does all the work, so if this is getting
+  // called in the child process something is likely wrong.
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!sInstance) {
+    sInstance = new CheckerboardEventStorage();
+    ClearOnShutdown(&sInstance);
+  }
+  RefPtr<CheckerboardEventStorage> instance = sInstance.get();
+  return instance.forget();
+}
+
+void
+CheckerboardEventStorage::ReportCheckerboard(uint32_t aSeverity, const std::string& aLog)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aSeverity == 0) {
+    // This code assumes all checkerboard reports have a nonzero severity.
+    return;
+  }
+
+  CheckerboardReport severe(aSeverity, JS_Now(), aLog);
+  CheckerboardReport recent;
+
+  // First look in the "severe" reports to see if the new one belongs in that
+  // list.
+  for (int i = 0; i < SEVERITY_MAX_INDEX; i++) {
+    if (mCheckerboardReports[i].mSeverity >= severe.mSeverity) {
+      continue;
+    }
+    // The new one deserves to be in the "severe" list. Take the one getting
+    // bumped off the list, and put it in |recent| for possible insertion into
+    // the recents list.
+    recent = mCheckerboardReports[SEVERITY_MAX_INDEX - 1];
+
+    // Shuffle the severe list down, insert the new one.
+    for (int j = SEVERITY_MAX_INDEX - 1; j > i; j--) {
+      mCheckerboardReports[j] = mCheckerboardReports[j - 1];
+    }
+    mCheckerboardReports[i] = severe;
+    severe.mSeverity = 0; // mark |severe| as inserted
+    break;
+  }
+
+  // If |severe.mSeverity| is nonzero, the incoming report didn't get inserted
+  // into the severe list; put it into |recent| for insertion into the recent
+  // list.
+  if (severe.mSeverity) {
+    MOZ_ASSERT(recent.mSeverity == 0, "recent should be empty here");
+    recent = severe;
+  } // else |recent| may hold a report that got knocked out of the severe list.
+
+  if (recent.mSeverity == 0) {
+    // Nothing to be inserted into the recent list.
+    return;
+  }
+
+  // If it wasn't in the "severe" list, add it to the "recent" list.
+  for (int i = SEVERITY_MAX_INDEX; i < RECENT_MAX_INDEX; i++) {
+    if (mCheckerboardReports[i].mTimestamp >= recent.mTimestamp) {
+      continue;
+    }
+    // |recent| needs to be inserted at |i|. Shuffle the remaining ones down
+    // and insert it.
+    for (int j = RECENT_MAX_INDEX - 1; j > i; j--) {
+      mCheckerboardReports[j] = mCheckerboardReports[j - 1];
+    }
+    mCheckerboardReports[i] = recent;
+    break;
+  }
+}
+
+void
+CheckerboardEventStorage::GetReports(nsTArray<dom::CheckerboardReport>& aOutReports)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (int i = 0; i < RECENT_MAX_INDEX; i++) {
+    CheckerboardReport& r = mCheckerboardReports[i];
+    if (r.mSeverity == 0) {
+      continue;
+    }
+    dom::CheckerboardReport report;
+    report.mSeverity.Construct() = r.mSeverity;
+    report.mTimestamp.Construct() = r.mTimestamp / 1000; // micros to millis
+    report.mLog.Construct() = NS_ConvertUTF8toUTF16(r.mLog.c_str(), r.mLog.size());
+    report.mReason.Construct() = (i < SEVERITY_MAX_INDEX)
+        ? dom::CheckerboardReason::Severe
+        : dom::CheckerboardReason::Recent;
+    aOutReports.AppendElement(report);
+  }
+}
+
+} // namespace layers
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CheckerboardReportService, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CheckerboardReportService, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CheckerboardReportService, Release)
+
+/*static*/ bool
+CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal)
+{
+  // Only allow this in the parent process
+  if (!XRE_IsParentProcess()) {
+    return false;
+  }
+
+  return nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard");
+}
+
+/*static*/ already_AddRefed<CheckerboardReportService>
+CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  RefPtr<CheckerboardReportService> ces = new CheckerboardReportService(aGlobal.GetAsSupports());
+  return ces.forget();
+}
+
+CheckerboardReportService::CheckerboardReportService(nsISupports* aParent)
+  : mParent(aParent)
+{
+}
+
+JSObject*
+CheckerboardReportService::WrapObject(JSContext* aCtx, JS::Handle<JSObject*> aGivenProto)
+{
+  return CheckerboardReportServiceBinding::Wrap(aCtx, this, aGivenProto);
+}
+
+nsISupports*
+CheckerboardReportService::GetParentObject()
+{
+  return mParent;
+}
+
+void
+CheckerboardReportService::GetReports(nsTArray<dom::CheckerboardReport>& aOutReports)
+{
+  RefPtr<mozilla::layers::CheckerboardEventStorage> instance =
+      mozilla::layers::CheckerboardEventStorage::GetInstance();
+  MOZ_ASSERT(instance);
+  instance->GetReports(aOutReports);
+}
+
+bool
+CheckerboardReportService::IsRecordingEnabled() const
+{
+  return gfxPrefs::APZRecordCheckerboarding();
+}
+
+void
+CheckerboardReportService::SetRecordingEnabled(bool aEnabled)
+{
+  gfxPrefs::SetAPZRecordCheckerboarding(aEnabled);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/util/CheckerboardReportService.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_CheckerboardReportService_h
+#define mozilla_dom_CheckerboardReportService_h
+
+#include <string>
+
+#include "js/TypeDecls.h" // for JSContext, JSObject
+#include "mozilla/ErrorResult.h" // for ErrorResult
+#include "mozilla/StaticPtr.h" // for StaticRefPtr
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsWrapperCache.h" // for nsWrapperCache
+
+namespace mozilla {
+
+namespace dom {
+struct CheckerboardReport;
+}
+
+namespace layers {
+
+// CheckerboardEventStorage is a singleton that stores info on checkerboard
+// events, so that they can be accessed from about:checkerboard and visualized.
+// Note that this class is NOT threadsafe, and all methods must be called on
+// the main thread.
+class CheckerboardEventStorage
+{
+  NS_INLINE_DECL_REFCOUNTING(CheckerboardEventStorage)
+
+public:
+  /**
+   * Get the singleton instance.
+   */
+  static already_AddRefed<CheckerboardEventStorage> GetInstance();
+
+  /**
+   * Save a checkerboard event log, optionally dropping older ones that were
+   * less severe or less recent. Zero-severity reports may be ignored entirely.
+   */
+  void ReportCheckerboard(uint32_t aSeverity, const std::string& aLog);
+
+  /**
+   * Get the stored checkerboard reports.
+   */
+  void GetReports(nsTArray<dom::CheckerboardReport>& aOutReports);
+
+private:
+  /* Stuff for refcounted singleton */
+  CheckerboardEventStorage() {}
+  virtual ~CheckerboardEventStorage() {}
+
+  static StaticRefPtr<CheckerboardEventStorage> sInstance;
+
+private:
+  /**
+   * Struct that this class uses internally to store a checkerboard report.
+   */
+  struct CheckerboardReport {
+      uint32_t mSeverity; // if 0, this report is empty
+      int64_t mTimestamp; // microseconds since epoch, as from JS_Now()
+      std::string mLog;
+
+      CheckerboardReport()
+        : mSeverity(0)
+        , mTimestamp(0)
+      {}
+
+      CheckerboardReport(uint32_t aSeverity, int64_t aTimestamp,
+                         const std::string& aLog)
+        : mSeverity(aSeverity)
+        , mTimestamp(aTimestamp)
+        , mLog(aLog)
+      {}
+  };
+
+  // The first 5 (indices 0-4) are the most severe ones in decreasing order
+  // of severity; the next 5 (indices 5-9) are the most recent ones that are
+  // not already in the "severe" list.
+  static const int SEVERITY_MAX_INDEX = 5;
+  static const int RECENT_MAX_INDEX = 10;
+  CheckerboardReport mCheckerboardReports[RECENT_MAX_INDEX];
+};
+
+} // namespace layers
+
+namespace dom {
+
+class GlobalObject;
+
+/**
+ * CheckerboardReportService is a wrapper object that allows access to the
+ * stuff in CheckerboardEventStorage (above). We need this wrapper for proper
+ * garbage/cycle collection, since this can be accessed from JS.
+ */
+class CheckerboardReportService : public nsWrapperCache
+{
+public:
+  /**
+   * Check if the given page is allowed to access this object via the WebIDL
+   * bindings. It only returns true if the page is about:checkerboard.
+   */
+  static bool IsEnabled(JSContext* aCtx, JSObject* aGlobal);
+
+  /*
+   * Other standard WebIDL binding glue.
+   */
+
+  static already_AddRefed<CheckerboardReportService>
+    Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv);
+
+  explicit CheckerboardReportService(nsISupports* aSupports);
+
+  JSObject* WrapObject(JSContext* aCtx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsISupports* GetParentObject();
+
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CheckerboardReportService)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CheckerboardReportService)
+
+public:
+  /*
+   * The methods exposed via the webidl.
+   */
+  void GetReports(nsTArray<dom::CheckerboardReport>& aOutReports);
+  bool IsRecordingEnabled() const;
+  void SetRecordingEnabled(bool aEnabled);
+
+private:
+  virtual ~CheckerboardReportService() {}
+
+  nsCOMPtr<nsISupports> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_layers_CheckerboardReportService_h */
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -87,16 +87,20 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
             'd3d11/CompositorD3D11.cpp',
             'd3d11/ReadbackManagerD3D11.cpp',
         ]
 
 EXPORTS.gfxipc += [
     'ipc/ShadowLayerUtils.h',
 ]
 
+EXPORTS.mozilla.dom += [
+    'apz/util/CheckerboardReportService.h',
+]
+
 EXPORTS.mozilla.layers += [
     'apz/public/GeckoContentController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
     'apz/src/AsyncDragMetrics.h',
     'apz/src/AsyncPanZoomAnimation.h',
@@ -259,16 +263,17 @@ UNIFIED_SOURCES += [
     'apz/src/PotentialCheckerboardDurationTracker.cpp',
     'apz/src/TouchCounter.cpp',
     'apz/src/WheelScrollAnimation.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
     'apz/util/APZEventState.cpp',
     'apz/util/APZThreadUtils.cpp',
+    'apz/util/CheckerboardReportService.cpp',
     'apz/util/ChromeProcessController.cpp',
     'apz/util/DoubleTapToZoom.cpp',
     'apz/util/InputAPZContext.cpp',
     'apz/util/ScrollLinkedEffectDetector.cpp',
     'AsyncCanvasRenderer.cpp',
     'AxisPhysicsModel.cpp',
     'AxisPhysicsMSDModel.cpp',
     'basic/BasicCanvasLayer.cpp',