Bug 937317 - Implement and expose GetIncumbentGlobal. r=bz,luke

This commit is contained in:
Bobby Holley 2013-12-05 21:34:16 -08:00
parent 286a469f49
commit 550f45cbab
8 changed files with 149 additions and 0 deletions

View File

@ -1545,6 +1545,7 @@ public:
static JSContext *GetCurrentJSContext();
static JSContext *GetSafeJSContext();
static JSContext *GetCurrentJSContextForThread();
static JSContext *GetDefaultJSContextForThread();
/**

View File

@ -5262,6 +5262,17 @@ nsContentUtils::GetDefaultJSContextForThread()
}
}
/* static */
JSContext *
nsContentUtils::GetCurrentJSContextForThread()
{
if (MOZ_LIKELY(NS_IsMainThread())) {
return GetCurrentJSContext();
} else {
return workers::GetCurrentThreadJSContext();
}
}
/* static */
nsresult
nsContentUtils::ASCIIToLower(nsAString& aStr)

View File

@ -9,6 +9,7 @@
#include "mozilla/Assertions.h"
#include "jsapi.h"
#include "xpcpublic.h"
#include "nsIGlobalObject.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
@ -91,6 +92,39 @@ void DestroyScriptSettings()
delete ptr;
}
// Note: When we're ready to expose it, GetEntryGlobal will look similar to
// GetIncumbentGlobal below.
nsIGlobalObject*
GetIncumbentGlobal()
{
// We need the current JSContext in order to check the JS for
// scripted frames that may have appeared since anyone last
// manipulated the stack. If it's null, that means that there
// must be no entry point on the stack, and therefore no incumbent
// global either.
JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
if (!cx) {
MOZ_ASSERT(ScriptSettingsStack::Ref().EntryPoint() == nullptr);
return nullptr;
}
// See what the JS engine has to say. If we've got a scripted caller
// override in place, the JS engine will lie to us and pretend that
// there's nothing on the JS stack, which will cause us to check the
// incumbent script stack below.
JS::RootedScript script(cx);
if (JS_DescribeScriptedCaller(cx, &script, nullptr)) {
JS::RootedObject global(cx, JS_GetGlobalFromScript(script));
MOZ_ASSERT(global);
return xpc::GetNativeForGlobal(global);
}
// Ok, nothing from the JS engine. Let's use whatever's on the
// explicit stack.
return ScriptSettingsStack::Ref().Incumbent();
}
AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
bool aIsMainThread,
JSContext* aCx)
@ -128,6 +162,7 @@ AutoEntryScript::~AutoEntryScript()
AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
: mStack(ScriptSettingsStack::Ref())
, mEntry(aGlobalObject, /* aCandidate = */ false)
, mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
{
mStack.Push(&mEntry);
}

View File

