diff --git a/docshell/base/TimelineMarker.cpp b/docshell/base/TimelineMarker.cpp index c9083c64942..2bae8e8e413 100644 --- a/docshell/base/TimelineMarker.cpp +++ b/docshell/base/TimelineMarker.cpp @@ -22,7 +22,8 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName, TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName, TracingMetadata aMetaData, - const nsAString& aCause) + const nsAString& aCause, + TimelineStackRequest aStackRequest) : mName(aName) , mMetaData(aMetaData) , mCause(aCause) @@ -30,7 +31,7 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName, MOZ_COUNT_CTOR(TimelineMarker); MOZ_ASSERT(aName); aDocShell->Now(&mTime); - if (aMetaData == TRACING_INTERVAL_START) { + if (aMetaData == TRACING_INTERVAL_START && aStackRequest != NO_STACK) { CaptureStack(); } } diff --git a/docshell/base/TimelineMarker.h b/docshell/base/TimelineMarker.h index 1b4ea8ceef4..a13baef3974 100644 --- a/docshell/base/TimelineMarker.h +++ b/docshell/base/TimelineMarker.h @@ -21,12 +21,15 @@ class nsDocShell; class TimelineMarker { public: + enum TimelineStackRequest { STACK, NO_STACK }; + TimelineMarker(nsDocShell* aDocShell, const char* aName, TracingMetadata aMetaData); TimelineMarker(nsDocShell* aDocShell, const char* aName, TracingMetadata aMetaData, - const nsAString& aCause); + const nsAString& aCause, + TimelineStackRequest aStackRequest = STACK); virtual ~TimelineMarker(); @@ -43,7 +46,9 @@ public: // called on both the starting and ending markers of a pair. // Ordinarily the ending marker doesn't need to do anything // here. - virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) {} + virtual void AddDetails(JSContext* aCx, + mozilla::dom::ProfileTimelineMarker& aMarker) + {} virtual void AddLayerRectangles( mozilla::dom::Sequence&) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 57dd6543b11..04652e753b2 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -2997,7 +2997,7 @@ nsDocShell::PopProfileTimelineMarkers( marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); marker->mStart = startPayload->GetTime(); marker->mEnd = startPayload->GetTime(); - startPayload->AddDetails(*marker); + startPayload->AddDetails(aCx, *marker); continue; } @@ -3045,8 +3045,8 @@ nsDocShell::PopProfileTimelineMarkers( if (isPaint) { marker->mRectangles.Construct(layerRectangles); } - startPayload->AddDetails(*marker); - endPayload->AddDetails(*marker); + startPayload->AddDetails(aCx, *marker); + endPayload->AddDetails(aCx, *marker); } // We want the start to be dropped either way. @@ -13912,27 +13912,61 @@ class JavascriptTimelineMarker : public TimelineMarker { public: JavascriptTimelineMarker(nsDocShell* aDocShell, const char* aName, - const char* aReason) + const char* aReason, + const char16_t* aFunctionName, + const char16_t* aFileName, + uint32_t aLineNumber) : TimelineMarker(aDocShell, aName, TRACING_INTERVAL_START, - NS_ConvertUTF8toUTF16(aReason)) + NS_ConvertUTF8toUTF16(aReason), + NO_STACK) + , mFunctionName(aFunctionName) + , mFileName(aFileName) + , mLineNumber(aLineNumber) { } - void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override + void AddDetails(JSContext* aCx, mozilla::dom::ProfileTimelineMarker& aMarker) + override { aMarker.mCauseName.Construct(GetCause()); + + if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) { + ProfileTimelineStackFrame stackFrame; + stackFrame.mLine.Construct(mLineNumber); + stackFrame.mSource.Construct(mFileName); + stackFrame.mFunctionDisplayName.Construct(mFunctionName); + + JS::Rooted newStack(aCx); + if (ToJSValue(aCx, stackFrame, &newStack)) { + if (newStack.isObject()) { + aMarker.mStack = &newStack.toObject(); + } + } else { + JS_ClearPendingException(aCx); + } + } } + +private: + nsString mFunctionName; + nsString mFileName; + uint32_t mLineNumber; }; void -nsDocShell::NotifyJSRunToCompletionStart(const char* aReason) +nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, + const char16_t* aFunctionName, + const char16_t* aFilename, + const uint32_t aLineNumber) { bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers(); // If first start, mark interval start. if (timelineOn && mJSRunToCompletionDepth == 0) { mozilla::UniquePtr marker = - MakeUnique(this, "Javascript", aReason); + MakeUnique(this, "Javascript", aReason, + aFunctionName, aFilename, + aLineNumber); AddProfileTimelineMarker(Move(marker)); } mJSRunToCompletionDepth++; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 96519454278..5e44c351548 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -54,7 +54,7 @@ interface nsITabParent; typedef unsigned long nsLoadFlags; -[scriptable, builtinclass, uuid(bf78de98-9e88-498d-bc19-0e138f683939)] +[scriptable, builtinclass, uuid(696b32a1-3cf1-4909-b501-474b25fc7954)] interface nsIDocShell : nsIDocShellTreeItem { /** @@ -1039,7 +1039,10 @@ interface nsIDocShell : nsIDocShellTreeItem * that execution has stopped. This only occurs when the Timeline devtool * is collecting information. */ - [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason); + [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason, + in wstring functionName, + in wstring fileName, + in unsigned long lineNumber); [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop(); /** diff --git a/docshell/test/browser/browser_timelineMarkers-frame-04.js b/docshell/test/browser/browser_timelineMarkers-frame-04.js index d81816073ee..7a6278a1da8 100644 --- a/docshell/test/browser/browser_timelineMarkers-frame-04.js +++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js @@ -26,6 +26,8 @@ let TESTS = [{ // cause. let jsMarkers = markers.filter(m => m.name == "Javascript" && m.causeName); ok(jsMarkers.length > 0, "Got some Javascript markers"); + is(jsMarkers[0].stack.functionDisplayName, "do_xhr", + "Javascript marker has entry point name"); } }]; diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index de1632766f2..7d186af3aaf 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -1019,7 +1019,8 @@ public: return GetCause() == aOther.GetCause(); } - virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override + virtual void AddDetails(JSContext* aCx, + mozilla::dom::ProfileTimelineMarker& aMarker) override { if (GetMetaData() == TRACING_INTERVAL_START) { aMarker.mCauseName.Construct(GetCause()); @@ -1041,7 +1042,8 @@ public: MOZ_ASSERT(aMetaData == TRACING_TIMESTAMP); } - virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override + virtual void AddDetails(JSContext* aCx, + mozilla::dom::ProfileTimelineMarker& aMarker) override { if (!GetCause().IsEmpty()) { aMarker.mCauseName.Construct(GetCause()); diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index 81696f2d0f5..cbd80827ba2 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -529,7 +529,6 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, aCx ? aCx : FindJSContext(aGlobalObject)) , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true) , mWebIDLCallerPrincipal(nullptr) - , mDocShellForJSRunToCompletion(nullptr) , mIsMainThread(aIsMainThread) { MOZ_ASSERT(aGlobalObject); @@ -540,23 +539,12 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, } if (aIsMainThread && gRunToCompletionListeners > 0) { - nsCOMPtr window = do_QueryInterface(aGlobalObject); - if (window) { - mDocShellForJSRunToCompletion = window->GetDocShell(); - } - } - - if (mDocShellForJSRunToCompletion) { - mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStart(aReason); + mDocShellEntryMonitor.emplace(cx(), aReason); } } AutoEntryScript::~AutoEntryScript() { - if (mDocShellForJSRunToCompletion) { - mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStop(); - } - if (mIsMainThread) { nsContentUtils::LeaveMicroTask(); } @@ -567,6 +555,76 @@ AutoEntryScript::~AutoEntryScript() JS_MaybeGC(cx()); } +AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx, + const char* aReason) + : JS::dbg::AutoEntryMonitor(aCx) + , mReason(aReason) +{ +} + +void +AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction, + JSScript* aScript) +{ + JS::Rooted rootedFunction(aCx); + if (aFunction) { + rootedFunction = aFunction; + } + JS::Rooted rootedScript(aCx); + if (aScript) { + rootedScript = aScript; + } + + nsCOMPtr window = + do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); + if (!window || !window->GetDocShell() || + !window->GetDocShell()->GetRecordProfileTimelineMarkers()) { + return; + } + + nsCOMPtr docShellForJSRunToCompletion = window->GetDocShell(); + nsString filename; + uint32_t lineNumber = 0; + + js::AutoStableStringChars functionName(aCx); + if (rootedFunction) { + JS::Rooted displayId(aCx, JS_GetFunctionDisplayId(rootedFunction)); + if (displayId) { + functionName.initTwoByte(aCx, displayId); + } + } + + if (!rootedScript) { + rootedScript = JS_GetFunctionScript(aCx, rootedFunction); + } + if (rootedScript) { + filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript)); + lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript); + } + + if (!filename.IsEmpty() || functionName.isTwoByte()) { + const char16_t* functionNameChars = functionName.isTwoByte() ? + functionName.twoByteChars() : nullptr; + + docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason, + functionNameChars, + filename.BeginReading(), + lineNumber); + } +} + +void +AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx) +{ + nsCOMPtr window = + do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); + // Not really worth checking GetRecordProfileTimelineMarkers here. + if (window && window->GetDocShell()) { + nsCOMPtr docShellForJSRunToCompletion = window->GetDocShell(); + docShellForJSRunToCompletion->NotifyJSRunToCompletionStop(); + } +} + AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false) , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread()) diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index 6527f54a87d..ee25dbfcf43 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -16,6 +16,7 @@ #include "mozilla/Maybe.h" #include "jsapi.h" +#include "js/Debug.h" class nsPIDOMWindow; class nsGlobalWindow; @@ -339,6 +340,30 @@ public: } private: + // A subclass of AutoEntryMonitor that notifies the docshell. + class DocshellEntryMonitor : public JS::dbg::AutoEntryMonitor + { + public: + DocshellEntryMonitor(JSContext* aCx, const char* aReason); + + void Entry(JSContext* aCx, JSFunction* aFunction) override + { + Entry(aCx, aFunction, nullptr); + } + + void Entry(JSContext* aCx, JSScript* aScript) override + { + Entry(aCx, nullptr, aScript); + } + + void Exit(JSContext* aCx) override; + + private: + void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript); + + const char* mReason; + }; + // It's safe to make this a weak pointer, since it's the subject principal // 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 @@ -349,7 +374,7 @@ private: nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; friend nsIPrincipal* GetWebIDLCallerPrincipal(); - nsCOMPtr mDocShellForJSRunToCompletion; + Maybe mDocShellEntryMonitor; bool mIsMainThread; }; diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index d5de395b0f9..146b8ef430e 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1037,7 +1037,8 @@ public: { } - virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override + virtual void AddDetails(JSContext* aCx, + mozilla::dom::ProfileTimelineMarker& aMarker) override { if (GetMetaData() == TRACING_INTERVAL_START) { aMarker.mType.Construct(GetCause()); diff --git a/dom/webidl/ProfileTimelineMarker.webidl b/dom/webidl/ProfileTimelineMarker.webidl index 6de59cf2a5d..03ffb804d66 100644 --- a/dom/webidl/ProfileTimelineMarker.webidl +++ b/dom/webidl/ProfileTimelineMarker.webidl @@ -4,6 +4,17 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// For Javascript markers, the |stack| of a ProfileTimelineMarker +// holds an object of this type. It intentionally looks like a +// SavedStack object and is a representation of the frame that is +// about to be constructed at the entry point. +dictionary ProfileTimelineStackFrame { + long line; + long column = 0; + DOMString source; + DOMString functionDisplayName; +}; + dictionary ProfileTimelineLayerRect { long x = 0; long y = 0; diff --git a/layout/base/RestyleTracker.cpp b/layout/base/RestyleTracker.cpp index dcc5e420ebc..8127ff746b3 100644 --- a/layout/base/RestyleTracker.cpp +++ b/layout/base/RestyleTracker.cpp @@ -109,7 +109,8 @@ public: } } - virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override + virtual void AddDetails(JSContext* aCx, + mozilla::dom::ProfileTimelineMarker& aMarker) override { if (GetMetaData() == TRACING_INTERVAL_START) { aMarker.mRestyleHint.Construct(mRestyleHint);