Bug 1050774 - Record script execution in timeline view. r=bholley
authorKannan Vijayan <kvijayan@mozilla.com>
Thu, 13 Nov 2014 16:22:24 -0500
changeset 239972 451e1d755d89b0c4b678f35ac85a782fccf6752c
parent 239971 c9e1814d648d006db6da926266cd060d003043e9
child 239973 a9034a65b5291f6dc7b31be626d7a6a84bb19a45
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1050774
milestone36.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 1050774 - Record script execution in timeline view. r=bholley
browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
browser/devtools/timeline/widgets/global.js
browser/locales/en-US/chrome/browser/devtools/timeline.properties
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
+++ b/browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
@@ -8,16 +8,18 @@
 
 var gRGB_TO_HSL = {
  "rgb(193, 132, 214)": "hsl(285,50%,68%)",
  "rgb(152, 61, 183)": "hsl(285,50%,48%)",
  "rgb(161, 223, 138)": "hsl(104,57%,71%)",
  "rgb(96, 201, 58)": "hsl(104,57%,51%)",
  "rgb(240, 195, 111)": "hsl(39,82%,69%)",
  "rgb(227, 155, 22)": "hsl(39,82%,49%)",
+ "rgb(204, 204, 204)": "hsl(0,0%,80%)",
+ "rgb(153, 153, 153)": "hsl(0,0%,60%)",
 };
 
 let test = Task.async(function*() {
   let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
   let { TIMELINE_BLUEPRINT } = devtools.require("devtools/timeline/global");
   let { $, $$, EVENTS, TimelineController } = panel.panelWin;
 
   yield TimelineController.toggleRecording();
--- a/browser/devtools/timeline/widgets/global.js
+++ b/browser/devtools/timeline/widgets/global.js
@@ -50,14 +50,20 @@ const TIMELINE_BLUEPRINT = {
     stroke: "hsl(219,82%,69%)",
     label: L10N.getStr("timeline.label.domevent")
   },
   "ConsoleTime": {
     group: 4,
     fill: "hsl(0,0%,80%)",
     stroke: "hsl(0,0%,60%)",
     label: L10N.getStr("timeline.label.consoleTime")
-  }
+  },
+  "Javascript": {
+    group: 4,
+    fill: "hsl(0,0%,80%)",
+    stroke: "hsl(0,0%,60%)",
+    label: L10N.getStr("timeline.label.javascript")
+  },
 };
 
 // Exported symbols.
 exports.L10N = L10N;
 exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/timeline.properties
@@ -33,16 +33,17 @@ timeline.tick=%S ms
 # This string is displayed in the timeline waterfall, as a title for the menu.
 timeline.records=RECORDS
 
 # LOCALIZATION NOTE (timeline.label.*):
 # These strings are displayed in the timeline waterfall, identifying markers.
 timeline.label.styles=Styles
 timeline.label.reflow=Reflow
 timeline.label.paint=Paint
+timeline.label.javascript=Javascript
 timeline.label.domevent=DOM Event
 timeline.label.consoleTime=Console
 
 # LOCALIZATION NOTE (graphs.memory):
 # This string is displayed in the memory graph of the Performance tool,
 # as the unit used to memory consumption. This label should be kept
 # AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
 graphs.memory=MB
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -849,17 +849,18 @@ nsDocShell::nsDocShell():
     mInEnsureScriptEnv(false),
 #endif
     mAffectPrivateSessionLifetime(true),
     mInvisible(false),
     mHasLoadedNonBlankURI(false),
     mDefaultLoadFlags(nsIRequest::LOAD_NORMAL),
     mFrameType(eFrameTypeRegular),
     mOwnOrContainingAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID),
-    mParentCharsetSource(0)
+    mParentCharsetSource(0),
+    mJSRunToCompletionDepth(0)
 {
     mHistoryID = ++gDocshellIDCounter;
     if (gDocShellCount++ == 0) {
         NS_ASSERTION(sURIFixup == nullptr,
                      "Huh, sURIFixup not null in first nsDocShell ctor!");
 
         CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup);
     }
