author | Tom Tromey <tromey@mozilla.com> |
Wed, 20 May 2015 05:28:00 -0400 (2015-05-20) | |
changeset 245282 | 23e8df452487eb978654e17ee785039d58a19d9b |
parent 245281 | 2d38fecce226b96c37f5b6da75be2172232080b2 |
child 245283 | 76c0e3be4fe8cbfb34251046a2872569f5ea6bd2 |
push id | 28800 |
push user | philringnalda@gmail.com |
push date | Sat, 23 May 2015 21:28:40 +0000 (2015-05-23) |
treeherder | mozilla-central@373fecc9d868 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 1050500 |
milestone | 41.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
|
--- a/docshell/base/TimelineMarker.cpp +++ b/docshell/base/TimelineMarker.cpp @@ -17,25 +17,26 @@ TimelineMarker::TimelineMarker(nsDocShel aDocShell->Now(&mTime); if (aMetaData == TRACING_INTERVAL_START) { CaptureStack(); } } TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName, TracingMetadata aMetaData, - const nsAString& aCause) + const nsAString& aCause, + TimelineStackRequest aStackRequest) : mName(aName) , mMetaData(aMetaData) , mCause(aCause) { MOZ_COUNT_CTOR(TimelineMarker); MOZ_ASSERT(aName); aDocShell->Now(&mTime); - if (aMetaData == TRACING_INTERVAL_START) { + if (aMetaData == TRACING_INTERVAL_START && aStackRequest != NO_STACK) { CaptureStack(); } } TimelineMarker::~TimelineMarker() { MOZ_COUNT_DTOR(TimelineMarker); }
--- a/docshell/base/TimelineMarker.h +++ b/docshell/base/TimelineMarker.h @@ -16,39 +16,44 @@ class nsDocShell; // Objects of this type can be added to the timeline. The class can // also be subclassed to let a given marker creator provide custom // details. 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(); // Check whether two markers should be considered the same, // for the purpose of pairing start and end markers. Normally // this definition suffices. virtual bool Equals(const TimelineMarker& aOther) { return strcmp(mName, aOther.mName) == 0; } // Add details specific to this marker type to aMarker. The // standard elements have already been set. This method is // 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<mozilla::dom::ProfileTimelineLayerRect>&) { MOZ_ASSERT_UNREACHABLE("can only be called on layer markers"); } const char* GetName() const { return mName; }
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -2992,17 +2992,17 @@ nsDocShell::PopProfileTimelineMarkers( // marker here. if (startPayload->GetMetaData() == TRACING_TIMESTAMP) { mozilla::dom::ProfileTimelineMarker* marker = profileTimelineMarkers.AppendElement(); marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); marker->mStart = startPayload->GetTime(); marker->mEnd = startPayload->GetTime(); - startPayload->AddDetails(*marker); + startPayload->AddDetails(aCx, *marker); continue; } if (startPayload->GetMetaData() == TRACING_INTERVAL_START) { bool hasSeenEnd = false; // DOM events can be nested, so we must take care when searching // for the matching end. It doesn't hurt to apply this logic to @@ -3040,18 +3040,18 @@ nsDocShell::PopProfileTimelineMarkers( marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); marker->mStart = startPayload->GetTime(); marker->mEnd = endPayload->GetTime(); marker->mStack = startPayload->GetStack(); 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. hasSeenEnd = true; break; } } @@ -13907,37 +13907,71 @@ nsDocShell::GetURLSearchParams() { return mURLSearchParams; } 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<JS::Value> 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<TimelineMarker> marker = - MakeUnique<JavascriptTimelineMarker>(this, "Javascript", aReason); + MakeUnique<JavascriptTimelineMarker>(this, "Javascript", aReason, + aFunctionName, aFilename, + aLineNumber); AddProfileTimelineMarker(Move(marker)); } mJSRunToCompletionDepth++; } void nsDocShell::NotifyJSRunToCompletionStop() {
--- 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(bf78de98-9e88-498d-bc19-0e138f683939)] +[scriptable, builtinclass, uuid(696b32a1-3cf1-4909-b501-474b25fc7954)] 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. * @@ -1034,17 +1034,20 @@ interface nsIDocShell : nsIDocShellTreeI // 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(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(); /** * This attribute determines whether a document which is not about:blank has * already be loaded by this docShell. */ [infallible] readonly attribute boolean hasLoadedNonBlankURI;
--- a/docshell/test/browser/browser_timelineMarkers-frame-04.js +++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js @@ -21,12 +21,14 @@ let TESTS = [{ // first state transition is reported synchronously and so should // show up as a nested marker. is(domMarkers.length, 5, "Got 5 markers"); // We should see some Javascript markers, and they should have a // 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"); } }]; timelineContentTest(TESTS);
--- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -1014,17 +1014,18 @@ public: { if (!TimelineMarker::Equals(aOther)) { return false; } // Console markers must have matching causes as well. 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()); } else { aMarker.mEndStack = GetStack(); } } }; @@ -1036,17 +1037,18 @@ public: TracingMetadata aMetaData, const nsAString& aCause) : TimelineMarker(aDocShell, "TimeStamp", aMetaData, aCause) { CaptureStack(); 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()); } aMarker.mEndStack = GetStack(); } };
--- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -524,54 +524,112 @@ AutoJSAPI::StealException(JS::MutableHan AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, const char *aReason, bool aIsMainThread, JSContext* aCx) : AutoJSAPI(aGlobalObject, aIsMainThread, aCx ? aCx : FindJSContext(aGlobalObject)) , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true) , mWebIDLCallerPrincipal(nullptr) - , mDocShellForJSRunToCompletion(nullptr) , mIsMainThread(aIsMainThread) { MOZ_ASSERT(aGlobalObject); MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread. MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject)); if (aIsMainThread) { nsContentUtils::EnterMicroTask(); } if (aIsMainThread && gRunToCompletionListeners > 0) { - nsCOMPtr<nsPIDOMWindow> 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(); } // 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()); } +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<JSFunction*> rootedFunction(aCx); + if (aFunction) { + rootedFunction = aFunction; + } + JS::Rooted<JSScript*> rootedScript(aCx); + if (aScript) { + rootedScript = aScript; + } + + nsCOMPtr<nsPIDOMWindow> window = + do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); + if (!window || !window->GetDocShell() || + !window->GetDocShell()->GetRecordProfileTimelineMarkers()) { + return; + } + + nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell(); + nsString filename; + uint32_t lineNumber = 0; + + js::AutoStableStringChars functionName(aCx); + if (rootedFunction) { + JS::Rooted<JSString*> 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<nsPIDOMWindow> window = + do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); + // Not really worth checking GetRecordProfileTimelineMarkers here. + if (window && window->GetDocShell()) { + nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell(); + docShellForJSRunToCompletion->NotifyJSRunToCompletionStop(); + } +} + AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false) , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread()) { } AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread) : ScriptSettingsStackEntry()
--- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -11,16 +11,17 @@ #include "MainThreadUtils.h" #include "nsIGlobalObject.h" #include "nsIPrincipal.h" #include "mozilla/Maybe.h" #include "jsapi.h" +#include "js/Debug.h" class nsPIDOMWindow; class nsGlobalWindow; class nsIScriptContext; class nsIDocument; class nsIDocShell; namespace mozilla { @@ -334,27 +335,51 @@ public: ~AutoEntryScript(); void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { mWebIDLCallerPrincipal = aPrincipal; } 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 // 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* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; friend nsIPrincipal* GetWebIDLCallerPrincipal(); - nsCOMPtr<nsIDocShell> mDocShellForJSRunToCompletion; + Maybe<DocshellEntryMonitor> mDocShellEntryMonitor; bool mIsMainThread; }; /* * A class that can be used to force a particular incumbent script on the stack. */ class AutoIncumbentScript : protected ScriptSettingsStackEntry {
--- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1032,17 +1032,18 @@ class EventTimelineMarker : public Timel public: EventTimelineMarker(nsDocShell* aDocShell, TracingMetadata aMetaData, uint16_t aPhase, const nsAString& aCause) : TimelineMarker(aDocShell, "DOMEvent", aMetaData, aCause) , mPhase(aPhase) { } - 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()); aMarker.mEventPhase.Construct(mPhase); } } private:
--- a/dom/webidl/ProfileTimelineMarker.webidl +++ b/dom/webidl/ProfileTimelineMarker.webidl @@ -1,14 +1,25 @@ /* -*- 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/. */ +// 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; long width = 0; long height = 0; }; dictionary ProfileTimelineMarker {
--- a/layout/base/RestyleTracker.cpp +++ b/layout/base/RestyleTracker.cpp @@ -104,17 +104,18 @@ public: nsRestyleHint aRestyleHint) : TimelineMarker(aDocShell, "Styles", aMetaData) { if (aRestyleHint) { mRestyleHint.AssignWithConversion(RestyleManager::RestyleHintToString(aRestyleHint)); } } - 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); } } private: nsAutoString mRestyleHint;