@ -27,6 +27,12 @@ namespace dom {
void InitScriptSettings();
void DestroyScriptSettings();
// Note: We don't yet expose GetEntryGlobal, because in order for it to be
// correct, we first need to replace a bunch of explicit cx pushing in the
// browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it
// can mostly be inferred from the JS stack.
nsIGlobalObject* GetIncumbentGlobal();
class ScriptSettingsStack;
struct ScriptSettingsStackEntry {
nsCOMPtr<nsIGlobalObject> mGlobalObject;
@ -86,6 +92,7 @@ public:
private:
dom::ScriptSettingsStack& mStack;
dom::ScriptSettingsStackEntry mEntry;
JS::AutoHideScriptedCaller mCallerOverride;
};
/*

View File

@ -6108,12 +6108,44 @@ JS_DescribeScriptedCaller(JSContext *cx, MutableHandleScript script, unsigned *l
if (i.done())
return false;
// If the caller is hidden, the embedding wants us to return null here so
// that it can check its own stack.
if (i.activation()->scriptedCallerIsHidden())
return false;
script.set(i.script());
if (lineno)
*lineno = js::PCToLineNumber(i.script(), i.pc());
return true;
}
namespace JS {
JS_PUBLIC_API(void)
HideScriptedCaller(JSContext *cx)
{
MOZ_ASSERT(cx);
// If there's no accessible activation on the stack, we'll return null from
// JS_DescribeScriptedCaller anyway, so there's no need to annotate
// anything.
Activation *act = cx->runtime()->mainThread.activation();
if (!act)
return;
act->hideScriptedCaller();
}
JS_PUBLIC_API(void)
UnhideScriptedCaller(JSContext *cx)
{
Activation *act = cx->runtime()->mainThread.activation();
if (!act)
return;
act->unhideScriptedCaller();
}
} /* namespace JS */
#ifdef JS_THREADSAFE
static PRStatus
CallOnce(void *func)

View File

@ -4576,10 +4576,53 @@ JS_IsIdentifier(JSContext *cx, JS::HandleString str, bool *isIdentifier);
/*
* Return the current script and line number of the most currently running
* frame. Returns true if a scripted frame was found, false otherwise.
*
* If a the embedding has hidden the scripted caller for the topmost activation
* record, this will also return false.
*/
extern JS_PUBLIC_API(bool)
JS_DescribeScriptedCaller(JSContext *cx, JS::MutableHandleScript script, unsigned *lineno);
namespace JS {
/*
* Informs the JS engine that the scripted caller should be hidden. This can be
* used by the embedding to maintain an override of the scripted caller in its
* calculations, by hiding the scripted caller in the JS engine and pushing data
* onto a separate stack, which it inspects when JS_DescribeScriptedCaller
* returns null.
*
* We maintain a counter on each activation record. Add() increments the counter
* of the topmost activation, and Remove() decrements it. The count may never
* drop below zero, and must always be exactly zero when the activation is
* popped from the stack.
*/
extern JS_PUBLIC_API(void)
HideScriptedCaller(JSContext *cx);
extern JS_PUBLIC_API(void)
UnhideScriptedCaller(JSContext *cx);
class AutoHideScriptedCaller
{
public:
AutoHideScriptedCaller(JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mContext(cx)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
HideScriptedCaller(mContext);
}
~AutoHideScriptedCaller() {
UnhideScriptedCaller(mContext);
}
protected:
JSContext *mContext;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
} /* namepsace JS */
/*
* Encode/Decode interpreted scripts and functions to/from memory.

View File

@ -833,6 +833,7 @@ Activation::Activation(JSContext *cx, Kind kind)
compartment_(cx->compartment()),
prev_(cx->mainThread().activation_),
savedFrameChain_(0),
hideScriptedCallerCount_(0),
kind_(kind)
{
cx->mainThread().activation_ = this;
@ -841,6 +842,7 @@ Activation::Activation(JSContext *cx, Kind kind)
Activation::~Activation()
{
JS_ASSERT(cx_->mainThread().activation_ == this);
JS_ASSERT(hideScriptedCallerCount_ == 0);
cx_->mainThread().activation_ = prev_;
}

View File

@ -1167,6 +1167,13 @@ class Activation
// set).
size_t savedFrameChain_;
// Counter incremented by JS::HideScriptedCaller and decremented by
// JS::UnhideScriptedCaller. If > 0 for the top activation,
// JS_DescribeScriptedCaller will return null instead of querying that
// activation, which should prompt the caller to consult embedding-specific
// data structures instead.
size_t hideScriptedCallerCount_;
enum Kind { Interpreter, Jit, ForkJoin };
Kind kind_;
@ -1218,6 +1225,17 @@ class Activation
return savedFrameChain_ > 0;
}
void hideScriptedCaller() {
hideScriptedCallerCount_++;
}
void unhideScriptedCaller() {
JS_ASSERT(hideScriptedCallerCount_ > 0);
hideScriptedCallerCount_--;
}
bool scriptedCallerIsHidden() const {
return hideScriptedCallerCount_ > 0;
}
private:
Activation(const Activation &other) MOZ_DELETE;
void operator=(const Activation &other) MOZ_DELETE;