import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1149486 - Regroup PerformanceStats by window. r=jandem, r=bholley (bedbaab6b)
- fix build.... we have a nwer GetScriptableTop() now... (d4c5b9833) (properly fixed by me)
- Bug 1167308 - Add a simpler to use CallTyped for the many simple pre-cast cases; r=jonco (73124e936)
- Bug 1167313 - Simplify UbiNode::construct dispatch using CallTyped; r=jimb (ce8e33fdd)
- Bug 1167314 - Use CallTyped to simplify PushArena; r=jonco (4e07c7a96)
- Bug 1167309 - Remove MaybeCompartment using CallTyped; r=jonco (b85699798)
- missing bits of bug 1156708: Part2. Add pref to toggle new code. (122a90f03)
- Bug 1164777 - Part 1: move evalStaticScope to GlobalSharedContext. (r=shu) (b38066072)
- Bug 1164777 - Part 2: Make super.prop parse inside inside eval inside arrow functions. (r=shu) (f7a2fc1fa)
- Bug 1141865 - Part 5: Implement new.target inside eval. (r=jorendorff, r=jandem) (4e558cb93)
- Bug 1141865 - Part 6: Implement new.target in arrow functions. (r=jandem, r=jorendorff) (f7694ad0b)
- Bug 1156264 - Activate/deactivate jank and CPOW monitoring separately (low-level). r=jandem (3837061f6)
- Bug 1150259 - Deactivating subtest under old Windows/old Linux. r=yoric (128dc82d4)
- Bug 1150514 - in about:performance, fixing CSS of jank levels. r=Yoric (136e06827)
- Bug 1150548 - MISBEHAVING_ADDONS_CPOW_TIME_MS is in milliseconds, but is filled with microseconds data. r=yoric (b477de514)
- Bug 1155355, e10s, rewrite and reenable browser tests in layout/xul/t#est, don't show tooltips during a drag, use system event listeners for tooltips, r=billm (858570301) (partly)
- Bug 1142814 (attempt 2) - Optimize String.fromCharCode() when the number of args is small but > 1. r=jandem. (7c03f649e)
and partly import changes from mozilla in order to fix build:
- Bug 1149486 - Extract a window title and window ID for PerformanceStats. r=mossop
- Bug 1156264 - Activate/deactivate jank and CPOW monitoring separately (high-level). r=mossop
This commit is contained in:
2020-10-09 23:05:14 +08:00
parent b08d019cf6
commit 9fcdd744ea
83 changed files with 1532 additions and 736 deletions
-8
View File
@@ -1466,19 +1466,11 @@ void MediaFormatReader::ReleaseMediaResources()
}
}
void MediaFormatReader::NotifyResourcesStatusChanged()
{
if (mDecoder) {
mDecoder->NotifyWaitingForResourcesStatusChanged();
}
}
void
MediaFormatReader::SetIdle()
{
if (mSharedDecoderManager && mVideo.mDecoder) {
mSharedDecoderManager->SetIdle(mVideo.mDecoder);
NotifyResourcesStatusChanged();
}
}
-4
View File
@@ -155,7 +155,6 @@ private:
void DrainComplete(TrackType aTrack);
bool IsSupportedAudioMimeType(const nsACString& aMimeType);
bool IsSupportedVideoMimeType(const nsACString& aMimeType);
void NotifyResourcesStatusChanged();
bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
@@ -183,9 +182,6 @@ private:
virtual void DrainComplete() override {
mReader->DrainComplete(mType);
}
virtual void NotifyResourcesStatusChanged() override {
mReader->NotifyResourcesStatusChanged();
}
virtual void ReleaseMediaResources() override {
mReader->ReleaseMediaResources();
}
+15 -15
View File
@@ -1174,17 +1174,17 @@ NS_IMETHODIMP
ChromeTooltipListener::AddTooltipListener()
{
if (mEventTarget) {
nsresult rv = mEventTarget->AddEventListener(NS_LITERAL_STRING("keydown"),
this, false, false);
nsresult rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
this, false, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
false, false);
rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), this,
false, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->AddEventListener(NS_LITERAL_STRING("mouseout"), this,
false, false);
rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), this,
false, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
false, false);
rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), this,
false, false);
NS_ENSURE_SUCCESS(rv, rv);
mTooltipListenerInstalled = true;
@@ -1226,17 +1226,17 @@ ChromeTooltipListener::RemoveTooltipListener()
{
if (mEventTarget) {
nsresult rv =
mEventTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), this,
false);
mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this,
false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
this, false);
rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
this, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this,
rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), this,
false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
this, false);
rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
this, false);
NS_ENSURE_SUCCESS(rv, rv);
mTooltipListenerInstalled = false;
+19 -5
View File
@@ -9,17 +9,31 @@
#include "nsContentUtils.h"
#include "CPOWTimer.h"
CPOWTimer::CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: cx_(nullptr)
, startInterval_(0)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JSRuntime* runtime = JS_GetRuntime(cx);
if (!js::GetStopwatchIsMonitoringCPOW(runtime))
return;
cx_ = cx;
startInterval_ = PR_IntervalNow();
}
CPOWTimer::~CPOWTimer()
{
JSContext* cx = nsContentUtils::GetCurrentJSContextForThread();
if (!cx)
if (!cx_) {
// Monitoring was off when we started the timer.
return;
}
JSRuntime* runtime = JS_GetRuntime(cx);
if (!js::IsStopwatchActive(runtime))
JSRuntime* runtime = JS_GetRuntime(cx_);
if (!js::GetStopwatchIsMonitoringCPOW(runtime)) {
// Monitoring has been deactivated while we were in the timer.
return;
}
js::PerformanceData* performance = js::GetPerformanceData(runtime);
uint64_t duration = PR_IntervalToMicroseconds(PR_IntervalNow() - startInterval);
uint64_t duration = PR_IntervalToMicroseconds(PR_IntervalNow() - startInterval_);
performance->totalCPOWTime += duration;
}
+11 -7
View File
@@ -9,6 +9,7 @@
#define CPOWTIMER_H
#include "prinrval.h"
#include "jsapi.h"
/**
* A stopwatch measuring the duration of a CPOW call.
@@ -21,20 +22,23 @@
*/
class MOZ_STACK_CLASS CPOWTimer final {
public:
explicit inline CPOWTimer(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
: startInterval(PR_IntervalNow())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
explicit inline CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~CPOWTimer();
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
/**
* The instant at which the stopwatch was started.
* The context in which this timer was created, or `nullptr` if
* CPOW monitoring was off when the timer was created.
*/
PRIntervalTime startInterval;
JSContext* cx_;
/**
* The instant at which the stopwatch was started. Undefined
* if CPOW monitoring was off when the timer was created.
*/
PRIntervalTime startInterval_;
};
#endif
+1 -1
View File
@@ -146,7 +146,7 @@ const CPOWProxyHandler CPOWProxyHandler::singleton;
return false; \
} \
{ \
CPOWTimer timer; \
CPOWTimer timer(cx); \
return owner->call args; \
}
+1
View File
@@ -271,6 +271,7 @@ class Node {
"ubi::Base specializations must be the same size as ubi::Base");
Concrete<T>::construct(base(), ptr);
}
struct ConstructFunctor;
public:
Node() { construct<void>(nullptr); }
+8 -6
View File
@@ -361,15 +361,17 @@ EvalKernel(JSContext* cx, const CallArgs& args, EvalType evalType, AbstractFrame
esg.setNewScript(compiled);
}
return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
// Look up the newTarget from the frame iterator.
Value newTargetVal = NullValue();
return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, newTargetVal, ExecuteType(evalType),
NullFramePtr() /* evalInFrame */, args.rval().address());
}
bool
js::DirectEvalStringFromIon(JSContext* cx,
HandleObject scopeobj, HandleScript callerScript,
HandleValue thisValue, HandleString str,
jsbytecode* pc, MutableHandleValue vp)
HandleValue thisValue, HandleValue newTargetValue,
HandleString str, jsbytecode* pc, MutableHandleValue vp)
{
AssertInnerizedScopeChain(cx, *scopeobj);
@@ -461,8 +463,8 @@ js::DirectEvalStringFromIon(JSContext* cx,
nthisValue = ObjectValue(*obj);
}
return ExecuteKernel(cx, esg.script(), *scopeobj, nthisValue, ExecuteType(DIRECT_EVAL),
NullFramePtr() /* evalInFrame */, vp.address());
return ExecuteKernel(cx, esg.script(), *scopeobj, nthisValue, newTargetValue,
ExecuteType(DIRECT_EVAL), NullFramePtr() /* evalInFrame */, vp.address());
}
bool
@@ -534,7 +536,7 @@ js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScri
RootedValue rval(cx);
// XXXbz when this is fixed to pass in an actual ScopeObject, fix
// up the assert in js::CloneFunctionObject accordingly.
if (!ExecuteKernel(cx, script, *scope, thisv, EXECUTE_GLOBAL,
if (!ExecuteKernel(cx, script, *scope, thisv, UndefinedValue(), EXECUTE_GLOBAL,
NullFramePtr() /* evalInFrame */, rval.address()))
{
return false;
+2 -2
View File
@@ -30,8 +30,8 @@ DirectEval(JSContext* cx, const CallArgs& args);
extern bool
DirectEvalStringFromIon(JSContext* cx,
HandleObject scopeObj, HandleScript callerScript,
HandleValue thisValue, HandleString str,
jsbytecode * pc, MutableHandleValue vp);
HandleValue thisValue, HandleValue newTargetValue,
HandleString str, jsbytecode * pc, MutableHandleValue vp);
// True iff fun is a built-in eval function.
extern bool
+2 -7
View File
@@ -269,11 +269,8 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco
return nullptr;
bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
bool allowSuperProperty = savedCallerFun &&
evalCaller->functionOrCallerFunction()->allowSuperProperty();
Directives directives(options.strictOption);
GlobalSharedContext globalsc(cx, directives, options.extraWarningsOption, allowSuperProperty);
GlobalSharedContext globalsc(cx, directives, evalStaticScope, options.extraWarningsOption);
Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun,
options, staticLevel, sourceObject, 0,
@@ -287,7 +284,7 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco
options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
/* lazyScript = */ nullptr, options.forEval,
evalCaller, evalStaticScope, insideNonGlobalEval,
evalCaller, insideNonGlobalEval,
options.lineno, emitterMode);
if (!bce.init())
return nullptr;
@@ -510,7 +507,6 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha
MOZ_ASSERT(!options.forEval);
BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
/* insideEval = */ false, /* evalCaller = */ nullptr,
/* evalStaticScope = */ nullptr,
/* insideNonGlobalEval = */ false, options.lineno,
BytecodeEmitter::LazyFunction);
if (!bce.init())
@@ -638,7 +634,6 @@ CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyComp
BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script,
/* lazyScript = */ nullptr, /* insideEval = */ false,
/* evalCaller = */ nullptr,
/* evalStaticScope = */ nullptr,
/* insideNonGlobalEval = */ false, options.lineno);
if (!funbce.init())
return false;
+28 -9
View File
@@ -105,7 +105,6 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
Parser<FullParseHandler>* parser, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
bool insideEval, HandleScript evalCaller,
Handle<StaticEvalObject*> staticEvalScope,
bool insideNonGlobalEval, uint32_t lineNum,
EmitterMode emitterMode)
: sc(sc),
@@ -118,7 +117,6 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
current(&main),
parser(parser),
evalCaller(evalCaller),
evalStaticScope(staticEvalScope),
topStmt(nullptr),
topScopeStmt(nullptr),
staticScope(cx),
@@ -143,8 +141,6 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
{
MOZ_ASSERT_IF(evalCaller, insideEval);
MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
// Function scripts are never eval scripts.
MOZ_ASSERT_IF(evalStaticScope, !sc->isFunctionBox());
}
bool
@@ -773,7 +769,7 @@ BytecodeEmitter::enclosingStaticScope()
// Top-level eval scripts have a placeholder static scope so that
// StaticScopeIter may iterate through evals.
return evalStaticScope;
return sc->asGlobalSharedContext()->evalStaticScope();
}
return sc->asFunctionBox()->function();
@@ -5416,6 +5412,19 @@ BytecodeEmitter::emitFor(ParseNode* pn, ptrdiff_t top)
return emitNormalFor(pn, top);
}
bool
BytecodeEmitter::arrowNeedsNewTarget()
{
for (BytecodeEmitter* bce = this; bce; bce = bce->parent) {
SharedContext *sc = bce->sc;
if (sc->isFunctionBox() && sc->asFunctionBox()->function()->isArrow())
continue;
return sc->allowSyntax(SharedContext::AllowedSyntax::NewTarget);
}
MOZ_CRASH("impossible parent chain");
}
MOZ_NEVER_INLINE bool
BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
{
@@ -5487,7 +5496,6 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr,
insideEval, evalCaller,
/* evalStaticScope = */ nullptr,
insideNonGlobalEval, lineNum, emitterMode);
if (!bce2.init())
return false;
@@ -5508,10 +5516,21 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
/* Non-hoisted functions simply emit their respective op. */
if (!pn->functionIsHoisted()) {
/* JSOP_LAMBDA_ARROW is always preceded by JSOP_THIS. */
/* JSOP_LAMBDA_ARROW is always preceded by JSOP_THIS and a new.target */
MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
if (fun->isArrow() && !emit1(JSOP_THIS))
return false;
if (fun->isArrow()) {
if (!emit1(JSOP_THIS))
return false;
if (arrowNeedsNewTarget()) {
if (!emit1(JSOP_NEWTARGET))
return false;
} else {
if (!emit1(JSOP_NULL))
return false;
}
}
if (needsProto) {
MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA);
pn->setOp(JSOP_FUNWITHPROTO);
+2 -4
View File
@@ -136,8 +136,6 @@ struct BytecodeEmitter
Parser<FullParseHandler>* const parser;
HandleScript evalCaller; /* scripted caller info for eval and dbgapi */
Handle<StaticEvalObject*> evalStaticScope;
/* compile time scope for eval; does not imply stmt stack */
StmtInfoBCE* topStmt; /* top of statement info stack */
StmtInfoBCE* topScopeStmt; /* top lexical scope statement */
@@ -222,8 +220,7 @@ struct BytecodeEmitter
BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
bool insideEval, HandleScript evalCaller,
Handle<StaticEvalObject*> evalStaticScope, bool insideNonGlobalEval,
uint32_t lineNum, EmitterMode emitterMode = Normal);
bool insideNonGlobalEval, uint32_t lineNum, EmitterMode emitterMode = Normal);
bool init();
bool updateLocalsToFrameSlots();
@@ -435,6 +432,7 @@ struct BytecodeEmitter
bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
bool emitRegExp(uint32_t index);
bool arrowNeedsNewTarget();
MOZ_NEVER_INLINE bool emitFunction(ParseNode* pn, bool needsProto = false);
MOZ_NEVER_INLINE bool emitObject(ParseNode* pn);
+31 -15
View File
@@ -701,8 +701,9 @@ Parser<ParseHandler>::parse(JSObject* chain)
* protected from the GC by a root or a stack frame reference.
*/
Directives directives(options().strictOption);
GlobalSharedContext globalsc(context, directives, options().extraWarningsOption,
/* allowSuperProperty = */ false);
GlobalSharedContext globalsc(context, directives,
/* staticEvalScope = */ nullptr,
options().extraWarningsOption);
ParseContext<ParseHandler> globalpc(this, /* parent = */ nullptr, ParseHandler::null(),
&globalsc, /* newDirectives = */ nullptr,
/* staticLevel = */ 0, /* bodyid = */ 0,
@@ -7744,18 +7745,35 @@ Parser<ParseHandler>::argumentList(YieldHandling yieldHandling, Node listNode, b
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkAndMarkSuperScope()
Parser<ParseHandler>::checkAllowedNestedSyntax(SharedContext::AllowedSyntax allowed,
SharedContext** allowingContext)
{
for (GenericParseContext* gpc = pc; gpc; gpc = gpc->parent) {
SharedContext* sc = gpc->sc;
if (sc->allowSuperProperty()) {
if (sc->isFunctionBox())
sc->asFunctionBox()->setNeedsHomeObject();
return true;
} else if (sc->isFunctionBox() && !sc->asFunctionBox()->function()->isArrow()) {
// super is not legal in normal functions.
break;
}
// Arrow functions don't help decide whether we should allow nested
// syntax, as they don't store any of the necessary state for themselves.
if (sc->isFunctionBox() && sc->asFunctionBox()->function()->isArrow())
continue;
if (!sc->allowSyntax(allowed))
return false;
if (allowingContext)
*allowingContext = sc;
return true;
}
return false;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkAndMarkSuperScope()
{
SharedContext* foundContext = nullptr;
if (checkAllowedNestedSyntax(SharedContext::AllowedSyntax::SuperProperty, &foundContext)) {
if (foundContext->isFunctionBox())
foundContext->asFunctionBox()->setNeedsHomeObject();
return true;
}
return false;
}
@@ -7894,7 +7912,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool
// If we're in a method, mark the method as requiring
// support for 'super', since direct eval code can use it.
// (If we're not in a method, that's fine, so ignore the
// (If we're not in a method, that's fine, so ignore the
// return value.)
checkAndMarkSuperScope();
}
@@ -8552,9 +8570,7 @@ Parser<ParseHandler>::tryNewTarget(Node &newTarget)
return false;
}
if (!pc->sc->isFunctionBox() || pc->sc->asFunctionBox()->isGenerator() ||
pc->sc->asFunctionBox()->function()->isArrow())
{
if (!checkAllowedNestedSyntax(SharedContext::AllowedSyntax::NewTarget)) {
reportWithOffset(ParseError, false, begin, JSMSG_BAD_NEWTARGET);
return false;
}
+2
View File
@@ -588,6 +588,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node parenExprOrGeneratorComprehension(YieldHandling yieldHandling);
Node exprInParens(InHandling inHandling, YieldHandling yieldHandling);
bool checkAllowedNestedSyntax(SharedContext::AllowedSyntax allowed,
SharedContext** allowingContext = nullptr);
bool tryNewTarget(Node& newTarget);
bool checkAndMarkSuperScope();
+55 -8
View File
@@ -208,6 +208,7 @@ class SharedContext
virtual ObjectBox* toObjectBox() = 0;
inline bool isFunctionBox() { return toObjectBox() && toObjectBox()->isFunctionBox(); }
inline FunctionBox* asFunctionBox();
inline GlobalSharedContext* asGlobalSharedContext();
bool hasExplicitUseStrict() const { return anyCxFlags.hasExplicitUseStrict; }
bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
@@ -239,23 +240,56 @@ class SharedContext
return atom == context->names().dotGenerator || atom == context->names().dotGenRVal;
}
virtual bool allowSuperProperty() const = 0;
enum class AllowedSyntax {
NewTarget,
SuperProperty
};
virtual bool allowSyntax(AllowedSyntax allowed) const = 0;
protected:
static bool FunctionAllowsSyntax(JSFunction* func, AllowedSyntax allowed)
{
MOZ_ASSERT(!func->isArrow());
switch (allowed) {
case AllowedSyntax::NewTarget:
// For now, disallow new.target inside generators
return !func->isGenerator();
case AllowedSyntax::SuperProperty:
return func->allowSuperProperty();
default:;
}
MOZ_CRASH("Unknown AllowedSyntax query");
}
};
class GlobalSharedContext : public SharedContext
{
private:
bool allowSuperProperty_;
Handle<StaticEvalObject*> staticEvalScope_;
public:
GlobalSharedContext(ExclusiveContext* cx,
Directives directives, bool extraWarnings, bool allowSuperProperty)
Directives directives, Handle<StaticEvalObject*> staticEvalScope,
bool extraWarnings)
: SharedContext(cx, directives, extraWarnings),
allowSuperProperty_(allowSuperProperty)
staticEvalScope_(staticEvalScope)
{}
ObjectBox* toObjectBox() { return nullptr; }
bool allowSuperProperty() const { return allowSuperProperty_; }
HandleObject evalStaticScope() const { return staticEvalScope_; }
bool allowSyntax(AllowedSyntax allowed) const {
StaticScopeIter<CanGC> it(context, staticEvalScope_);
for (; !it.done(); it++) {
if (it.type() == StaticScopeIter<CanGC>::Function &&
!it.fun().isArrow())
{
return FunctionAllowsSyntax(&it.fun(), allowed);
}
}
return false;
}
};
class FunctionBox : public ObjectBox, public SharedContext
@@ -316,7 +350,7 @@ class FunctionBox : public ObjectBox, public SharedContext
void setArgumentsHasLocalBinding() { funCxFlags.argumentsHasLocalBinding = true; }
void setDefinitelyNeedsArgsObj() { MOZ_ASSERT(funCxFlags.argumentsHasLocalBinding);
funCxFlags.definitelyNeedsArgsObj = true; }
void setNeedsHomeObject() { MOZ_ASSERT(allowSuperProperty());
void setNeedsHomeObject() { MOZ_ASSERT(function()->allowSuperProperty());
funCxFlags.needsHomeObject = true; }
FunctionContextFlags flagsForNestedGeneratorComprehensionLambda() const {
@@ -352,8 +386,13 @@ class FunctionBox : public ObjectBox, public SharedContext
isGenerator();
}
bool allowSuperProperty() const {
return function()->allowSuperProperty();
bool allowSyntax(AllowedSyntax allowed) const {
// For now (!) we don't allow new.target in generators, and can't
// check that for functions we haven't finished parsing, as they
// don't have initialized scripts. Check from our stashed bits instead.
if (allowed == AllowedSyntax::NewTarget)
return !isGenerator();
return FunctionAllowsSyntax(function(), allowed);
}
};
@@ -364,6 +403,14 @@ SharedContext::asFunctionBox()
return static_cast<FunctionBox*>(this);
}
inline GlobalSharedContext*
SharedContext::asGlobalSharedContext()
{
MOZ_ASSERT(!isFunctionBox());
return static_cast<GlobalSharedContext*>(this);
}
// In generators, we treat all locals as aliased so that they get stored on the
// heap. This way there is less information to copy off the stack when
// suspending, and back on when resuming. It also avoids the need to create and
+7 -40
View File
@@ -1693,49 +1693,16 @@ PushArenaTyped(GCMarker* gcmarker, ArenaHeader* aheader)
gcmarker->traverse(i.get<T>());
}
struct PushArenaFunctor {
template <typename T> void operator()(GCMarker* gcmarker, ArenaHeader* aheader) {
PushArenaTyped<T>(gcmarker, aheader);
}
};
void
gc::PushArena(GCMarker* gcmarker, ArenaHeader* aheader)
{
switch (MapAllocToTraceKind(aheader->getAllocKind())) {
case JSTRACE_OBJECT:
PushArenaTyped<JSObject>(gcmarker, aheader);
break;
case JSTRACE_SCRIPT:
PushArenaTyped<JSScript>(gcmarker, aheader);
break;
case JSTRACE_STRING:
PushArenaTyped<JSString>(gcmarker, aheader);
break;
case JSTRACE_SYMBOL:
PushArenaTyped<JS::Symbol>(gcmarker, aheader);
break;
case JSTRACE_BASE_SHAPE:
PushArenaTyped<js::BaseShape>(gcmarker, aheader);
break;
case JSTRACE_JITCODE:
PushArenaTyped<js::jit::JitCode>(gcmarker, aheader);
break;
case JSTRACE_LAZY_SCRIPT:
PushArenaTyped<LazyScript>(gcmarker, aheader);
break;
case JSTRACE_SHAPE:
PushArenaTyped<js::Shape>(gcmarker, aheader);
break;
case JSTRACE_OBJECT_GROUP:
PushArenaTyped<js::ObjectGroup>(gcmarker, aheader);
break;
default:
MOZ_CRASH("Invalid trace kind in PushArena.");
}
CallTyped(PushArenaFunctor(), MapAllocToTraceKind(aheader->getAllocKind()), gcmarker, aheader);
}
#ifdef DEBUG
+2 -5
View File
@@ -592,10 +592,7 @@ js::gc::GCRuntime::bufferGrayRoots()
}
struct SetMaybeAliveFunctor {
template <typename T>
void operator()(Cell* cell) {
SetMaybeAliveFlag<T>(static_cast<T*>(cell));
}
template <typename T> void operator()(T* t) { SetMaybeAliveFlag(t); }
};
void
@@ -612,7 +609,7 @@ BufferGrayRootsTracer::appendGrayRoot(TenuredCell* thing, JSGCTraceKind kind)
// objects and scripts. We rely on gray root buffering for this to work,
// but we only need to worry about uncollected dead compartments during
// incremental GCs (when we do gray root buffering).
CallTyped(SetMaybeAliveFunctor(), kind, thing);
CallTyped(SetMaybeAliveFunctor(), thing, kind);
if (!zone->gcGrayRoots.append(thing))
bufferingGrayRootsFailed = true;
@@ -0,0 +1,38 @@
// Test that new.target is acceptably usable in RematerializedFrames.
load(libdir + "jitopts.js");
if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
quit();
withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
var g = newGlobal();
var dbg = new Debugger;
g.toggle = function toggle(d, expected) {
if (d) {
dbg.addDebuggee(g);
var frame = dbg.getNewestFrame();
assertEq(frame.implementation, "ion");
assertEq(frame.constructing, true);
// CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse
// in debug frame evals (presumably by hooking up static scope walks).
// Uncomment the assert below for efaust's undying gratitude.
// Note that we use .name here because of CCW nonsense.
assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError");
// assertEq(frame.eval('new.target').value.unsafeDereference(), expected);
}
};
g.eval("" + function f(d) { new g(d, g, 15); });
g.eval("" + function g(d, expected) { toggle(d, expected); });
g.eval("(" + function test() {
for (var i = 0; i < 5; i++)
f(false);
f(true);
} + ")();");
});
@@ -0,0 +1,41 @@
// Test that new.target is acceptably usable in RematerializedFrames.
load(libdir + "jitopts.js");
if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
quit();
withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
var g = newGlobal();
var dbg = new Debugger;
g.toggle = function toggle(d, expected) {
if (d) {
dbg.addDebuggee(g);
var frame = dbg.getNewestFrame();
assertEq(frame.implementation, "ion");
// the arrow function will not be constructing, even though it has a
// new.target value.
assertEq(frame.constructing, false);
// CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse
// in debug frame evals (presumably by hooking up static scope walks).
// Uncomment the assert below for efaust's undying gratitude.
// Note that we use .name here because of CCW nonsense.
assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError");
// assertEq(frame.eval('new.target').return.unsafeDereference(), expected);
}
};
g.eval("" + function f(d) { new g(d, g, 15); });
g.eval("" + function g(d, expected) { (() => toggle(d, expected))(); });
g.eval("(" + function test() {
for (var i = 0; i < 5; i++)
f(false);
f(true);
} + ")();");
});
+27 -5
View File
@@ -1452,22 +1452,24 @@ BaselineCompiler::emit_JSOP_LAMBDA()
return true;
}
typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject, HandleValue);
typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject,
HandleValue, HandleValue);
static const VMFunction LambdaArrowInfo = FunctionInfo<LambdaArrowFn>(js::LambdaArrow);
bool
BaselineCompiler::emit_JSOP_LAMBDA_ARROW()
{
// Keep pushed |this| in R0.
frame.popRegsAndSync(1);
// Keep pushed |this| in R0, and newTarget in R1.
frame.popRegsAndSync(2);
RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc)));
prepareVMCall();
masm.loadPtr(frame.addressOfScopeChain(), R1.scratchReg());
masm.loadPtr(frame.addressOfScopeChain(), R2.scratchReg());
pushArg(R1);
pushArg(R0);
pushArg(R1.scratchReg());
pushArg(R2.scratchReg());
pushArg(ImmGCPtr(fun));
if (!callVM(LambdaArrowInfo))
@@ -1502,6 +1504,11 @@ BaselineCompiler::storeValue(const StackValue* source, const Address& dest,
masm.loadValue(frame.addressOfThis(), scratch);
masm.storeValue(scratch, dest);
break;
case StackValue::EvalNewTargetSlot:
MOZ_ASSERT(script->isForEval());
masm.loadValue(frame.addressOfEvalNewTarget(), scratch);
masm.storeValue(scratch, dest);
break;
case StackValue::Stack:
masm.loadValue(frame.addressOfStackValue(source), scratch);
masm.storeValue(scratch, dest);
@@ -2670,9 +2677,24 @@ BaselineCompiler::emit_JSOP_SETARG()
bool
BaselineCompiler::emit_JSOP_NEWTARGET()
{
if (script->isForEval()) {
frame.pushEvalNewTarget();
return true;
}
MOZ_ASSERT(function());
frame.syncStack(0);
if (function()->isArrow()) {
// Arrow functions store their |new.target| value in an
// extended slot.
Register scratch = R0.scratchReg();
masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch);
masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowNewTargetSlot()), R0);
frame.push(R0);
return true;
}
// if (!isConstructing()) push(undefined)
Label constructing, done;
masm.branchTestPtr(Assembler::NonZero, frame.addressOfCalleeToken(),
+3 -1
View File
@@ -48,8 +48,10 @@ BaselineFrame::trace(JSTracer* trc, JitFrameIterator& frameIterator)
if (hasReturnValue())
TraceRoot(trc, returnValue().address(), "baseline-rval");
if (isEvalFrame())
if (isEvalFrame()) {
TraceRoot(trc, &evalScript_, "baseline-evalscript");
TraceRoot(trc, evalNewTargetAddress(), "baseline-evalNewTarget");
}
if (hasArgsObj())
TraceRoot(trc, &argsObj_, "baseline-args-obj");
+25
View File
@@ -217,6 +217,28 @@ class BaselineFrame
offsetOfArg(0));
}
private:
Value* evalNewTargetAddress() const {
MOZ_ASSERT(isEvalFrame());
return (Value*)(reinterpret_cast<const uint8_t*>(this) +
BaselineFrame::Size() +
offsetOfEvalNewTarget());
}
public:
Value newTarget() const {
MOZ_ASSERT(isFunctionFrame());
if (isEvalFrame())
return *evalNewTargetAddress();
if (fun()->isArrow())
return fun()->getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT);
if (isConstructing())
return *(Value*)(reinterpret_cast<const uint8_t*>(this) +
BaselineFrame::Size() +
offsetOfArg(Max(numFormalArgs(), numActualArgs())));
return UndefinedValue();
}
bool copyRawFrameSlots(AutoValueVector* vec) const;
bool hasReturnValue() const {
@@ -399,6 +421,9 @@ class BaselineFrame
static size_t offsetOfThis() {
return FramePointerOffset + js::jit::JitFrameLayout::offsetOfThis();
}
static size_t offsetOfEvalNewTarget() {
return offsetOfArg(0);
}
static size_t offsetOfArg(size_t index) {
return FramePointerOffset + js::jit::JitFrameLayout::offsetOfActualArg(index);
}
+7
View File
@@ -39,6 +39,10 @@ FrameInfo::sync(StackValue* val)
case StackValue::ThisSlot:
masm.pushValue(addressOfThis());
break;
case StackValue::EvalNewTargetSlot:
MOZ_ASSERT(script->isForEval());
masm.pushValue(addressOfEvalNewTarget());
break;
case StackValue::Register:
masm.pushValue(val->reg());
break;
@@ -95,6 +99,9 @@ FrameInfo::popValue(ValueOperand dest)
case StackValue::ThisSlot:
masm.loadValue(addressOfThis(), dest);
break;
case StackValue::EvalNewTargetSlot:
masm.loadValue(addressOfEvalNewTarget(), dest);
break;
case StackValue::Stack:
masm.popValue(dest);
break;
+15 -1
View File
@@ -54,7 +54,8 @@ class StackValue
Stack,
LocalSlot,
ArgSlot,
ThisSlot
ThisSlot,
EvalNewTargetSlot
#ifdef DEBUG
// In debug builds, assert Kind is initialized.
, Uninitialized
@@ -150,6 +151,10 @@ class StackValue
kind_ = ThisSlot;
knownType_ = JSVAL_TYPE_UNKNOWN;
}
void setEvalNewTarget() {
kind_ = EvalNewTargetSlot;
knownType_ = JSVAL_TYPE_UNKNOWN;
}
void setStack() {
kind_ = Stack;
knownType_ = JSVAL_TYPE_UNKNOWN;
@@ -259,6 +264,12 @@ class FrameInfo
StackValue* sv = rawPush();
sv->setThis();
}
inline void pushEvalNewTarget() {
MOZ_ASSERT(script->isForEval());
StackValue* sv = rawPush();
sv->setEvalNewTarget();
}
inline void pushScratchValue() {
masm.pushValue(addressOfScratchValue());
StackValue* sv = rawPush();
@@ -275,6 +286,9 @@ class FrameInfo
Address addressOfThis() const {
return Address(BaselineFrameReg, BaselineFrame::offsetOfThis());
}
Address addressOfEvalNewTarget() const {
return Address(BaselineFrameReg, BaselineFrame::offsetOfEvalNewTarget());
}
Address addressOfCalleeToken() const {
return Address(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken());
}
+16
View File
@@ -181,6 +181,7 @@ jit::EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc)
data.osrFrame = fp;
data.osrNumStackValues = fp->script()->nfixed() + cx->interpreterRegs().stackDepth();
AutoValueVector vals(cx);
RootedValue thisv(cx);
if (fp->isNonEvalFunctionFrame()) {
@@ -203,6 +204,21 @@ jit::EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc)
data.calleeToken = CalleeToToken(&fp->callee(), /* constructing = */ false);
else
data.calleeToken = CalleeToToken(fp->script());
if (fp->isEvalFrame()) {
if (!vals.reserve(2))
return JitExec_Aborted;
vals.infallibleAppend(thisv);
if (fp->isFunctionFrame())
vals.infallibleAppend(fp->newTarget());
else
vals.infallibleAppend(NullValue());
data.maxArgc = 2;
data.maxArgv = vals.begin();
}
}
TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+76 -9
View File
@@ -1720,42 +1720,100 @@ CodeGenerator::visitLambda(LLambda* lir)
masm.bind(ool->rejoin());
}
typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject, HandleValue);
class OutOfLineLambdaArrow : public OutOfLineCodeBase<CodeGenerator>
{
public:
LLambdaArrow* lir;
Label entryNoPop_;
explicit OutOfLineLambdaArrow(LLambdaArrow* lir)
: lir(lir)
{ }
void accept(CodeGenerator* codegen) {
codegen->visitOutOfLineLambdaArrow(this);
}
Label* entryNoPop() {
return &entryNoPop_;
}
};
typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject, HandleValue, HandleValue);
static const VMFunction LambdaArrowInfo = FunctionInfo<LambdaArrowFn>(js::LambdaArrow);
void
CodeGenerator::visitOutOfLineLambdaArrow(OutOfLineLambdaArrow* ool)
{
Register scopeChain = ToRegister(ool->lir->scopeChain());
ValueOperand thisv = ToValue(ool->lir, LLambdaArrow::ThisValue);
ValueOperand newTarget = ToValue(ool->lir, LLambdaArrow::NewTargetValue);
Register output = ToRegister(ool->lir->output());
const LambdaFunctionInfo& info = ool->lir->mir()->info();
// When we get here, we may need to restore part of the newTarget,
// which has been conscripted into service as a temp register.
masm.pop(newTarget.scratchReg());
masm.bind(ool->entryNoPop());
saveLive(ool->lir);
pushArg(newTarget);
pushArg(thisv);
pushArg(scopeChain);
pushArg(ImmGCPtr(info.fun));
callVM(LambdaArrowInfo, ool->lir);
StoreRegisterTo(output).generate(this);
restoreLiveIgnore(ool->lir, StoreRegisterTo(output).clobbered());
masm.jump(ool->rejoin());
}
void
CodeGenerator::visitLambdaArrow(LLambdaArrow* lir)
{
Register scopeChain = ToRegister(lir->scopeChain());
ValueOperand thisv = ToValue(lir, LLambdaArrow::ThisValue);
ValueOperand newTarget = ToValue(lir, LLambdaArrow::NewTargetValue);
Register output = ToRegister(lir->output());
Register tempReg = ToRegister(lir->temp());
const LambdaFunctionInfo& info = lir->mir()->info();
OutOfLineCode* ool = oolCallVM(LambdaArrowInfo, lir,
(ArgList(), ImmGCPtr(info.fun), scopeChain, thisv),
StoreRegisterTo(output));
OutOfLineLambdaArrow* ool = new (alloc()) OutOfLineLambdaArrow(lir);
addOutOfLineCode(ool, lir->mir());
MOZ_ASSERT(!info.useSingletonForClone);
if (info.singletonType) {
// If the function has a singleton type, this instruction will only be
// executed once so we don't bother inlining it.
masm.jump(ool->entry());
masm.jump(ool->entryNoPop());
masm.bind(ool->rejoin());
return;
}
// There's not enough registers on x86 with the profiler enabled to request
// a temp. Instead, spill part of one of the values, being prepared to
// restore it if necessary on the out of line path.
Register tempReg = newTarget.scratchReg();
masm.push(newTarget.scratchReg());
masm.createGCObject(output, tempReg, info.fun, gc::DefaultHeap, ool->entry());
masm.pop(newTarget.scratchReg());
emitLambdaInit(output, scopeChain, info);
// Initialize extended slots. Lexical |this| is stored in the first one.
MOZ_ASSERT(info.flags & JSFunction::EXTENDED);
static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized");
static_assert(FunctionExtended::ARROW_THIS_SLOT == 0, "|this| must be stored in first slot");
static_assert(FunctionExtended::ARROW_NEWTARGET_SLOT == 1,
"|new.target| must be stored in second slot");
masm.storeValue(thisv, Address(output, FunctionExtended::offsetOfExtendedSlot(0)));
masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(1)));
masm.storeValue(newTarget, Address(output, FunctionExtended::offsetOfExtendedSlot(1)));
masm.bind(ool->rejoin());
}
@@ -3446,8 +3504,8 @@ CodeGenerator::visitGetDynamicName(LGetDynamicName* lir)
bailoutFrom(&undefined, lir->snapshot());
}
typedef bool (*DirectEvalSFn)(JSContext*, HandleObject, HandleScript, HandleValue, HandleString,
jsbytecode*, MutableHandleValue);
typedef bool (*DirectEvalSFn)(JSContext*, HandleObject, HandleScript, HandleValue, HandleValue,
HandleString, jsbytecode*, MutableHandleValue);
static const VMFunction DirectEvalStringInfo = FunctionInfo<DirectEvalSFn>(DirectEvalStringFromIon);
void
@@ -3458,6 +3516,7 @@ CodeGenerator::visitCallDirectEval(LCallDirectEval* lir)
pushArg(ImmPtr(lir->mir()->pc()));
pushArg(string);
pushArg(ToValue(lir, LCallDirectEval::NewTarget));
pushArg(ToValue(lir, LCallDirectEval::ThisValue));
pushArg(ImmGCPtr(gen->info().script()));
pushArg(scopeChain);
@@ -4925,6 +4984,14 @@ CodeGenerator::visitLoadArrowThis(LLoadArrowThis* lir)
masm.loadValue(Address(callee, FunctionExtended::offsetOfArrowThisSlot()), output);
}
void
CodeGenerator::visitArrowNewTarget(LArrowNewTarget* lir)
{
Register callee = ToRegister(lir->callee());
ValueOperand output = ToOutValue(lir);
masm.loadValue(Address(callee, FunctionExtended::offsetOfArrowNewTargetSlot()), output);
}
void
CodeGenerator::visitArrayLength(LArrayLength* lir)
{
+3
View File
@@ -42,6 +42,7 @@ class OutOfLineCallPostWriteBarrier;
class OutOfLineIsCallable;
class OutOfLineRegExpExec;
class OutOfLineRegExpTest;
class OutOfLineLambdaArrow;
class CodeGenerator : public CodeGeneratorSpecific
{
@@ -104,6 +105,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void visitRegExpReplace(LRegExpReplace* lir);
void visitStringReplace(LStringReplace* lir);
void visitLambda(LLambda* lir);
void visitOutOfLineLambdaArrow(OutOfLineLambdaArrow* ool);
void visitLambdaArrow(LLambdaArrow* lir);
void visitLambdaForSingleton(LLambdaForSingleton* lir);
void visitPointer(LPointer* lir);
@@ -323,6 +325,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void visitThrowUninitializedLexical(LThrowUninitializedLexical* ins);
void visitDebugger(LDebugger* ins);
void visitNewTarget(LNewTarget* ins);
void visitArrowNewTarget(LArrowNewTarget* ins);
void visitCheckOverRecursed(LCheckOverRecursed* lir);
void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool);
+16
View File
@@ -2602,6 +2602,22 @@ jit::SetEnterJitData(JSContext* cx, EnterJitData& data, RunState& state, AutoVal
ScriptFrameIter iter(cx);
if (iter.isFunctionFrame())
data.calleeToken = CalleeToToken(iter.callee(cx), /* constructing = */ false);
// Push newTarget onto the stack, as well as Argv.
if (!vals.reserve(2))
return false;
data.maxArgc = 2;
data.maxArgv = vals.begin();
vals.infallibleAppend(state.asExecute()->thisv());
if (iter.isFunctionFrame()) {
if (state.asExecute()->newTarget().isNull())
vals.infallibleAppend(iter.newTarget());
else
vals.infallibleAppend(state.asExecute()->newTarget());
} else {
vals.infallibleAppend(NullValue());
}
}
}
+22 -2
View File
@@ -6447,6 +6447,10 @@ IonBuilder::jsop_eval(uint32_t argc)
current->pushSlot(info().thisSlot());
MDefinition* thisValue = current->pop();
if (!jsop_newtarget())
return false;
MDefinition* newTargetValue = current->pop();
// Try to pattern match 'eval(v + "()")'. In this case v is likely a
// name on the scope chain and the eval is performing a call on that
// value. Use a dynamic scope chain lookup rather than a full eval.
@@ -6472,7 +6476,8 @@ IonBuilder::jsop_eval(uint32_t argc)
}
}
MInstruction* ins = MCallDirectEval::New(alloc(), scopeChain, string, thisValue, pc);
MInstruction* ins = MCallDirectEval::New(alloc(), scopeChain, string,
thisValue, newTargetValue, pc);
current->add(ins);
current->push(ins);
@@ -9425,7 +9430,21 @@ IonBuilder::jsop_arguments()
bool
IonBuilder::jsop_newtarget()
{
if (!info().funMaybeLazy()) {
MOZ_ASSERT(!info().script()->isForEval());
pushConstant(NullValue());
return true;
}
MOZ_ASSERT(info().funMaybeLazy());
if (info().funMaybeLazy()->isArrow()) {
MArrowNewTarget* arrowNewTarget = MArrowNewTarget::New(alloc(), getCallee());
current->add(arrowNewTarget);
current->push(arrowNewTarget);
return true;
}
if (inliningDepth_ == 0) {
MNewTarget* newTarget = MNewTarget::New(alloc());
current->add(newTarget);
@@ -11874,10 +11893,11 @@ IonBuilder::jsop_lambda_arrow(JSFunction* fun)
MOZ_ASSERT(fun->isArrow());
MOZ_ASSERT(!fun->isNative());
MDefinition* newTargetDef = current->pop();
MDefinition* thisDef = current->pop();
MLambdaArrow* ins = MLambdaArrow::New(alloc(), constraints(), current->scopeChain(),
thisDef, fun);
thisDef, newTargetDef, fun);
current->add(ins);
current->push(ins);
+4 -4
View File
@@ -701,8 +701,8 @@ class InlineFrameIterator
template <class ArgOp, class LocalOp>
void readFrameArgsAndLocals(JSContext* cx, ArgOp& argOp, LocalOp& localOp,
JSObject** scopeChain, bool* hasCallObj, Value* rval,
ArgumentsObject** argsObj, Value* thisv,
JSObject** scopeChain, bool* hasCallObj,
Value* rval, ArgumentsObject** argsObj, Value* thisv,
ReadFrameArgsBehavior behavior,
MaybeReadFallback& fallback) const
{
@@ -762,13 +762,13 @@ class InlineFrameIterator
parent_s.skip(); // scope chain
parent_s.skip(); // return value
parent_s.readFunctionFrameArgs(argOp, nullptr, nullptr,
nformal, nactual, it.script(),
nformal, nactual + isConstructing(), it.script(),
fallback);
} else {
// There is no parent frame to this inlined frame, we can read
// from the frame's Value vector directly.
Value* argv = frame_->actualArgs();
for (unsigned i = nformal; i < nactual; i++)
for (unsigned i = nformal; i < nactual + isConstructing(); i++)
argOp(argv[i]);
}
}
+19 -7
View File
@@ -1870,7 +1870,7 @@ class LGetDynamicName : public LCallInstructionHelper<BOX_PIECES, 2, 3>
}
};
class LCallDirectEval : public LCallInstructionHelper<BOX_PIECES, 2 + BOX_PIECES, 0>
class LCallDirectEval : public LCallInstructionHelper<BOX_PIECES, 2 + (2 * BOX_PIECES), 0>
{
public:
LIR_HEADER(CallDirectEval)
@@ -1882,6 +1882,7 @@ class LCallDirectEval : public LCallInstructionHelper<BOX_PIECES, 2 + BOX_PIECES
}
static const size_t ThisValue = 2;
static const size_t NewTarget = 2 + BOX_PIECES;
MCallDirectEval* mir() const {
return mir_->toCallDirectEval();
@@ -3949,23 +3950,20 @@ class LLambda : public LInstructionHelper<1, 1, 1>
}
};
class LLambdaArrow : public LInstructionHelper<1, 1 + BOX_PIECES, 1>
class LLambdaArrow : public LInstructionHelper<1, 1 + (2 * BOX_PIECES), 0>
{
public:
LIR_HEADER(LambdaArrow)
static const size_t ThisValue = 1;
static const size_t NewTargetValue = ThisValue + BOX_PIECES;
LLambdaArrow(const LAllocation& scopeChain, const LDefinition& temp) {
explicit LLambdaArrow(const LAllocation& scopeChain) {
setOperand(0, scopeChain);
setTemp(0, temp);
}
const LAllocation* scopeChain() {
return getOperand(0);
}
const LDefinition* temp() {
return getTemp(0);
}
const MLambdaArrow* mir() const {
return mir_->toLambdaArrow();
}
@@ -6973,6 +6971,20 @@ class LNewTarget : public LInstructionHelper<BOX_PIECES, 0, 0>
LIR_HEADER(NewTarget)
};
class LArrowNewTarget : public LInstructionHelper<BOX_PIECES, 1, 0>
{
public:
explicit LArrowNewTarget(const LAllocation& callee) {
setOperand(0, callee);
}
LIR_HEADER(ArrowNewTarget)
const LAllocation* callee() {
return getOperand(0);
}
};
} // namespace jit
} // namespace js
+2 -1
View File
@@ -348,7 +348,8 @@
_(ThrowUninitializedLexical) \
_(NurseryObject) \
_(Debugger) \
_(NewTarget)
_(NewTarget) \
_(ArrowNewTarget)
#if defined(JS_CODEGEN_X86)
# include "jit/x86/LOpcodes-x86.h"
+15 -1
View File
@@ -385,6 +385,16 @@ LIRGenerator::visitLoadArrowThis(MLoadArrowThis* ins)
defineBox(lir, ins);
}
void
LIRGenerator::visitArrowNewTarget(MArrowNewTarget* ins)
{
MOZ_ASSERT(ins->type() == MIRType_Value);
MOZ_ASSERT(ins->callee()->type() == MIRType_Object);
LArrowNewTarget* lir = new(alloc()) LArrowNewTarget(useRegister(ins->callee()));
defineBox(lir, ins);
}
void
LIRGenerator::lowerCallArguments(MCall* call)
{
@@ -585,10 +595,12 @@ LIRGenerator::visitCallDirectEval(MCallDirectEval* ins)
MOZ_ASSERT(string->type() == MIRType_String);
MDefinition* thisValue = ins->getThisValue();
MDefinition* newTargetValue = ins->getNewTargetValue();
LInstruction* lir = new(alloc()) LCallDirectEval(useRegisterAtStart(scopeChain),
useRegisterAtStart(string));
useBoxAtStart(lir, LCallDirectEval::ThisValue, thisValue);
useBoxAtStart(lir, LCallDirectEval::NewTarget, newTargetValue);
defineReturn(lir, ins);
assignSafepoint(lir, ins);
@@ -2129,9 +2141,11 @@ LIRGenerator::visitLambdaArrow(MLambdaArrow* ins)
{
MOZ_ASSERT(ins->scopeChain()->type() == MIRType_Object);
MOZ_ASSERT(ins->thisDef()->type() == MIRType_Value);
MOZ_ASSERT(ins->newTargetDef()->type() == MIRType_Value);
LLambdaArrow* lir = new(alloc()) LLambdaArrow(useRegister(ins->scopeChain()), temp());
LLambdaArrow* lir = new(alloc()) LLambdaArrow(useRegister(ins->scopeChain()));
useBox(lir, LLambdaArrow::ThisValue, ins->thisDef());
useBox(lir, LLambdaArrow::NewTargetValue, ins->newTargetDef());
define(lir, ins);
assignSafepoint(lir, ins);
}
+1
View File
@@ -297,6 +297,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitDebugger(MDebugger* ins);
void visitNurseryObject(MNurseryObject* ins);
void visitNewTarget(MNewTarget* ins);
void visitArrowNewTarget(MArrowNewTarget* ins);
};
} // namespace jit
+51 -12
View File
@@ -4025,19 +4025,21 @@ class MGetDynamicName
};
class MCallDirectEval
: public MAryInstruction<3>,
public Mix3Policy<ObjectPolicy<0>,
: public MAryInstruction<4>,
public Mix4Policy<ObjectPolicy<0>,
StringPolicy<1>,
BoxPolicy<2> >::Data
BoxPolicy<2>,
BoxPolicy<3> >::Data
{
protected:
MCallDirectEval(MDefinition* scopeChain, MDefinition* string, MDefinition* thisValue,
jsbytecode* pc)
MDefinition* newTargetValue, jsbytecode* pc)
: pc_(pc)
{
initOperand(0, scopeChain);
initOperand(1, string);
initOperand(2, thisValue);
initOperand(3, newTargetValue);
setResultType(MIRType_Value);
}
@@ -4046,9 +4048,9 @@ class MCallDirectEval
static MCallDirectEval*
New(TempAllocator& alloc, MDefinition* scopeChain, MDefinition* string, MDefinition* thisValue,
jsbytecode* pc)
MDefinition* newTargetValue, jsbytecode* pc)
{
return new(alloc) MCallDirectEval(scopeChain, string, thisValue, pc);
return new(alloc) MCallDirectEval(scopeChain, string, thisValue, newTargetValue, pc);
}
MDefinition* getScopeChain() const {
@@ -4060,6 +4062,9 @@ class MCallDirectEval
MDefinition* getThisValue() const {
return getOperand(2);
}
MDefinition* getNewTargetValue() const {
return getOperand(3);
}
jsbytecode* pc() const {
return pc_;
@@ -6656,6 +6661,36 @@ class MLoadArrowThis
}
};
// Load an arrow function's |new.target| value.
class MArrowNewTarget
: public MUnaryInstruction,
public SingleObjectPolicy::Data
{
explicit MArrowNewTarget(MDefinition* callee)
: MUnaryInstruction(callee)
{
setResultType(MIRType_Value);
setMovable();
}
public:
INSTRUCTION_HEADER(ArrowNewTarget)
static MArrowNewTarget* New(TempAllocator& alloc, MDefinition* callee) {
return new(alloc) MArrowNewTarget(callee);
}
MDefinition* callee() const {
return getOperand(0);
}
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override {
// An arrow function's lexical |this| value is immutable.
return AliasSet::None();
}
};
class MPhi final
: public MDefinition,
public InlineListNode<MPhi>,
@@ -7488,14 +7523,14 @@ class MLambda
};
class MLambdaArrow
: public MBinaryInstruction,
public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
: public MTernaryInstruction,
public Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, BoxPolicy<2> >::Data
{
const LambdaFunctionInfo info_;
MLambdaArrow(CompilerConstraintList* constraints, MDefinition* scopeChain,
MDefinition* this_, JSFunction* fun)
: MBinaryInstruction(scopeChain, this_), info_(fun)
MDefinition* this_, MDefinition* newTarget_, JSFunction* fun)
: MTernaryInstruction(scopeChain, this_, newTarget_), info_(fun)
{
setResultType(MIRType_Object);
MOZ_ASSERT(!ObjectGroup::useSingletonForClone(fun));
@@ -7507,9 +7542,10 @@ class MLambdaArrow
INSTRUCTION_HEADER(LambdaArrow)
static MLambdaArrow* New(TempAllocator& alloc, CompilerConstraintList* constraints,
MDefinition* scopeChain, MDefinition* this_, JSFunction* fun)
MDefinition* scopeChain, MDefinition* this_, MDefinition* newTarget_,
JSFunction* fun)
{
return new(alloc) MLambdaArrow(constraints, scopeChain, this_, fun);
return new(alloc) MLambdaArrow(constraints, scopeChain, this_, newTarget_, fun);
}
MDefinition* scopeChain() const {
return getOperand(0);
@@ -7517,6 +7553,9 @@ class MLambdaArrow
MDefinition* thisDef() const {
return getOperand(1);
}
MDefinition* newTargetDef() const {
return getOperand(2);
}
const LambdaFunctionInfo& info() const {
return info_;
}
+2 -1
View File
@@ -268,7 +268,8 @@ namespace jit {
_(LexicalCheck) \
_(ThrowUninitializedLexical) \
_(Debugger) \
_(NewTarget)
_(NewTarget) \
_(ArrowNewTarget)
// Forward declarations of MIR types.
#define FORWARD_DECLARE(op) class M##op;
+4 -2
View File
@@ -35,6 +35,7 @@ RematerializedFrame::RematerializedFrame(JSContext* cx, uint8_t* top, unsigned n
InlineFrameIterator& iter, MaybeReadFallback& fallback)
: prevUpToDate_(false),
isDebuggee_(iter.script()->isDebuggee()),
isConstructing_(iter.isConstructing()),
top_(top),
pc_(iter.pc()),
frameNo_(iter.frameNo()),
@@ -52,7 +53,7 @@ RematerializedFrame::New(JSContext* cx, uint8_t* top, InlineFrameIterator& iter,
MaybeReadFallback& fallback)
{
unsigned numFormals = iter.isFunctionFrame() ? iter.calleeTemplate()->nargs() : 0;
unsigned argSlots = Max(numFormals, iter.numActualArgs());
unsigned argSlots = Max(numFormals, iter.numActualArgs()) + iter.isConstructing();
size_t numBytes = sizeof(RematerializedFrame) +
(argSlots + iter.script()->nfixed()) * sizeof(Value) -
sizeof(Value); // 1 Value included in sizeof(RematerializedFrame)
@@ -151,7 +152,8 @@ RematerializedFrame::mark(JSTracer* trc)
TraceRoot(trc, &scopeChain_, "remat ion frame scope chain");
TraceRoot(trc, &returnValue_, "remat ion frame return value");
TraceRoot(trc, &thisValue_, "remat ion frame this");
TraceRootRange(trc, numActualArgs_ + script_->nfixed(), slots_, "remat ion frame stack");
TraceRootRange(trc, numActualArgs_ + isConstructing_ + script_->nfixed(),
slots_, "remat ion frame stack");
}
void
+17 -1
View File
@@ -32,6 +32,9 @@ class RematerializedFrame
// Has a call object been pushed?
bool hasCallObj_;
// Is this frame constructing?
bool isConstructing_;
// The fp of the top frame associated with this possibly inlined frame.
uint8_t* top_;
@@ -160,6 +163,10 @@ class RematerializedFrame
return thisValue_;
}
bool isConstructing() const {
return isConstructing_;
}
unsigned numFormalArgs() const {
return maybeFun() ? fun()->nargs() : 0;
}
@@ -171,7 +178,7 @@ class RematerializedFrame
return slots_;
}
Value* locals() {
return slots_ + numActualArgs_;
return slots_ + numActualArgs_ + isConstructing_;
}
Value& unaliasedLocal(unsigned i) {
@@ -191,6 +198,15 @@ class RematerializedFrame
return argv()[i];
}
Value newTarget() {
MOZ_ASSERT(isFunctionFrame());
if (callee()->isArrow())
return callee()->getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT);
if (isConstructing())
return argv()[numActualArgs()];
return UndefinedValue();
}
Value returnValue() const {
return returnValue_;
}
+1
View File
@@ -1155,6 +1155,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins)
_(Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>) \
_(Mix3Policy<StringPolicy<0>, ObjectPolicy<1>, StringPolicy<2> >) \
_(Mix3Policy<StringPolicy<0>, StringPolicy<1>, StringPolicy<2> >) \
_(Mix4Policy<ObjectPolicy<0>, StringPolicy<1>, BoxPolicy<2>, BoxPolicy<3>>) \
_(Mix4Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>, IntPolicy<3>>) \
_(Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \
_(MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >) \
+8
View File
@@ -451,6 +451,7 @@ template <> struct MatchContext<ExclusiveContext*> {
#define FOR_EACH_ARGS_4(Macro, Sep, Last) FOR_EACH_ARGS_3(Macro, Sep, Sep) Macro(4) Last(4)
#define FOR_EACH_ARGS_5(Macro, Sep, Last) FOR_EACH_ARGS_4(Macro, Sep, Sep) Macro(5) Last(5)
#define FOR_EACH_ARGS_6(Macro, Sep, Last) FOR_EACH_ARGS_5(Macro, Sep, Sep) Macro(6) Last(6)
#define FOR_EACH_ARGS_7(Macro, Sep, Last) FOR_EACH_ARGS_6(Macro, Sep, Sep) Macro(7) Last(7)
#define COMPUTE_INDEX(NbArg) NbArg
#define COMPUTE_OUTPARAM_RESULT(NbArg) OutParamToDataType<A ## NbArg>::result
@@ -590,8 +591,15 @@ template <class R, class Context, class A1, class A2, class A3, class A4, class
FUNCTION_INFO_STRUCT_BODY(FOR_EACH_ARGS_6)
};
template <class R, class Context, class A1, class A2, class A3, class A4, class A5, class A6, class A7>
struct FunctionInfo<R (*)(Context, A1, A2, A3, A4, A5, A6, A7)> : public VMFunction {
typedef R (*pf)(Context, A1, A2, A3, A4, A5, A6, A7);
FUNCTION_INFO_STRUCT_BODY(FOR_EACH_ARGS_7)
};
#undef FUNCTION_INFO_STRUCT_BODY
#undef FOR_EACH_ARGS_7
#undef FOR_EACH_ARGS_6
#undef FOR_EACH_ARGS_5
#undef FOR_EACH_ARGS_4
+12 -29
View File
@@ -274,9 +274,10 @@ JS_GetEmptyString(JSRuntime* rt)
namespace js {
JS_PUBLIC_API(bool)
GetPerformanceStats(JSRuntime* rt,
PerformanceStatsVector& stats,
PerformanceStats& processStats)
IterPerformanceStats(JSContext* cx,
PerformanceStatsWalker walker,
PerformanceData* processStats,
void* closure)
{
// As a PerformanceGroup is typically associated to several
// compartments, use a HashSet to make sure that we only report
@@ -289,13 +290,16 @@ GetPerformanceStats(JSRuntime* rt,
return false;
}
JSRuntime* rt = JS_GetRuntime(cx);
for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
JSCompartment* compartment = c.get();
if (!compartment->performanceMonitoring.isLinked()) {
// Don't report compartments that do not even have a PerformanceGroup.
continue;
}
PerformanceGroup* group = compartment->performanceMonitoring.getGroup();
js::AutoCompartment autoCompartment(cx, compartment);
PerformanceGroup* group = compartment->performanceMonitoring.getGroup(cx);
if (group->data.ticks == 0) {
// Don't report compartments that have never been used.
@@ -308,38 +312,16 @@ GetPerformanceStats(JSRuntime* rt,
continue;
}
if (!stats.growBy(1)) {
// Memory issue
if (!(*walker)(cx, group->data, closure)) {
// Issue in callback
return false;
}
PerformanceStats* stat = &stats.back();
stat->isSystem = compartment->isSystem();
if (compartment->addonId)
stat->addonId = compartment->addonId;
if (compartment->addonId || !compartment->isSystem()) {
if (rt->compartmentNameCallback) {
(*rt->compartmentNameCallback)(rt, compartment,
stat->name,
mozilla::ArrayLength(stat->name));
} else {
strcpy(stat->name, "<unknown>");
}
} else {
strcpy(stat->name, "<platform>");
}
stat->performance = group->data;
if (!set.add(ptr, group)) {
// Memory issue
return false;
}
}
strcpy(processStats.name, "<process>");
processStats.addonId = nullptr;
processStats.isSystem = true;
processStats.performance = rt->stopwatch.performance;
*processStats = rt->stopwatch.performance;
return true;
}
@@ -925,6 +907,7 @@ JSAutoCompartment::JSAutoCompartment(JSContext* cx, JSScript* target)
cx_->enterCompartment(target->compartment());
}
JSAutoCompartment::~JSAutoCompartment()
{
cx_->leaveCompartment(oldCompartment_);
+50 -54
View File
@@ -712,6 +712,18 @@ typedef void
(* JSCompartmentNameCallback)(JSRuntime* rt, JSCompartment* compartment,
char* buf, size_t bufsize);
/**
* Callback used to ask the embedding to determine in which
* Performance Group the current execution belongs. Typically, this is
* used to regroup JSCompartments from several iframes from the same
* page or from several compartments of the same addon into a single
* Performance Group.
*
* Returns an opaque key.
*/
typedef void*
(* JSCurrentPerfGroupCallback)(JSContext*);
/************************************************************************/
static MOZ_ALWAYS_INLINE jsval
@@ -5413,9 +5425,10 @@ struct PerformanceGroup {
stopwatch_ = nullptr;
}
PerformanceGroup()
explicit PerformanceGroup(void* key)
: stopwatch_(nullptr)
, iteration_(0)
, key_(key)
, refCount_(0)
{ }
~PerformanceGroup()
@@ -5434,6 +5447,9 @@ struct PerformanceGroup {
// may safely overflow.
uint64_t iteration_;
// The hash key for this PerformanceGroup.
void* const key_;
// Increment/decrement the refcounter, return the updated value.
uint64_t incRefCount() {
MOZ_ASSERT(refCount_ + 1 > 0);
@@ -5445,7 +5461,7 @@ struct PerformanceGroup {
}
friend struct PerformanceGroupHolder;
private:
private:
// A reference counter. Maintained by PerformanceGroupHolder.
uint64_t refCount_;
};
@@ -5458,7 +5474,7 @@ struct PerformanceGroupHolder {
// Get the group.
// On first call, this causes a single Hashtable lookup.
// Successive calls do not require further lookups.
js::PerformanceGroup* getGroup();
js::PerformanceGroup* getGroup(JSContext*);
// `true` if the this holder is currently associated to a
// PerformanceGroup, `false` otherwise. Use this method to avoid
@@ -5473,9 +5489,8 @@ struct PerformanceGroupHolder {
// (new values of `isSystem()`, `principals()` or `addonId`).
void unlink();
PerformanceGroupHolder(JSRuntime* runtime, JSCompartment* compartment)
explicit PerformanceGroupHolder(JSRuntime* runtime)
: runtime_(runtime)
, compartment_(compartment)
, group_(nullptr)
{ }
~PerformanceGroupHolder();
@@ -5483,10 +5498,9 @@ private:
// Return the key representing this PerformanceGroup in
// Runtime::Stopwatch.
// Do not deallocate the key.
void* getHashKey();
void* getHashKey(JSContext* cx);
JSRuntime* runtime_;
JSCompartment* compartment_;
JSRuntime *runtime_;
// The PerformanceGroup held by this object.
// Initially set to `nullptr` until the first cal to `getGroup`.
@@ -5505,11 +5519,19 @@ ResetStopwatches(JSRuntime*);
/**
* Turn on/off stopwatch-based CPU monitoring.
*
* `SetStopwatchActive` may return `false` if monitoring could not be
* activated, which may happen if we are out of memory.
* `SetStopwatchIsMonitoringCPOW` or `SetStopwatchIsMonitoringJank`
* may return `false` if monitoring could not be activated, which may
* happen if we are out of memory.
*/
extern JS_PUBLIC_API(bool)
SetStopwatchActive(JSRuntime*, bool);
SetStopwatchIsMonitoringCPOW(JSRuntime*, bool);
extern JS_PUBLIC_API(bool)
GetStopwatchIsMonitoringCPOW(JSRuntime*);
extern JS_PUBLIC_API(bool)
SetStopwatchIsMonitoringJank(JSRuntime*, bool);
extern JS_PUBLIC_API(bool)
GetStopwatchIsMonitoringJank(JSRuntime*);
extern JS_PUBLIC_API(bool)
IsStopwatchActive(JSRuntime*);
@@ -5519,56 +5541,30 @@ IsStopwatchActive(JSRuntime*);
extern JS_PUBLIC_API(PerformanceData*)
GetPerformanceData(JSRuntime*);
typedef bool
(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, void* closure);
/**
* Performance statistics for a performance group (a process, an
* add-on, a webpage, the built-ins or a special compartment).
*/
struct PerformanceStats {
/**
* If this group represents an add-on, the ID of the addon,
* otherwise `nullptr`.
*/
JSAddonId* addonId;
/**
* If this group represents a webpage, the process itself or a special
* compartment, a human-readable name. Unspecified for add-ons.
*/
char name[1024];
/**
* `true` if the group represents in system compartments, `false`
* otherwise. A group may never contain both system and non-system
* compartments.
*/
bool isSystem;
/**
* Performance information.
*/
js::PerformanceData performance;
PerformanceStats()
: addonId(nullptr)
, isSystem(false)
{
name[0] = '\0';
}
};
typedef js::Vector<PerformanceStats, 0, js::SystemAllocPolicy> PerformanceStatsVector;
/**
* Extract the performance statistics.
*
* After a successful call, `stats` holds the `PerformanceStats` for
* all performance groups, and `global` holds a `PerformanceStats`
* representing the entire process.
* Note that before calling `walker`, we enter the corresponding context.
*/
extern JS_PUBLIC_API(bool)
GetPerformanceStats(JSRuntime* rt, js::PerformanceStatsVector& stats, js::PerformanceStats& global);
IterPerformanceStats(JSContext* cx, PerformanceStatsWalker* walker, js::PerformanceData* process, void* closure);
} /* namespace js */
/**
* Callback used to ask the embedding to determine in which
* Performance Group a compartment belongs. Typically, this is used to
* regroup JSCompartments from several iframes from the same page or
* from several compartments of the same addon into a single
* Performance Group.
*
* Returns an opaque key.
*/
extern JS_PUBLIC_API(void)
JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb);
#endif /* jsapi_h */
+1 -1
View File
@@ -53,7 +53,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
#endif
global_(nullptr),
enterCompartmentDepth(0),
performanceMonitoring(runtime_, this),
performanceMonitoring(runtime_),
data(nullptr),
objectMetadataCallback(nullptr),
lastAnimationTime(0),
+4
View File
@@ -635,6 +635,7 @@ class FunctionExtended : public JSFunction
/* Arrow functions store their lexical |this| in the first extended slot. */
static const unsigned ARROW_THIS_SLOT = 0;
static const unsigned ARROW_NEWTARGET_SLOT = 1;
static const unsigned METHOD_HOMEOBJECT_SLOT = 0;
@@ -645,6 +646,9 @@ class FunctionExtended : public JSFunction
static inline size_t offsetOfArrowThisSlot() {
return offsetOfExtendedSlot(ARROW_THIS_SLOT);
}
static inline size_t offsetOfArrowNewTargetSlot() {
return offsetOfExtendedSlot(ARROW_NEWTARGET_SLOT);
}
private:
friend class JSFunction;
+7 -20
View File
@@ -3725,20 +3725,9 @@ CheckCompartment(CompartmentCheckTracer* trc, JSCompartment* thingCompartment,
InCrossCompartmentMap((JSObject*)trc->src, thing, kind)));
}
static JSCompartment*
CompartmentOfCell(Cell* thing, JSGCTraceKind kind)
{
if (kind == JSTRACE_OBJECT)
return static_cast<JSObject*>(thing)->compartment();
else if (kind == JSTRACE_SHAPE)
return static_cast<Shape*>(thing)->compartment();
else if (kind == JSTRACE_BASE_SHAPE)
return static_cast<BaseShape*>(thing)->compartment();
else if (kind == JSTRACE_SCRIPT)
return static_cast<JSScript*>(thing)->compartment();
else
return nullptr;
}
struct MaybeCompartmentFunctor {
template <typename T> JSCompartment* operator()(T* t) { return t->maybeCompartment(); }
};
static void
CheckCompartmentCallback(JS::CallbackTracer* trcArg, void** thingp, JSGCTraceKind kind)
@@ -3746,7 +3735,7 @@ CheckCompartmentCallback(JS::CallbackTracer* trcArg, void** thingp, JSGCTraceKin
CompartmentCheckTracer* trc = static_cast<CompartmentCheckTracer*>(trcArg);
TenuredCell* thing = TenuredCell::fromPointer(*thingp);
JSCompartment* comp = CompartmentOfCell(thing, kind);
JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), thing, kind);
if (comp && trc->compartment)
CheckCompartment(trc, comp, thing, kind);
else
@@ -3766,7 +3755,7 @@ GCRuntime::checkForCompartmentMismatches()
for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
trc.src = i.getCell();
trc.srcKind = MapAllocToTraceKind(thingKind);
trc.compartment = CompartmentOfCell(trc.src, trc.srcKind);
trc.compartment = CallTyped(MaybeCompartmentFunctor(), trc.src, trc.srcKind);
JS_TraceChildren(&trc, trc.src, trc.srcKind);
}
}
@@ -7172,9 +7161,7 @@ JS::IsIncrementalBarrierNeeded(JSContext* cx)
}
struct IncrementalReferenceBarrierFunctor {
template <typename T> void operator()(gc::Cell* cell) {
T::writeBarrierPre(static_cast<T*>(cell));
}
template <typename T> void operator()(T* t) { T::writeBarrierPre(t); }
};
JS_PUBLIC_API(void)
@@ -7183,7 +7170,7 @@ JS::IncrementalReferenceBarrier(GCCellPtr thing)
if (!thing)
return;
CallTyped(IncrementalReferenceBarrierFunctor(), thing.kind(), thing.asCell());
CallTyped(IncrementalReferenceBarrierFunctor(), thing.asCell(), thing.kind());
}
JS_PUBLIC_API(void)
+28
View File
@@ -224,6 +224,34 @@ CallTyped(F f, JSGCTraceKind traceKind, Args&&... args)
}
#undef DEPENDENT_TEMPLATE_HINT
template <typename F, typename... Args>
auto
CallTyped(F f, void* thing, JSGCTraceKind traceKind, Args&&... args)
-> decltype(f(reinterpret_cast<JSObject*>(0), mozilla::Forward<Args>(args)...))
{
switch (traceKind) {
case JSTRACE_OBJECT:
return f(static_cast<JSObject*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_SCRIPT:
return f(static_cast<JSScript*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_STRING:
return f(static_cast<JSString*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_SYMBOL:
return f(static_cast<JS::Symbol*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_BASE_SHAPE:
return f(static_cast<BaseShape*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_JITCODE:
return f(static_cast<jit::JitCode*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_LAZY_SCRIPT:
return f(static_cast<LazyScript*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_SHAPE:
return f(static_cast<Shape*>(thing), mozilla::Forward<Args>(args)...);
case JSTRACE_OBJECT_GROUP:
return f(static_cast<ObjectGroup*>(thing), mozilla::Forward<Args>(args)...);
default:
MOZ_CRASH("Invalid trace kind in CallTyped.");
}
}
/* Capacity for slotsToThingKind */
const size_t SLOTS_TO_THING_KIND_LIMIT = 17;
+29
View File
@@ -4071,15 +4071,44 @@ js::StringConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
str_fromCharCode_few_args(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE);
char16_t chars[JSFatInlineString::MAX_LENGTH_TWO_BYTE];
for (unsigned i = 0; i < args.length(); i++) {
uint16_t code;
if (!ToUint16(cx, args[i], &code))
return false;
chars[i] = char16_t(code);
}
JSString* str = NewStringCopyN<CanGC>(cx, chars, args.length());
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
// Optimize the single-char case.
if (args.length() == 1)
return str_fromCharCode_one_arg(cx, args[0], args.rval());
// Optimize the case where the result will definitely fit in an inline
// string (thin or fat) and so we don't need to malloc the chars. (We could
// cover some cases where args.length() goes up to
// JSFatInlineString::MAX_LENGTH_LATIN1 if we also checked if the chars are
// all Latin1, but it doesn't seem worth the effort.)
if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE)
return str_fromCharCode_few_args(cx, args);
char16_t* chars = cx->pod_malloc<char16_t>(args.length() + 1);
if (!chars)
return false;
+20
View File
@@ -153,4 +153,24 @@ new TestCase( SECTION, "String.fromCharCode(0x007D)", "}", String.fromCh
new TestCase( SECTION, "String.fromCharCode(0x007E)", "~", String.fromCharCode(0x007E) );
// new TestCase( SECTION, "String.fromCharCode(0x0020, 0x007F)", "", String.fromCharCode(0x0040, 0x007F) );
new TestCase( SECTION, "String.fromCharCode(<2 args>)", "ab", String.fromCharCode(97,98));
new TestCase( SECTION, "String.fromCharCode(<3 args>)", "abc", String.fromCharCode(97,98,99));
new TestCase( SECTION, "String.fromCharCode(<4 args>)", "abcd", String.fromCharCode(97,98,99,100));
new TestCase( SECTION, "String.fromCharCode(<5 args>)", "abcde", String.fromCharCode(97,98,99,100,101));
new TestCase( SECTION, "String.fromCharCode(<6 args>)", "abcdef", String.fromCharCode(97,98,99,100,101,102));
new TestCase( SECTION, "String.fromCharCode(<7 args>)", "abcdefg", String.fromCharCode(97,98,99,100,101,102,103));
new TestCase( SECTION, "String.fromCharCode(<8 args>)", "abcdefgh", String.fromCharCode(97,98,99,100,101,102,103,104));
new TestCase( SECTION, "String.fromCharCode(<9 args>)", "abcdefghi", String.fromCharCode(97,98,99,100,101,102,103,104,105));
new TestCase( SECTION, "String.fromCharCode(<10 args>)", "abcdefghij", String.fromCharCode(97,98,99,100,101,102,103,104,105,106));
new TestCase( SECTION, "String.fromCharCode(<11 args>)", "abcdefghijk", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107));
new TestCase( SECTION, "String.fromCharCode(<12 args>)", "abcdefghijkl", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108));
new TestCase( SECTION, "String.fromCharCode(<13 args>)", "abcdefghijklm", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109));
new TestCase( SECTION, "String.fromCharCode(<14 args>)", "abcdefghijklmn", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110));
new TestCase( SECTION, "String.fromCharCode(<15 args>)", "abcdefghijklmno", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111));
new TestCase( SECTION, "String.fromCharCode(<16 args>)", "abcdefghijklmnop", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112));
new TestCase( SECTION, "String.fromCharCode(<17 args>)", "abcdefghijklmnopq", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113));
new TestCase( SECTION, "String.fromCharCode(<18 args>)", "abcdefghijklmnopqr", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114));
new TestCase( SECTION, "String.fromCharCode(<19 args>)", "abcdefghijklmnopqrs", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115));
new TestCase( SECTION, "String.fromCharCode(<20 args>)", "abcdefghijklmnopqrst", String.fromCharCode(97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116));
test();
@@ -0,0 +1,24 @@
// new.target is valid in any arrow function not in a global context.
new Function('(() => new.target)()');
// It's also good inside eval, but not global eval
assertThrowsInstanceOf(() => eval('() => new.target'), SyntaxError);
function assertNewTarget(expected) {
assertEq((()=>new.target)(), expected);
assertEq(eval('()=>new.target')(), expected);
// Make sure that arrow functions can escape their original context and
// still get the right answer.
return (() => new.target);
}
const ITERATIONS = 550;
for (let i = 0; i < ITERATIONS; i++)
assertEq(assertNewTarget(undefined)(), undefined);
for (let i = 0; i < ITERATIONS; i++)
assertEq(new assertNewTarget(assertNewTarget)(), assertNewTarget);
if (typeof reportCompare === 'function')
reportCompare(0,0,"OK");
@@ -1,11 +0,0 @@
// new.target is valid inside Function() invocations
var func = new Function("new.target");
// new.target is invalid inside eval, even (for now!) eval inside a function.
assertThrowsInstanceOf(() => eval('new.target'), SyntaxError);
function evalInFunction() { eval('new.target'); }
assertThrowsInstanceOf(() => evalInFunction(), SyntaxError);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");
@@ -1,3 +1,6 @@
// new.target is valid inside Function() invocations
var func = new Function("new.target");
// Note that this will also test new.target in ion inlines. When the toplevel
// script is compiled, assertNewTarget will be inlined.
function assertNewTarget(expected, unused) { assertEq(new.target, expected); }
@@ -0,0 +1,34 @@
// Eval of new.target is invalid outside functions.
try {
eval('new.target');
assertEq(false, true);
} catch (e if e instanceof SyntaxError) { }
// new.target is invalid inside eval inside top-level arrow functions
assertThrowsInstanceOf(() => eval('new.target'), SyntaxError);
// new.target is invalid inside indirect eval.
let ieval = eval;
try {
(function () ieval('new.target'))();
assertEq(false, true);
} catch (e if e instanceof SyntaxError) { }
function assertNewTarget(expected) {
assertEq(eval('new.target'), expected);
assertEq((()=>eval('new.target'))(), expected);
// Also test nestings "by induction"
assertEq(eval('eval("new.target")'), expected);
assertEq(eval("eval('eval(`new.target`)')"), expected);
}
const ITERATIONS = 550;
for (let i = 0; i < ITERATIONS; i++)
assertNewTarget(undefined);
for (let i = 0; i < ITERATIONS; i++)
new assertNewTarget(assertNewTarget);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");
@@ -0,0 +1,11 @@
class foo {
constructor() { }
method() {
return (() => eval('super.toString'));
}
}
assertEq(new foo().method()(), Object.prototype.toString);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");
@@ -0,0 +1,13 @@
// It's invalid to eval super.prop inside a nested non-method, even if it
// appears inside a method definition
assertThrowsInstanceOf(() =>
({
method() {
(function () {
eval('super.toString');
})();
}
}).method(), SyntaxError);
if (typeof reportCompare === 'function')
reportCompare(0,0,"OK");
@@ -11,8 +11,8 @@ function testNewTarget() {
// invalid in top-level scripts
assertError("new.target", SyntaxError);
// invalid (for now!) in any arrow function
assertError("function foo() { (() => new.target) }", SyntaxError);
// valid in arrow functions inside functions
assertInFunctionExpr("()=>new.target", arrowExpr([], newTarget()));
assertError("(() => new.target))", SyntaxError);
// invalid (for now!) in generators
+1 -1
View File
@@ -6288,7 +6288,7 @@ EvaluateInEnv(JSContext* cx, Handle<Env*> env, HandleValue thisv, AbstractFrameP
script->setActiveEval();
ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG;
return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address());
return ExecuteKernel(cx, script, *env, thisv, NullValue(), type, frame, rval.address());
}
enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false };
+50 -44
View File
@@ -362,8 +362,8 @@ InvokeState::pushInterpreterFrame(JSContext* cx)
InterpreterFrame*
ExecuteState::pushInterpreterFrame(JSContext* cx)
{
return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, scopeChain_,
type_, evalInFrame_);
return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, newTargetValue_,
scopeChain_, type_, evalInFrame_);
}
namespace js {
@@ -381,8 +381,7 @@ struct AutoStopwatch final
//
// Previous owner is restored upon destruction.
explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: compartment_(nullptr)
, runtime_(nullptr)
: cx_(cx)
, iteration_(0)
, isActive_(false)
, isTop_(false)
@@ -391,16 +390,18 @@ struct AutoStopwatch final
, CPOWTimeStart_(0)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
runtime_ = cx->runtime();
if (!runtime_->stopwatch.isActive())
return;
compartment_ = cx->compartment();
MOZ_ASSERT(compartment_);
if (compartment_->scheduledForDestruction)
return;
iteration_ = runtime_->stopwatch.iteration;
PerformanceGroup* group = compartment_->performanceMonitoring.getGroup();
JSRuntime* runtime = JS_GetRuntime(cx_);
if (!runtime->stopwatch.isMonitoringJank())
return;
JSCompartment* compartment = cx_->compartment();
if (compartment->scheduledForDestruction)
return;
iteration_ = runtime->stopwatch.iteration;
PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx);
MOZ_ASSERT(group);
if (group->hasStopwatch(iteration_)) {
@@ -410,22 +411,22 @@ struct AutoStopwatch final
}
// Start the stopwatch.
if (!this->getTimes(&userTimeStart_, &systemTimeStart_))
if (!this->getTimes(runtime, &userTimeStart_, &systemTimeStart_))
return;
isActive_ = true;
CPOWTimeStart_ = runtime_->stopwatch.performance.totalCPOWTime;
CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
// We are now in charge of monitoring this group for the tick,
// until destruction of `this` or until we enter a nested event
// loop and `iteration_` is incremented.
group->acquireStopwatch(iteration_, this);
if (runtime_->stopwatch.isEmpty) {
if (runtime->stopwatch.isEmpty) {
// This is the topmost stopwatch on the stack.
// It will be in charge of updating the per-process
// performance data.
runtime_->stopwatch.isEmpty = false;
runtime_->stopwatch.performance.ticks++;
runtime->stopwatch.isEmpty = false;
runtime->stopwatch.performance.ticks++;
isTop_ = true;
}
}
@@ -435,32 +436,35 @@ struct AutoStopwatch final
return;
}
MOZ_ASSERT(!compartment_->scheduledForDestruction);
JSRuntime* runtime = JS_GetRuntime(cx_);
JSCompartment* compartment = cx_->compartment();
if (!runtime_->stopwatch.isActive()) {
MOZ_ASSERT(!compartment->scheduledForDestruction);
if (!runtime->stopwatch.isMonitoringJank()) {
// Monitoring has been stopped while we were
// executing the code. Drop everything.
return;
}
if (iteration_ != runtime_->stopwatch.iteration) {
if (iteration_ != runtime->stopwatch.iteration) {
// We have entered a nested event loop at some point.
// Any information we may have is obsolete.
return;
}
PerformanceGroup* group = compartment_->performanceMonitoring.getGroup();
PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx_);
MOZ_ASSERT(group);
// Compute time spent.
group->releaseStopwatch(iteration_, this);
uint64_t userTimeEnd, systemTimeEnd;
if (!this->getTimes(&userTimeEnd, &systemTimeEnd))
if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd))
return;
uint64_t userTimeDelta = userTimeEnd - userTimeStart_;
uint64_t systemTimeDelta = systemTimeEnd - systemTimeStart_;
uint64_t CPOWTimeDelta = runtime_->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
uint64_t CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
group->data.totalUserTime += userTimeDelta;
group->data.totalSystemTime += systemTimeDelta;
group->data.totalCPOWTime += CPOWTimeDelta;
@@ -472,10 +476,10 @@ struct AutoStopwatch final
if (isTop_) {
// This is the topmost stopwatch on the stack.
// Record the timing information.
runtime_->stopwatch.performance.totalUserTime = userTimeEnd;
runtime_->stopwatch.performance.totalSystemTime = systemTimeEnd;
updateDurations(totalTimeDelta, runtime_->stopwatch.performance.durations);
runtime_->stopwatch.isEmpty = true;
runtime->stopwatch.performance.totalUserTime = userTimeEnd;
runtime->stopwatch.performance.totalSystemTime = systemTimeEnd;
updateDurations(totalTimeDelta, runtime->stopwatch.performance.durations);
runtime->stopwatch.isEmpty = true;
}
}
@@ -499,7 +503,7 @@ struct AutoStopwatch final
// Get the OS-reported time spent in userland/systemland, in
// microseconds. On most platforms, this data is per-thread,
// but on some platforms we need to fall back to per-process.
bool getTimes(uint64_t* userTime, uint64_t* systemTime) const {
bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
MOZ_ASSERT(userTime);
MOZ_ASSERT(systemTime);
@@ -563,12 +567,12 @@ struct AutoStopwatch final
kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
// Convert 100 ns to 1 us, make sure that the result is monotonic
*systemTime = runtime_-> stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
*systemTime = runtime->stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
userTimeInt.LowPart = userFileTime.dwLowDateTime;
userTimeInt.HighPart = userFileTime.dwHighDateTime;
// Convert 100 ns to 1 us, make sure that the result is monotonic
*userTime = runtime_-> stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
*userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
@@ -576,13 +580,9 @@ struct AutoStopwatch final
}
private:
// The compartment with which this object was initialized.
// The context with which this object was initialized.
// Non-null.
JSCompartment* compartment_;
// The runtime with which this object was initialized.
// Non-null.
JSRuntime* runtime_;
JSContext* const cx_;
// An indication of the number of times we have entered the event
// loop. Used only for comparison.
@@ -844,7 +844,8 @@ js::InvokeSetter(JSContext* cx, const Value& thisv, Value fval, HandleValue v)
bool
js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg, const Value& thisv,
ExecuteType type, AbstractFramePtr evalInFrame, Value* result)
const Value& newTargetValue, ExecuteType type, AbstractFramePtr evalInFrame,
Value* result)
{
MOZ_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG);
MOZ_ASSERT_IF(type == EXECUTE_GLOBAL, !IsSyntacticScope(&scopeChainArg));
@@ -879,7 +880,7 @@ js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg, c
TypeScript::SetThis(cx, script, thisv);
probes::StartExecution(script);
ExecuteState state(cx, script, thisv, scopeChainArg, type, evalInFrame, result);
ExecuteState state(cx, script, thisv, newTargetValue, scopeChainArg, type, evalInFrame, result);
bool ok = RunScript(cx, state);
probes::StopExecution(script);
@@ -919,7 +920,7 @@ js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value*
return false;
Value thisv = ObjectValue(*thisObj);
return ExecuteKernel(cx, script, *scopeChain, thisv, EXECUTE_GLOBAL,
return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), EXECUTE_GLOBAL,
NullFramePtr() /* evalInFrame */, rval);
}
@@ -3465,12 +3466,14 @@ CASE(JSOP_LAMBDA_ARROW)
{
/* Load the specified function object literal. */
ReservedRooted<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc)));
ReservedRooted<Value> thisv(&rootValue0, REGS.sp[-1]);
JSObject* obj = LambdaArrow(cx, fun, REGS.fp()->scopeChain(), thisv);
ReservedRooted<Value> thisv(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> newTarget(&rootValue1, REGS.sp[-1]);
JSObject* obj = LambdaArrow(cx, fun, REGS.fp()->scopeChain(), thisv, newTarget);
if (!obj)
goto error;
MOZ_ASSERT(obj->getProto());
REGS.sp[-1].setObject(*obj);
REGS.sp[-2].setObject(*obj);
REGS.sp--;
}
END_CASE(JSOP_LAMBDA_ARROW)
@@ -4010,6 +4013,7 @@ END_CASE(JSOP_SUPERBASE)
CASE(JSOP_NEWTARGET)
PUSH_COPY(REGS.fp()->newTarget());
MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined());
END_CASE(JSOP_NEWTARGET)
DEFAULT()
@@ -4211,7 +4215,8 @@ js::Lambda(JSContext* cx, HandleFunction fun, HandleObject parent)
}
JSObject*
js::LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue thisv)
js::LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue thisv,
HandleValue newTargetv)
{
MOZ_ASSERT(fun->isArrow());
@@ -4222,6 +4227,7 @@ js::LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleVa
MOZ_ASSERT(clone->as<JSFunction>().isArrow());
clone->as<JSFunction>().setExtendedSlot(0, thisv);
clone->as<JSFunction>().setExtendedSlot(1, newTargetv);
MOZ_ASSERT(fun->global() == clone->global());
return clone;
+11 -4
View File
@@ -105,7 +105,8 @@ InvokeConstructor(JSContext* cx, Value fval, unsigned argc, const Value* argv,
*/
extern bool
ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChain, const Value& thisv,
ExecuteType type, AbstractFramePtr evalInFrame, Value* result);
const Value& newTargetVal, ExecuteType type, AbstractFramePtr evalInFrame,
Value* result);
/* Execute a script with the given scopeChain as global code. */
extern bool
@@ -163,23 +164,28 @@ class ExecuteState : public RunState
ExecuteType type_;
RootedValue thisv_;
RootedValue newTargetValue_;
RootedObject scopeChain_;
AbstractFramePtr evalInFrame_;
Value* result_;
public:
ExecuteState(JSContext* cx, JSScript* script, const Value& thisv, JSObject& scopeChain,
ExecuteType type, AbstractFramePtr evalInFrame, Value* result)
ExecuteState(JSContext* cx, JSScript* script, const Value& thisv, const Value& newTargetValue,
JSObject& scopeChain, ExecuteType type, AbstractFramePtr evalInFrame,
Value* result)
: RunState(cx, Execute, script),
type_(type),
thisv_(cx, thisv),
newTargetValue_(cx, newTargetValue),
scopeChain_(cx, &scopeChain),
evalInFrame_(evalInFrame),
result_(result)
{ }
Value* addressOfThisv() { return thisv_.address(); }
Value thisv() { return thisv_; }
Value newTarget() { return newTargetValue_; }
JSObject* scopeChain() const { return scopeChain_; }
ExecuteType type() const { return type_; }
@@ -353,7 +359,8 @@ JSObject*
Lambda(JSContext* cx, HandleFunction fun, HandleObject parent);
JSObject*
LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue thisv);
LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue thisv,
HandleValue newTargetv);
bool
GetElement(JSContext* cx, MutableHandleValue lref, HandleValue rref, MutableHandleValue res);
+1 -1
View File
@@ -1340,7 +1340,7 @@
* Operands: uint32_t funcIndex
* Stack: this => obj
*/ \
macro(JSOP_LAMBDA_ARROW, 131, "lambda_arrow", NULL, 5, 1, 1, JOF_OBJECT) \
macro(JSOP_LAMBDA_ARROW, 131, "lambda_arrow", NULL, 5, 2, 1, JOF_OBJECT) \
\
/*
* Pushes current callee onto the stack.
+31 -18
View File
@@ -860,17 +860,25 @@ js::ResetStopwatches(JSRuntime* rt)
}
bool
js::SetStopwatchActive(JSRuntime* rt, bool isActive)
js::SetStopwatchIsMonitoringJank(JSRuntime* rt, bool value)
{
MOZ_ASSERT(rt);
return rt->stopwatch.setIsActive(isActive);
return rt->stopwatch.setIsMonitoringJank(value);
}
bool
js::GetStopwatchIsMonitoringJank(JSRuntime* rt)
{
return rt->stopwatch.isMonitoringJank();
}
bool
js::IsStopwatchActive(JSRuntime* rt)
js::SetStopwatchIsMonitoringCPOW(JSRuntime* rt, bool value)
{
MOZ_ASSERT(rt);
return rt->stopwatch.isActive();
return rt->stopwatch.setIsMonitoringCPOW(value);
}
bool
js::GetStopwatchIsMonitoringCPOW(JSRuntime* rt)
{
return rt->stopwatch.isMonitoringCPOW();
}
js::PerformanceGroupHolder::~PerformanceGroupHolder()
@@ -879,15 +887,14 @@ js::PerformanceGroupHolder::~PerformanceGroupHolder()
}
void*
js::PerformanceGroupHolder::getHashKey()
js::PerformanceGroupHolder::getHashKey(JSContext* cx)
{
return compartment_->isSystem() ?
(void*)compartment_->addonId :
(void*)JS_GetCompartmentPrincipals(compartment_);
// This key may be `nullptr` if we have `isSystem() == true`
// and `compartment_->addonId`. This is absolutely correct,
// and this represents the `PerformanceGroup` used to track
// the performance of the the platform compartments.
if (runtime_->stopwatch.currentPerfGroupCallback) {
return (*runtime_->stopwatch.currentPerfGroupCallback)(cx);
}
// As a fallback, put everything in the same PerformanceGroup.
return nullptr;
}
void
@@ -908,26 +915,26 @@ js::PerformanceGroupHolder::unlink()
JSRuntime::Stopwatch::Groups::Ptr ptr =
runtime_->stopwatch.groups_.lookup(getHashKey());
runtime_->stopwatch.groups_.lookup(group->key_);
MOZ_ASSERT(ptr);
runtime_->stopwatch.groups_.remove(ptr);
js_delete(group);
}
PerformanceGroup*
js::PerformanceGroupHolder::getGroup()
js::PerformanceGroupHolder::getGroup(JSContext* cx)
{
if (group_)
return group_;
void* key = getHashKey();
void* key = getHashKey(cx);
JSRuntime::Stopwatch::Groups::AddPtr ptr =
runtime_->stopwatch.groups_.lookupForAdd(key);
if (ptr) {
group_ = ptr->value();
MOZ_ASSERT(group_);
} else {
group_ = runtime_->new_<PerformanceGroup>();
group_ = runtime_->new_<PerformanceGroup>(key);
runtime_->stopwatch.groups_.add(ptr, key, group_);
}
@@ -941,3 +948,9 @@ js::GetPerformanceData(JSRuntime* rt)
{
return &rt->stopwatch.performance;
}
void
JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb)
{
rt->stopwatch.currentPerfGroupCallback = cb;
}
+35 -12
View File
@@ -1472,10 +1472,24 @@ struct JSRuntime : public JS::shadow::Runtime,
*/
js::PerformanceData performance;
/**
* Callback used to ask the embedding to determine in which
* Performance Group the current execution belongs. Typically, this is
* used to regroup JSCompartments from several iframes from the same
* page or from several compartments of the same addon into a single
* Performance Group.
*
* May be `nullptr`, in which case we put all the JSCompartments
* in the same PerformanceGroup.
*/
JSCurrentPerfGroupCallback currentPerfGroupCallback;
Stopwatch()
: iteration(0)
, isEmpty(true)
, isActive_(false)
, currentPerfGroupCallback(nullptr)
, isMonitoringJank_(false)
, isMonitoringCPOW_(false)
{ }
/**
@@ -1489,9 +1503,8 @@ struct JSRuntime : public JS::shadow::Runtime,
++iteration;
isEmpty = true;
}
/**
* Activate/deactivate stopwatch measurement.
* Activate/deactivate stopwatch measurement of jank.
*
* Noop if `value` is `true` and the stopwatch is already active,
* or if `value` is `false` and the stopwatch is already inactive.
@@ -1501,8 +1514,8 @@ struct JSRuntime : public JS::shadow::Runtime,
*
* May return `false` if the underlying hashtable cannot be allocated.
*/
bool setIsActive(bool value) {
if (isActive_ != value)
bool setIsMonitoringJank(bool value) {
if (isMonitoringJank_ != value)
reset();
if (value && !groups_.initialized()) {
@@ -1510,15 +1523,24 @@ struct JSRuntime : public JS::shadow::Runtime,
return false;
}
isActive_ = value;
isMonitoringJank_ = value;
return true;
}
bool isMonitoringJank() const {
return isMonitoringJank_;
}
/**
* Activate/deactivate stopwatch measurement of CPOW.
*/
bool setIsMonitoringCPOW(bool value) {
isMonitoringCPOW_ = value;
return true;
}
/**
* `true` if the stopwatch is currently monitoring, `false` otherwise.
*/
bool isActive() const {
return isActive_;
bool isMonitoringCPOW() const {
return isMonitoringCPOW_;
}
// Some systems have non-monotonic clocks. While we cannot
@@ -1569,7 +1591,8 @@ struct JSRuntime : public JS::shadow::Runtime,
/**
* `true` if stopwatch monitoring is active, `false` otherwise.
*/
bool isActive_;
bool isMonitoringJank_;
bool isMonitoringCPOW_;
};
Stopwatch stopwatch;
};
+10
View File
@@ -816,6 +816,16 @@ AbstractFramePtr::thisValue() const
return asRematerializedFrame()->thisValue();
}
inline Value
AbstractFramePtr::newTarget() const
{
if (isInterpreterFrame())
return asInterpreterFrame()->newTarget();
if (isBaselineFrame())
return asBaselineFrame()->newTarget();
return asRematerializedFrame()->newTarget();
}
inline bool
AbstractFramePtr::freshenBlock(JSContext *cx) const
{
+38 -12
View File
@@ -33,7 +33,8 @@ using mozilla::PodCopy;
void
InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr evalInFramePrev,
const Value& thisv, HandleObject scopeChain, ExecuteType type)
const Value& thisv, const Value& newTargetValue, HandleObject scopeChain,
ExecuteType type)
{
/*
* See encoding of ExecuteType. When GLOBAL isn't set, we are executing a
@@ -43,11 +44,17 @@ InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractF
flags_ = type | HAS_SCOPECHAIN;
JSObject* callee = nullptr;
// newTarget = NullValue is an initial sentinel for "please fill me in from the stack".
// It should never be passed from Ion code.
RootedValue newTarget(cx, newTargetValue);
if (!(flags_ & (GLOBAL))) {
if (evalInFramePrev) {
MOZ_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame());
if (evalInFramePrev.isFunctionFrame()) {
callee = evalInFramePrev.callee();
if (newTarget.isNull())
newTarget = evalInFramePrev.newTarget();
flags_ |= FUNCTION;
} else {
flags_ |= GLOBAL;
@@ -57,6 +64,8 @@ InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractF
MOZ_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame());
MOZ_ASSERT(!iter.isAsmJS());
if (iter.isFunctionFrame()) {
if (newTarget.isNull())
newTarget = iter.newTarget();
callee = iter.callee(cx);
flags_ |= FUNCTION;
} else {
@@ -65,21 +74,22 @@ InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractF
}
}
Value* dstvp = (Value*)this - 2;
dstvp[1] = thisv;
Value* dstvp = (Value*)this - 3;
dstvp[2] = thisv;
if (isFunctionFrame()) {
dstvp[0] = ObjectValue(*callee);
dstvp[1] = ObjectValue(*callee);
exec.fun = &callee->as<JSFunction>();
u.evalScript = script;
} else {
MOZ_ASSERT(isGlobalFrame());
dstvp[0] = NullValue();
dstvp[1] = NullValue();
exec.script = script;
#ifdef DEBUG
u.evalScript = (JSScript*)0xbad;
#endif
}
dstvp[0] = newTarget;
scopeChain_ = scopeChain.get();
prev_ = nullptr;
@@ -399,8 +409,8 @@ InterpreterFrame::markValues(JSTracer* trc, Value* sp, jsbytecode* pc)
unsigned argc = Max(numActualArgs(), numFormalArgs());
TraceRootRange(trc, argc + 2 + isConstructing(), argv_ - 2, "fp argv");
} else {
// Mark callee and |this|
TraceRootRange(trc, 2, ((Value*)this) - 2, "stack callee and this");
// Mark callee, |this|, and newTarget
TraceRootRange(trc, 3, ((Value*)this) - 3, "stack callee, this, newTarget");
}
}
@@ -458,19 +468,19 @@ InterpreterStack::pushInvokeFrame(JSContext* cx, const CallArgs& args, InitialFr
InterpreterFrame*
InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Value& thisv,
HandleObject scopeChain, ExecuteType type,
AbstractFramePtr evalInFrame)
const Value& newTargetValue, HandleObject scopeChain,
ExecuteType type, AbstractFramePtr evalInFrame)
{
LifoAlloc::Mark mark = allocator_.mark();
unsigned nvars = 2 /* callee, this */ + script->nslots();
unsigned nvars = 3 /* callee, this, newTarget */ + script->nslots();
uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
if (!buffer)
return nullptr;
InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 2 * sizeof(Value));
InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 3 * sizeof(Value));
fp->mark_ = mark;
fp->initExecuteFrame(cx, script, evalInFrame, thisv, scopeChain, type);
fp->initExecuteFrame(cx, script, evalInFrame, thisv, newTargetValue, scopeChain, type);
fp->initLocals();
return fp;
@@ -1252,6 +1262,22 @@ FrameIter::thisv(JSContext* cx) const
MOZ_CRASH("Unexpected state");
}
Value
FrameIter::newTarget() const
{
switch (data_.state_) {
case DONE:
case ASMJS:
break;
case INTERP:
return interpFrame()->newTarget();
case JIT:
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
return data_.jitFrames_.baselineFrame()->newTarget();
}
MOZ_CRASH("Unexpected state");
}
Value
FrameIter::returnValue() const
{
+15 -5
View File
@@ -204,6 +204,8 @@ class AbstractFramePtr
inline Value calleev() const;
inline Value& thisValue() const;
inline Value newTarget() const;
inline bool isNonEvalFunctionFrame() const;
inline bool isNonStrictDirectEvalFrame() const;
inline bool isStrictEvalFrame() const;
@@ -400,7 +402,8 @@ class InterpreterFrame
/* Used for global and eval frames. */
void initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr prev,
const Value& thisv, HandleObject scopeChain, ExecuteType type);
const Value& thisv, const Value& newTargetValue,
HandleObject scopeChain, ExecuteType type);
public:
/*
@@ -743,8 +746,13 @@ class InterpreterFrame
* frame.
*/
Value newTarget() const {
// new.target in eval() NYI.
MOZ_ASSERT(isNonEvalFunctionFrame());
MOZ_ASSERT(isFunctionFrame());
if (isEvalFrame())
return ((Value*)this)[-3];
if (callee().isArrow())
return callee().getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT);
if (isConstructing()) {
unsigned pushedArgs = Max(numFormalArgs(), numActualArgs());
return argv()[pushedArgs];
@@ -1009,8 +1017,8 @@ class InterpreterStack
// For execution of eval or global code.
InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script, const Value& thisv,
HandleObject scopeChain, ExecuteType type,
AbstractFramePtr evalInFrame);
const Value& newTargetValue, HandleObject scopeChain,
ExecuteType type, AbstractFramePtr evalInFrame);
// Called to invoke a function.
InterpreterFrame* pushInvokeFrame(JSContext* cx, const CallArgs& args,
@@ -1746,6 +1754,8 @@ class FrameIter
Value computedThisValue() const;
Value thisv(JSContext* cx) const;
Value newTarget() const;
Value returnValue() const;
void setReturnValue(const Value& v);
+6 -21
View File
@@ -58,33 +58,18 @@ Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const
MOZ_CRASH("null ubi::Node");
}
struct Node::ConstructFunctor : public BoolDefaultAdaptor<Value, false> {
template <typename T> bool operator()(T* t, Node* node) { node->construct(t); return true; }
};
Node::Node(JSGCTraceKind kind, void* ptr)
{
switch (kind) {
case JSTRACE_OBJECT: construct(static_cast<JSObject*>(ptr)); break;
case JSTRACE_SCRIPT: construct(static_cast<JSScript*>(ptr)); break;
case JSTRACE_STRING: construct(static_cast<JSString*>(ptr)); break;
case JSTRACE_SYMBOL: construct(static_cast<JS::Symbol*>(ptr)); break;
case JSTRACE_BASE_SHAPE: construct(static_cast<js::BaseShape*>(ptr)); break;
case JSTRACE_JITCODE: construct(static_cast<js::jit::JitCode*>(ptr)); break;
case JSTRACE_LAZY_SCRIPT: construct(static_cast<js::LazyScript*>(ptr)); break;
case JSTRACE_SHAPE: construct(static_cast<js::Shape*>(ptr)); break;
case JSTRACE_OBJECT_GROUP: construct(static_cast<js::ObjectGroup*>(ptr)); break;
default:
MOZ_CRASH("bad JSGCTraceKind passed to JS::ubi::Node::Node");
}
CallTyped(ConstructFunctor(), ptr, kind, this);
}
Node::Node(HandleValue value)
{
if (value.isObject())
construct(&value.toObject());
else if (value.isString())
construct(value.toString());
else if (value.isSymbol())
construct(value.toSymbol());
else
if (!DispatchValueTyped(ConstructFunctor(), value, this))
construct<void>(nullptr);
}
+1 -1
View File
@@ -29,7 +29,7 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 281;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 282;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
+57
View File
@@ -1754,6 +1754,20 @@ GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID,
}
}
extern void
xpc::GetCurrentCompartmentName(JSContext* cx, nsCString& name)
{
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
name.AssignLiteral("no global");
return;
}
JSCompartment* compartment = GetObjectCompartment(global);
int anonymizeID = 0;
GetCompartmentName(compartment, name, &anonymizeID, false);
}
static int64_t
JSMainRuntimeGCHeapDistinguishedAmount()
{
@@ -3208,6 +3222,47 @@ static const JSWrapObjectCallbacks WrapObjectCallbacks = {
xpc::WrapperFactory::PrepareForWrapping
};
/**
* Group JSCompartments into PerformanceGroups.
*
* - All JSCompartments from the same add-on belong to the same
* PerformanceGroup.
* - All JSCompartments from the same same webpage (including
* frames) belong to the same PerformanceGroup.
* - All other JSCompartments (normally, system add-ons)
* belong to to a big uncategorized PerformanceGroup.
*/
static void*
GetCurrentPerfGroupCallback(JSContext* cx) {
RootedObject global(cx, CurrentGlobalOrNull(cx));
if (!global) {
// This can happen for the atom compartments, which is system
// code.
return nullptr;
}
JSAddonId* addonId = AddonIdOfObject(global);
if (addonId) {
// If this is an add-on, use the id as key.
return addonId;
}
// If the compartment belongs to a webpage, use the address of the
// topmost scriptable window, hence regrouping all frames of a
// window.
nsRefPtr<nsGlobalWindow> win = WindowOrNull(global);
if (win) {
nsCOMPtr<nsPIDOMWindow> top = win->GetScriptableTop();
if (!top) {
return nullptr;
}
return top.get();
}
// Otherwise, this is platform code, use `nullptr` as key.
return nullptr;
}
XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
: CycleCollectedJSRuntime(nullptr, JS::DefaultHeapMaxBytes, JS::DefaultNurseryBytes),
mJSContextStack(new XPCJSContextStack(this)),
@@ -3382,6 +3437,8 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
// Watch for the JS boolean options.
ReloadPrefsCallback(nullptr, this);
Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
JS_SetCurrentPerfGroupCallback(runtime, ::GetCurrentPerfGroupCallback);
}
// static
+6 -3
View File
@@ -144,9 +144,6 @@ XrayAwareCalleeGlobal(JSObject* fun);
void
TraceXPCGlobal(JSTracer* trc, JSObject* obj);
uint64_t
GetCompartmentCPOWMicroseconds(JSCompartment* compartment);
} /* namespace xpc */
namespace JS {
@@ -537,6 +534,12 @@ void
DispatchScriptErrorEvent(nsPIDOMWindow* win, JSRuntime* rt, xpc::ErrorReport* xpcReport,
JS::Handle<JS::Value> exception);
// Return a name for the compartment.
// This function makes reasonable efforts to make this name both mostly human-readable
// and unique. However, there are no guarantees of either property.
extern void
GetCurrentCompartmentName(JSContext*, nsCString& name);
} // namespace xpc
namespace mozilla {
-3
View File
@@ -1,8 +1,5 @@
[DEFAULT]
[browser_bug685470.js]
skip-if = e10s # Bug ?????? - test touches content (TypeError: doc.documentElement is null)
[browser_bug703210.js]
skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_bug706743.js]
skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
+17 -35
View File
@@ -1,9 +1,10 @@
function test() {
waitForExplicitFinish();
add_task(function* () {
const html = "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html," + html);
gBrowser.selectedTab = gBrowser.addTab();
SpecialPowers.setIntPref("ui.tooltipDelay", 0);
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
});
var popup = false;
var doc;
@@ -13,39 +14,20 @@ function test() {
let onPopupShown = function(aEvent) {
popup = true;
}
document.addEventListener("popupshown", onPopupShown, true);
// test that a mouse click prior to the tooltip appearing blocks it
let runTest = function() {
EventUtils.synthesizeMouseAtCenter(p1, { type: "mousemove" }, win);
EventUtils.sendMouseEvent({type:'mousedown'}, p1, win);
EventUtils.sendMouseEvent({type:'mouseup'}, p1, win);
// Send a mousemove at a known position to start the test.
BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" },
gBrowser.selectedBrowser);
BrowserTestUtils.synthesizeMouseAtCenter("#p1", { }, gBrowser.selectedBrowser);
yield new Promise(resolve => {
setTimeout(function() {
is(popup, false, "shouldn't get tooltip after click");
document.removeEventListener("popupshown", onPopupShown, true);
SpecialPowers.clearUserPref("ui.tooltipDelay");
gBrowser.removeCurrentTab();
finish();
resolve();
}, 200);
}
});
let onLoad = function (aEvent) {
doc = gBrowser.contentDocument;
win = gBrowser.contentWindow;
p1 = doc.getElementById("p1");
document.addEventListener("popupshown", onPopupShown, true);
runTest();
}
gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
setTimeout(onLoad, 0);
}, true);
content.location = "data:text/html," +
"<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>";
}
document.removeEventListener("popupshown", onPopupShown, true);
gBrowser.removeCurrentTab();
});
+32 -71
View File
@@ -1,72 +1,33 @@
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
SpecialPowers.setIntPref("ui.tooltipDelay", 0);
let doStopPropagation = function (aEvent)
{
aEvent.stopPropagation();
}
let onPopupShown = function (aEvent)
{
is(aEvent.originalTarget.localName, "tooltip", "tooltip is showing");
let doc = gBrowser.contentDocument;
let win = gBrowser.contentWindow;
let p2 = doc.getElementById("p2");
setTimeout(function () {
EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win); }, 0);
}
let onPopupHiding = function (aEvent)
{
is(aEvent.originalTarget.localName, "tooltip", "tooltip is hiding");
let doc = gBrowser.contentDocument;
doc.removeEventListener("mousemove", doStopPropagation, true);
doc.removeEventListener("mouseenter", doStopPropagation, true);
doc.removeEventListener("mouseleave", doStopPropagation, true);
doc.removeEventListener("mouseover", doStopPropagation, true);
doc.removeEventListener("mouseout", doStopPropagation, true);
document.removeEventListener("popupshown", onPopupShown, true);
document.removeEventListener("popuphiding", onPopupHiding, true);
SpecialPowers.clearUserPref("ui.tooltipDelay");
gBrowser.removeCurrentTab();
finish();
}
let onLoad = function (aEvent)
{
let doc = gBrowser.contentDocument;
let win = gBrowser.contentWindow;
let p1 = doc.getElementById("p1");
let p2 = doc.getElementById("p2");
EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win);
doc.addEventListener("mousemove", doStopPropagation, true);
doc.addEventListener("mouseenter", doStopPropagation, true);
doc.addEventListener("mouseleave", doStopPropagation, true);
doc.addEventListener("mouseover", doStopPropagation, true);
doc.addEventListener("mouseout", doStopPropagation, true);
document.addEventListener("popupshown", onPopupShown, true);
document.addEventListener("popuphiding", onPopupHiding, true);
EventUtils.synthesizeMouseAtCenter(p1, { type: "mousemove" }, win);
}
gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
setTimeout(onLoad, 0);
}, true);
content.location = "data:text/html," +
add_task(function* () {
const url = "data:text/html," +
"<html onmousemove='event.stopPropagation()'" +
" onmouseenter='event.stopPropagation()' onmouseleave='event.stopPropagation()'" +
" onmouseover='event.stopPropagation()' onmouseout='event.stopPropagation()'>" +
"<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>" +
"<p id=\"p2\">This paragraph doesn't have tooltip.</p>";
}
"<p id=\"p2\">This paragraph doesn't have tooltip.</p></html>";
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
let browser = gBrowser.selectedBrowser;
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
});
// Send a mousemove at a known position to start the test.
yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser);
let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", event => {
is(event.originalTarget.localName, "tooltip", "tooltip is showing");
return true;
});
yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, browser);
yield popupShownPromise;
let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden", event => {
is(event.originalTarget.localName, "tooltip", "tooltip is hidden");
return true;
});
yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser);
yield popupHiddenPromise;
gBrowser.removeCurrentTab();
});
+67 -137
View File
@@ -1,154 +1,84 @@
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
add_task(function* () {
const url = "data:text/html,<html><head></head><body>" +
"<a id=\"target\" href=\"about:blank\" title=\"This is tooltip text\" " +
"style=\"display:block;height:20px;margin:10px;\" " +
"onclick=\"return false;\">here is an anchor element</a></body></html>";
SpecialPowers.setIntPref("ui.tooltipDelay", 0);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
let browser = gBrowser.selectedBrowser;
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
});
// Send a mousemove at a known position to start the test.
yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser);
// show tooltip by mousemove into target.
let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
yield popupShownPromise;
// hide tooltip by mousemove to outside.
let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
yield popupHiddenPromise;
// mousemove into the target and start drag by emulation via nsIDragService.
// Note that on some platforms, we cannot actually start the drag by
// synthesized events. E.g., Windows waits an actual mousemove event after
// dragstart.
// Emulate a buggy mousemove event. widget might dispatch mousemove event
// during drag.
function tooltipNotExpected()
{
ok(false, "tooltip is shown during drag");
}
addEventListener("popupshown", tooltipNotExpected, true);
let target;
let doc;
let win;
let callbackOnPopupShown;
let callbackOnPopupHidden;
let dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
getService(Components.interfaces.nsIDragService);
dragService.startDragSession();
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
let onPopupHidden = function (aEvent)
{
if (aEvent.originalTarget.localName != "tooltip") {
return;
}
if (callbackOnPopupHidden) {
setTimeout(callbackOnPopupHidden, 0);
}
}
yield new Promise(resolve => setTimeout(resolve, 100));
removeEventListener("popupshown", tooltipNotExpected, true);
dragService.endDragSession(true);
let onPopupShown = function (aEvent)
{
if (aEvent.originalTarget.localName != "tooltip") {
return;
}
if (callbackOnPopupShown) {
setTimeout(callbackOnPopupShown, 0);
}
}
yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser);
let finishTest = function ()
{
document.removeEventListener("popupshown", onPopupShown, true);
document.removeEventListener("popuphidden", onPopupHidden, true);
// If tooltip listener used a flag for managing D&D state, we would need
// to test if the tooltip is shown after drag.
SpecialPowers.clearUserPref("ui.tooltipDelay");
// show tooltip by mousemove into target.
popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
yield popupShownPromise;
gBrowser.removeCurrentTab();
finish();
}
// hide tooltip by mousemove to outside.
popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
yield popupHiddenPromise;
let testHideTooltipByMouseDown = function ()
{
callbackOnPopupShown = function () {
callbackOnPopupShown = null;
ok(true, "tooltip is shown before testing mousedown");
// Show tooltip after mousedown
popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser);
yield popupShownPromise;
// hide tooltip by mousemove to outside.
callbackOnPopupHidden = function () {
callbackOnPopupHidden = null;
ok(true, "The tooltip is hidden by mousedown");
popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden");
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousedown" }, browser);
yield popupHiddenPromise;
EventUtils.synthesizeMouse(target, 5, 15, { type: "mouseup" }, win);
EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win);
yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mouseup" }, browser);
yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser);
setTimeout(finishTest, 0);
}
EventUtils.synthesizeMouse(target, 5, 15, { type: "mousedown" }, win);
}
EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win);
}
ok(true, "tooltips appear properly");
gBrowser.removeCurrentTab();
});
let testShowTooltipAgain = function ()
{
// If tooltip listener used a flag for managing D&D state, we would need
// to test if the tooltip is shown after drag.
callbackOnPopupShown = function () {
callbackOnPopupShown = null;
ok(true, "tooltip is shown after drag");
// hide tooltip by mousemove to outside.
callbackOnPopupHidden = function () {
callbackOnPopupHidden = null;
ok(true, "The tooltip is hidden again");
setTimeout(testHideTooltipByMouseDown, 0);
}
EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win);
}
EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win);
}
let testDuringDnD = function ()
{
// mousemove into the target and start drag by emulation via nsIDragService.
// Note that on some platforms, we cannot actually start the drag by
// synthesized events. E.g., Windows waits an actual mousemove event after
// dragstart.
callbackOnPopupShown = function () {
callbackOnPopupShown = null;
ok(false, "tooltip is shown during drag");
}
dragService.startDragSession();
// Emulate a buggy mousemove event. widget might dispatch mousemove event
// during drag.
EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win);
setTimeout(function () {
callbackOnPopupShown = null;
ok(true, "tooltip isn't shown during drag");
dragService.endDragSession(true);
EventUtils.synthesizeMouse(target, -5, -5, { type: "mousemove" }, win);
setTimeout(testShowTooltipAgain, 0);
}, 100);
}
let onLoad = function (aEvent)
{
doc = gBrowser.contentDocument;
win = gBrowser.contentWindow;
target = doc.getElementById("target");
document.addEventListener("popupshown", onPopupShown, true);
document.addEventListener("popuphidden", onPopupHidden, true);
EventUtils.synthesizeMouse(target, -5, -5, { type: "mousemove" }, win);
// show tooltip by mousemove into target.
callbackOnPopupShown = function ()
{
callbackOnPopupShown = null;
ok(true, "The tooltip is shown");
// hide tooltip by mousemove to outside.
callbackOnPopupHidden = function () {
callbackOnPopupHidden = null;
ok(true, "The tooltip is hidden");
setTimeout(testDuringDnD, 0);
}
EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win);
}
EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win);
}
gBrowser.selectedBrowser.addEventListener("load",
function () {
gBrowser.selectedBrowser.
removeEventListener("load", arguments.callee, true);
setTimeout(onLoad, 0);
}, true);
content.location = "data:text/html,<html><head></head><body>" +
"<a id=\"target\" href=\"about:blank\" title=\"This is tooltip text\" " +
"style=\"display:block;height:20px;margin:10px;\" " +
"onclick=\"return false;\">here is an anchor element</a></body></html>";
}
@@ -40,36 +40,47 @@
}
td.jank0 {
color: rgb(0, 0, 0);
font-weight: bold;
}
td.jank1 {
color: rgb(25, 0, 0);
font-weight: bold;
}
td.jank2 {
color: rgb(50, 0, 0);
font-weight: bold;
}
td.jank3 {
color: rgb(75, 0, 0);
font-weight: bold;
}
td.jank4 {
color: rgb(100, 0, 0);
font-weight: bold;
}
td.jank5 {
color: rgb(125, 0, 0);
font-weight: bold;
}
td.jank6 {
color: rgb(150, 0, 0);
font-weight: bold;
}
td.jank7 {
color: rgb(175, 0, 0);
font-weight: bold;
}
td.jank8 {
color: rgb(200, 0, 0);
font-weight: bold;
}
td.jank9 {
color: rgb(225, 0, 0);
font-weight: bold;
}
td.jank9 {
td.jank10 {
color: rgb(255, 0, 0);
font-weight: bold;
}
</style>
</head>
@@ -22,6 +22,10 @@ EXPORTS += [
'nsPerformanceStats.h'
]
LOCAL_INCLUDES += [
'/dom/base',
]
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
@@ -6,6 +6,7 @@
#include "nsISupports.idl"
#include "nsIArray.idl"
#include "nsIDOMWindow.idl"
/**
* Mechanisms for querying the current process about performance
@@ -21,7 +22,7 @@
* All values are monotonic and are updated only when
* `nsIPerformanceStatsService.isStopwatchActive` is `true`.
*/
[scriptable, uuid(f015cbad-e16f-4982-a080-67e4e69a5b2e)]
[scriptable, uuid(b060d75d-55bc-4c82-a4ff-458fc5ab2a69)]
interface nsIPerformanceStats: nsISupports {
/**
* The name of the component:
@@ -38,6 +39,18 @@ interface nsIPerformanceStats: nsISupports {
*/
readonly attribute AString addonId;
/**
* If the component is code executed in a window, the ID of the topmost
* outer window (i.e. the tab), otherwise 0.
*/
readonly attribute uint64_t windowId;
/**
* If the component is code executed in a window, the title of the topmost
* window (i.e. the tab), otherwise an empty string.
*/
readonly attribute AString title;
/**
* Total amount of time spent executing code in this group, in
* microseconds.
@@ -91,17 +104,22 @@ interface nsIPerformanceSnapshot: nsISupports {
nsIPerformanceStats getProcessData();
};
[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)]
[scriptable, builtinclass, uuid(0469e6af-95c3-4961-a385-4bc009128939)]
interface nsIPerformanceStatsService : nsISupports {
/**
* `true` if we should monitor performance, `false` otherwise.
* `true` if we should monitor CPOW, `false` otherwise.
*/
[implicit_jscontext] attribute bool isStopwatchActive;
[implicit_jscontext] attribute bool isMonitoringCPOW;
/**
* `true` if we should monitor jank, `false` otherwise.
*/
[implicit_jscontext] attribute bool isMonitoringJank;
/**
* Capture a snapshot of the performance data.
*/
nsIPerformanceSnapshot getSnapshot();
[implicit_jscontext] nsIPerformanceSnapshot getSnapshot();
};
%{C++
@@ -3,24 +3,37 @@
* 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 "jsapi.h"
#include "nsPerformanceStats.h"
#include "nsMemory.h"
#include "nsLiteralString.h"
#include "nsCRTGlue.h"
#include "nsIJSRuntimeService.h"
#include "nsServiceManagerUtils.h"
#include "nsCOMArray.h"
#include "nsIMutableArray.h"
#include "jsapi.h"
#include "nsJSUtils.h"
#include "xpcpublic.h"
#include "jspubtd.h"
#include "nsIJSRuntimeService.h"
#include "nsIDOMWindow.h"
#include "nsGlobalWindow.h"
class nsPerformanceStats: public nsIPerformanceStats {
public:
nsPerformanceStats(nsAString& aName, nsAString& aAddonId, bool aIsSystem, js::PerformanceData& aPerformanceData)
nsPerformanceStats(const nsAString& aName,
const nsAString& aAddonId,
const nsAString& aTitle,
const uint64_t aWindowId,
const bool aIsSystem,
const js::PerformanceData& aPerformanceData)
: mName(aName)
, mAddonId(aAddonId)
, mTitle(aTitle)
, mWindowId(aWindowId)
, mIsSystem(aIsSystem)
, mPerformanceData(aPerformanceData)
{
@@ -41,6 +54,24 @@ public:
return NS_OK;
};
/* readonly attribute uint64_t windowId; */
NS_IMETHOD GetWindowId(uint64_t *aWindowId) override {
*aWindowId = mWindowId;
return NS_OK;
}
/* readonly attribute AString title; */
NS_IMETHOD GetTitle(nsAString & aTitle) override {
aTitle.Assign(mTitle);
return NS_OK;
}
/* readonly attribute bool isSystem; */
NS_IMETHOD GetIsSystem(bool *_retval) override {
*_retval = mIsSystem;
return NS_OK;
}
/* readonly attribute unsigned long long totalUserTime; */
NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) override {
*aTotalUserTime = mPerformanceData.totalUserTime;
@@ -78,16 +109,13 @@ public:
return NS_OK;
};
/* readonly attribute bool isSystem; */
NS_IMETHOD GetIsSystem(bool *_retval) override {
*_retval = mIsSystem;
return NS_OK;
}
private:
nsString mName;
nsString mAddonId;
nsString mTitle;
uint64_t mWindowId;
bool mIsSystem;
js::PerformanceData mPerformanceData;
virtual ~nsPerformanceStats() {}
@@ -103,15 +131,45 @@ public:
NS_DECL_NSIPERFORMANCESNAPSHOT
nsPerformanceSnapshot();
nsresult Init();
nsresult Init(JSContext*);
private:
virtual ~nsPerformanceSnapshot();
/**
* Import a `PerformanceStats` as a `nsIPerformanceStats`.
* Import a `js::PerformanceStats` as a `nsIPerformanceStats`.
*
* Precondition: this method assumes that we have entered the JSCompartment for which data `c`
* has been collected.
*
* `cx` may be `nullptr` if we are importing the statistics for the
* entire process, rather than the statistics for a specific set of
* compartments.
*/
already_AddRefed<nsIPerformanceStats> ImportStats(js::PerformanceStats* c);
already_AddRefed<nsIPerformanceStats> ImportStats(JSContext* cx, const js::PerformanceData& data);
/**
* Callbacks for iterating through the `PerformanceStats` of a runtime.
*/
bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats);
static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self);
// If the context represents a window, extract the title and window ID.
// Otherwise, extract "" and 0.
static void GetWindowData(JSContext*,
nsString& title,
uint64_t* windowId);
// If the context presents an add-on, extract the addon ID.
// Otherwise, extract "".
static void GetAddonId(JSContext*,
JS::Handle<JSObject*> global,
nsAString& addonId);
// Determine whether a context is part of the system principals.
static bool GetIsSystem(JSContext*,
JS::Handle<JSObject*> global);
private:
nsCOMArray<nsIPerformanceStats> mComponentsData;
nsCOMPtr<nsIPerformanceStats> mProcessData;
};
@@ -126,36 +184,118 @@ nsPerformanceSnapshot::~nsPerformanceSnapshot()
{
}
already_AddRefed<nsIPerformanceStats>
nsPerformanceSnapshot::ImportStats(js::PerformanceStats* c) {
nsString addonId;
if (c->addonId) {
AssignJSFlatString(addonId, (JSFlatString*)c->addonId);
/* static */ void
nsPerformanceSnapshot::GetWindowData(JSContext* cx,
nsString& title,
uint64_t* windowId)
{
MOZ_ASSERT(windowId);
title.SetIsVoid(true);
*windowId = 0;
nsCOMPtr<nsPIDOMWindow> win = xpc::CurrentWindowOrNull(cx);
if (!win) {
return;
}
nsCString cname(c->name);
NS_ConvertUTF8toUTF16 name(cname);
nsCOMPtr<nsIPerformanceStats> result = new nsPerformanceStats(name, addonId, c->isSystem, c->performance);
nsCOMPtr<nsIDOMWindow> top = win->GetTop();
if (!top) {
return;
}
nsCOMPtr<nsPIDOMWindow> ptop = do_QueryInterface(top);
if (!ptop) {
return;
}
nsCOMPtr<nsIDocument> doc = ptop->GetExtantDoc();
if (!doc) {
return;
}
doc->GetTitle(title);
*windowId = ptop->WindowID();
}
/* static */ void
nsPerformanceSnapshot::GetAddonId(JSContext*,
JS::Handle<JSObject*> global,
nsAString& addonId)
{
addonId.AssignLiteral("");
JSAddonId* jsid = AddonIdOfObject(global);
if (!jsid) {
return;
}
AssignJSFlatString(addonId, (JSFlatString*)jsid);
}
/* static */ bool
nsPerformanceSnapshot::GetIsSystem(JSContext*,
JS::Handle<JSObject*> global)
{
return nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
}
already_AddRefed<nsIPerformanceStats>
nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance) {
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
// While it is possible for a compartment to have no global
// (e.g. atoms), this compartment is not very interesting for us.
return nullptr;
}
nsString addonId;
GetAddonId(cx, global, addonId);
nsString title;
uint64_t windowId;
GetWindowData(cx, title, &windowId);
nsAutoString name;
nsAutoCString cname;
xpc::GetCurrentCompartmentName(cx, cname);
name.Assign(NS_ConvertUTF8toUTF16(cname));
bool isSystem = GetIsSystem(cx, global);
nsCOMPtr<nsIPerformanceStats> result =
new nsPerformanceStats(name, addonId, title, windowId, isSystem, performance);
return result.forget();
}
nsresult
nsPerformanceSnapshot::Init() {
JSRuntime* rt;
nsCOMPtr<nsIJSRuntimeService> svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
svc->GetRuntime(&rt);
js::PerformanceStats processStats;
js::PerformanceStatsVector componentsStats;
if (!js::GetPerformanceStats(rt, componentsStats, processStats)) {
return NS_ERROR_OUT_OF_MEMORY;
/*static*/ bool
nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self) {
return reinterpret_cast<nsPerformanceSnapshot*>(self)->IterPerformanceStatsCallbackInternal(cx, stats);
}
bool
nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats) {
nsCOMPtr<nsIPerformanceStats> result = ImportStats(cx, stats);
if (result) {
mComponentsData.AppendElement(result);
}
size_t num = componentsStats.length();
for (size_t pos = 0; pos < num; pos++) {
nsCOMPtr<nsIPerformanceStats> stats = ImportStats(&componentsStats[pos]);
mComponentsData.AppendObject(stats);
return true;
}
nsresult
nsPerformanceSnapshot::Init(JSContext* cx) {
js::PerformanceData processStats;
if (!js::IterPerformanceStats(cx, nsPerformanceSnapshot::IterPerformanceStatsCallback, &processStats, this)) {
return NS_ERROR_UNEXPECTED;
}
mProcessData = ImportStats(&processStats);
mProcessData = new nsPerformanceStats(NS_LITERAL_STRING("<process>"), // name
NS_LITERAL_STRING(""), // add-on id
NS_LITERAL_STRING(""), // title
0, // window id
true, // isSystem
processStats);
return NS_OK;
}
@@ -192,27 +332,41 @@ nsPerformanceStatsService::~nsPerformanceStatsService()
{
}
/* [implicit_jscontext] attribute bool isStopwatchActive; */
NS_IMETHODIMP nsPerformanceStatsService::GetIsStopwatchActive(JSContext* cx, bool *aIsStopwatchActive)
//[implicit_jscontext] attribute bool isMonitoringCPOW;
NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
{
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::IsStopwatchActive(runtime);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(runtime);
return NS_OK;
}
NS_IMETHODIMP nsPerformanceStatsService::SetIsStopwatchActive(JSContext* cx, bool aIsStopwatchActive)
NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
{
JSRuntime *runtime = JS_GetRuntime(cx);
if (!js::SetStopwatchActive(runtime, aIsStopwatchActive)) {
if (!js::SetStopwatchIsMonitoringCPOW(runtime, aIsStopwatchActive)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
{
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(runtime);
return NS_OK;
}
NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
{
JSRuntime *runtime = JS_GetRuntime(cx);
if (!js::SetStopwatchIsMonitoringJank(runtime, aIsStopwatchActive)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
/* readonly attribute nsIPerformanceSnapshot snapshot; */
NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(nsIPerformanceSnapshot * *aSnapshot)
NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
{
nsRefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
nsresult rv = snapshot->Init();
nsresult rv = snapshot->Init(cx);
if (NS_FAILED(rv)) {
return rv;
}
@@ -2,24 +2,19 @@
<html>
<head>
<title>
browser_compartments.html
Main frame for test browser_aboutperformance.js
</title>
<script type="text/javascript">
// Use some CPU.
window.setInterval(() => {
// Compute an arbitrary value, print it out to make sure that the JS
// engine doesn't discard all our computation.
var date = Date.now();
var array = [];
var i = 0;
while (Date.now() - date <= 100) {
array[i%2] = i++;
}
console.log("Arbitrary value", array);
}, 300);
</script>
</head>
<body>
browser_compartments.html
Main frame.
<iframe src="browser_compartments_frame.html?frame=1">
Subframe 1
</iframe>
<iframe src="browser_compartments_frame.html?frame=2">
Subframe 2.
</iframe>
</body>
</html>
@@ -141,13 +141,16 @@ add_task(function* test() {
monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process");
monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" );
let skipTotalUserTime = hasLowPrecision();
while (true) {
let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null));
let found = stats.componentsData.find(stat => {
return (stat.name.indexOf(URL) != -1)
&& (stat.totalUserTime > 1000)
&& (skipTotalUserTime || stat.totalUserTime > 1000)
});
if (found) {
info(`Expected totalUserTime > 1000, got ${found.totalUserTime}`);
break;
}
yield new Promise(resolve => setTimeout(resolve, 100));
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>
Subframe for test browser_compartments.html (do not change this title)
</title>
<script src="browser_compartments_script.js"></script>
</head>
<body>
Subframe loaded.
</body>
</html>
@@ -0,0 +1,29 @@
var carryOn = true;
window.addEventListener("message", e => {
console.log("frame content", "message", e);
if ("title" in e.data) {
document.title = e.data.title;
}
if ("stop" in e.data) {
carryOn = false;
}
});
// Use some CPU.
var interval = window.setInterval(() => {
if (!carryOn) {
window.clearInterval(interval);
return;
}
// Compute an arbitrary value, print it out to make sure that the JS
// engine doesn't discard all our computation.
var date = Date.now();
var array = [];
var i = 0;
while (Date.now() - date <= 100) {
array[i%2] = i++;
}
}, 300);
@@ -3,6 +3,8 @@
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
Cu.import("resource://gre/modules/Services.jsm", this);
function promiseContentResponse(browser, name, message) {
let mm = browser.messageManager;
let promise = new Promise(resolve => {
@@ -27,3 +29,24 @@ function promiseContentResponseOrNull(browser, name, message) {
}
return promiseContentResponse(browser, name, message);
}
/**
* `true` if we are running an OS in which the OS performance
* clock has a low precision and might unpredictably
* never be updated during the execution of the test.
*/
function hasLowPrecision() {
let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
info(`Running ${sysName} version ${sysVersion}`);
if (sysName == "Windows_NT" && sysVersion < 6) {
info("Running old Windows, need to deactivate tests due to bad precision.");
return true;
}
if (sysName == "Linux" && sysVersion <= 2.6) {
info("Running old Linux, need to deactivate tests due to bad precision.");
return true;
}
info("This platform has good precision.")
return false;
}
+1 -1
View File
@@ -158,7 +158,7 @@ let AddonWatcher = {
}
if (diff.totalCPOWTime > 0) {
Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_CPOW_TIME_MS").
add(addonId, diff.totalCPOWTime);
add(addonId, diff.totalCPOWTime / 1000);
}
// Report mibehaviors to the user.
@@ -82,7 +82,7 @@ let burn_rubber = Task.async(function*({histogramName, topic, expectedReason, pr
Assert.equal(reason, expectedReason, "Reason is valid");
let snap2 = histogram.snapshot(ADDON_ID);
Assert.ok(snap2.sum > expectedMinSum, `Histogram ${histogramName} recorded a gravity of ${snap2.sum}, expecting at least ${expectedMinSum}.`);
Assert.ok(snap2.sum >= expectedMinSum, `Histogram ${histogramName} recorded a gravity of ${snap2.sum}, expecting at least ${expectedMinSum}.`);
} finally {
AddonWatcher.uninit();
for (let key of Object.keys(prefs)) {
@@ -121,6 +121,6 @@ add_task(function* test_burn_CPOW() {
histogramName: "MISBEHAVING_ADDONS_CPOW_TIME_MS",
topic: "test-addonwatcher-burn-some-cpow",
expectedReason: "totalCPOWTime",
expectedMinSum: 1000,
expectedMinSum: 400,
});
});