@@ -885,16 +886,18 @@ nsDocShell::nsDocShell():
                     getpid(),
                     AssertedCast<unsigned long long>(mHistoryID));
   }
 #endif
 }
 
 nsDocShell::~nsDocShell()
 {
+    MOZ_ASSERT(!mProfileTimelineRecording);
+
     Destroy();
 
     nsCOMPtr<nsISHistoryInternal>
         shPrivate(do_QueryInterface(mSessionHistory));
     if (shPrivate) {
         shPrivate->SetRootDocShell(nullptr);
     }
 
@@ -2824,24 +2827,25 @@ nsDocShell::HistoryTransactionRemoved(in
 }
 
 unsigned long nsDocShell::gProfileTimelineRecordingsCount = 0;
 
 NS_IMETHODIMP
 nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
-  bool currentValue;
-  GetRecordProfileTimelineMarkers(&currentValue);
+  bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
   if (currentValue != aValue) {
     if (aValue) {
       ++gProfileTimelineRecordingsCount;
+      UseEntryScriptProfiling();
       mProfileTimelineRecording = true;
     } else {
       --gProfileTimelineRecordingsCount;
+      UnuseEntryScriptProfiling();
       mProfileTimelineRecording = false;
       ClearProfileTimelineMarkers();
     }
   }
 
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
@@ -13604,16 +13608,40 @@ nsDocShell::GetOpenedRemote()
 
 URLSearchParams*
 nsDocShell::GetURLSearchParams()
 {
   return mURLSearchParams;
 }
 
 void
+nsDocShell::NotifyJSRunToCompletionStart()
+{
+    bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
+
+    // If first start, mark interval start.
+    if (timelineOn && mJSRunToCompletionDepth == 0) {
+        AddProfileTimelineMarker("Javascript", TRACING_INTERVAL_START);
+    }
+    mJSRunToCompletionDepth++;
+}
+
+void
+nsDocShell::NotifyJSRunToCompletionStop()
+{
+    bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
+
+    // If last stop, mark interval end.
+    mJSRunToCompletionDepth--;
+    if (timelineOn && mJSRunToCompletionDepth == 0) {
+        AddProfileTimelineMarker("Javascript", TRACING_INTERVAL_END);
+    }
+}
+
+void
 nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString &aProvider,
                                             const nsString &aKeyword) {
 
   if (aProvider.IsEmpty()) {
     return;
   }
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -945,16 +945,20 @@ private:
     nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
     nsTObserverArray<nsWeakPtr> mPrivacyObservers;
     nsTObserverArray<nsWeakPtr> mReflowObservers;
     nsTObserverArray<nsWeakPtr> mScrollObservers;
     nsCString         mOriginalUriString;
     nsWeakPtr mOpener;
     nsWeakPtr mOpenedRemote;
 
+    // A depth count of how many times NotifyRunToCompletionStart
+    // has been called without a matching NotifyRunToCompletionStop.
+    uint32_t          mJSRunToCompletionDepth;
+
     // True if recording profiles.
     bool mProfileTimelineRecording;
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     struct InternalProfileTimelineMarker
     {
       InternalProfileTimelineMarker(const char* aName,
                                     ProfilerMarkerTracing* aPayload,
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -49,17 +49,17 @@ interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
  
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(da8f78f1-8f20-4d6d-be56-fe53e177b630)]
+[scriptable, builtinclass, uuid(4e3de242-0b2a-4cf0-81b5-a5fe8628431c)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -1022,14 +1022,22 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   [noscript,notxpcom,nostdcall] void setOpenedRemote(in nsITabParent aOpenedRemote);
   [noscript,notxpcom,nostdcall] nsITabParent getOpenedRemote();
 
   // URLSearchParams for the window.location is owned by the docShell.
   [noscript,notxpcom] URLSearchParams getURLSearchParams();
 
   /**
+   * Notify DocShell when the browser is about to start executing JS, and after
+   * that execution has stopped.  This only occurs when the Timeline devtool
+   * is collecting information.
+   */
+  [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart();
+  [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
+
+  /**
    * This attribute determines whether a document which is not about:blank has
    * already be loaded by this docShell.
    */
   [infallible] readonly attribute boolean hasLoadedNonBlankURI;
 
 };
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -7,16 +7,17 @@
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Assertions.h"
 
 #include "jsapi.h"
 #include "xpcprivate.h" // For AutoCxPusher guts
 #include "xpcpublic.h"
 #include "nsIGlobalObject.h"
+#include "nsIDocShell.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsTArray.h"
 #include "nsJSUtils.h"
 #include "nsDOMJSUtils.h"
@@ -65,18 +66,36 @@ public:
     }
     MOZ_CRASH("Non-empty stack should always have an entry point");
   }
 
   static nsIGlobalObject* EntryGlobal() {
     ScriptSettingsStackEntry *entry = EntryPoint();
     return entry ? entry->mGlobalObject : nullptr;
   }
+
 };
 
+static unsigned long gRunToCompletionListeners = 0;
+
+void
+UseEntryScriptProfiling()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ++gRunToCompletionListeners;
+}
+
+void
+UnuseEntryScriptProfiling()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(gRunToCompletionListeners > 0);
+  --gRunToCompletionListeners;
+}
+
 void
 InitScriptSettings()
 {
   if (!sScriptSettingsTLS.initialized()) {
     bool success = sScriptSettingsTLS.init();
     if (!success) {
       MOZ_CRASH();
     }
@@ -492,24 +511,40 @@ AutoJSAPI::StealException(JS::MutableHan
 
 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
                                  bool aIsMainThread,
                                  JSContext* aCx)
   : AutoJSAPI(aGlobalObject, aIsMainThread,
               aCx ? aCx : FindJSContext(aGlobalObject))
   , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
   , mWebIDLCallerPrincipal(nullptr)
+  , mDocShellForJSRunToCompletion(nullptr)
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
   MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
+
+  if (aIsMainThread && gRunToCompletionListeners > 0) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobalObject);
+    if (window) {
+        mDocShellForJSRunToCompletion = window->GetDocShell();
+    }
+  }
+
+  if (mDocShellForJSRunToCompletion) {
+    mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStart();
+  }
 }
 
 AutoEntryScript::~AutoEntryScript()
 {
+  if (mDocShellForJSRunToCompletion) {
+    mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
+  }
+
   // GC when we pop a script entry point. This is a useful heuristic that helps
   // us out on certain (flawed) benchmarks like sunspider, because it lets us
   // avoid GCing during the timing loop.
   JS_MaybeGC(cx());
 }
 
 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
   : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false)
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -16,16 +16,17 @@
 #include "mozilla/Maybe.h"
 
 #include "jsapi.h"
 
 class nsPIDOMWindow;
 class nsGlobalWindow;
 class nsIScriptContext;
 class nsIDocument;
+class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
 // For internal use only - use AutoJSAPI instead.
 namespace danger {
 
 /**
@@ -58,16 +59,26 @@ private:
 
 /*
  * System-wide setup/teardown routines. Init and Destroy should be invoked
  * once each, at startup and shutdown (respectively).
  */
 void InitScriptSettings();
 void DestroyScriptSettings();
 
+/*
+ * Static helpers in ScriptSettings which track the number of listeners
+ * of Javascript RunToCompletion events.  These should be used by the code in
+ * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
+ * settings that script run-to-completion needs to be monitored.
+ * SHOULD BE CALLED ONLY BY MAIN THREAD.
+ */
+void UseEntryScriptProfiling();
+void UnuseEntryScriptProfiling();
+
 // To implement a web-compatible browser, it is often necessary to obtain the
 // global object that is "associated" with the currently-running code. This
 // process is made more complicated by the fact that, historically, different
 // algorithms have operated with different definitions of the "associated"
 // global.
 //
 // HTML5 formalizes this into two concepts: the "incumbent global" and the
 // "entry global". The incumbent global corresponds to the global of the
@@ -326,16 +337,18 @@ private:
   // when we go on the stack, so can't go away until after we're gone.  In
   // particular, this is only used from the CallSetup constructor, and only in
   // the aIsJSImplementedWebIDL case.  And in that case, the subject principal
   // is the principal of the callee function that is part of the CallArgs just a
   // bit up the stack, and which will outlive us.  So we know the principal
   // can't go away until then either.
   nsIPrincipal* mWebIDLCallerPrincipal;
   friend nsIPrincipal* GetWebIDLCallerPrincipal();
+
+  nsIDocShell* mDocShellForJSRunToCompletion;
 };
 
 /*
  * A class that can be used to force a particular incumbent script on the stack.
  */
 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
 public:
   explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);