mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1117242 - SavedFrame objects should do principal checks for every accessor; r=jimb,jandem,bz
This commit is contained in:
parent
c9c82c55da
commit
770748abfb
@ -739,11 +739,10 @@ DOMException::Sanitize(JSContext* aCx,
|
||||
// Now it's possible that the stack on retval still starts with
|
||||
// stuff aCx is not supposed to touch; it depends on what's on the
|
||||
// stack right this second. Walk past all of that.
|
||||
while (retval->mLocation && !retval->mLocation->CallerSubsumes(aCx)) {
|
||||
nsCOMPtr<nsIStackFrame> caller;
|
||||
retval->mLocation->GetCaller(getter_AddRefs(caller));
|
||||
retval->mLocation.swap(caller);
|
||||
}
|
||||
nsCOMPtr<nsIStackFrame> stack;
|
||||
nsresult rv = retval->mLocation->GetSanitized(aCx, getter_AddRefs(stack));
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
retval->mLocation.swap(stack);
|
||||
}
|
||||
|
||||
return ToJSValue(aCx, retval, aSanitizedValue);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsprf.h"
|
||||
#include "mozilla/CycleCollectedJSRuntime.h"
|
||||
@ -299,6 +300,8 @@ public:
|
||||
NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE;
|
||||
virtual bool CallerSubsumes(JSContext* aCx) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetSanitized(JSContext* aCx,
|
||||
nsIStackFrame** aSanitized) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
virtual bool IsJSFrame() const MOZ_OVERRIDE {
|
||||
@ -390,15 +393,22 @@ NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename)
|
||||
JS::Rooted<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
|
||||
JS::Rooted<JS::Value> filenameVal(cx);
|
||||
if (!JS_GetProperty(cx, stack, "source", &filenameVal) ||
|
||||
!filenameVal.isString()) {
|
||||
if (!JS_GetProperty(cx, stack, "source", &filenameVal)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (filenameVal.isNull()) {
|
||||
filenameVal = JS_GetEmptyStringValue(cx);
|
||||
}
|
||||
MOZ_ASSERT(filenameVal.isString());
|
||||
|
||||
nsAutoJSString str;
|
||||
if (!str.init(cx, filenameVal.toString())) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
mFilename = str;
|
||||
mFilenameInitialized = true;
|
||||
}
|
||||
@ -471,11 +481,15 @@ JSStackFrame::GetLineno(int32_t* aLineNo)
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> lineVal(cx);
|
||||
if (!JS_GetProperty(cx, stack, "line", &lineVal) ||
|
||||
!lineVal.isNumber()) {
|
||||
if (!JS_GetProperty(cx, stack, "line", &lineVal)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (lineVal.isNumber()) {
|
||||
mLineno = lineVal.toNumber();
|
||||
} else {
|
||||
MOZ_ASSERT(lineVal.isNull());
|
||||
mLineno = 0;
|
||||
}
|
||||
mLinenoInitialized = true;
|
||||
}
|
||||
|
||||
@ -500,11 +514,15 @@ JSStackFrame::GetColNo(int32_t* aColNo)
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> colVal(cx);
|
||||
if (!JS_GetProperty(cx, stack, "column", &colVal) ||
|
||||
!colVal.isNumber()) {
|
||||
if (!JS_GetProperty(cx, stack, "column", &colVal)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (colVal.isNumber()) {
|
||||
mColNo = colVal.toNumber();
|
||||
} else {
|
||||
MOZ_ASSERT(colVal.isNull());
|
||||
mColNo = 0;
|
||||
}
|
||||
mColNoInitialized = true;
|
||||
}
|
||||
|
||||
@ -524,6 +542,35 @@ NS_IMETHODIMP StackFrame::GetSourceLine(nsACString& aSourceLine)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* [noscript] readonly attribute nsIStackFrame sanitized */
|
||||
NS_IMETHODIMP StackFrame::GetSanitized(JSContext*, nsIStackFrame** aSanitized)
|
||||
{
|
||||
NS_ADDREF(*aSanitized = this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* [noscript] readonly attribute nsIStackFrame sanitized */
|
||||
NS_IMETHODIMP JSStackFrame::GetSanitized(JSContext* aCx, nsIStackFrame** aSanitized)
|
||||
{
|
||||
// NB: Do _not_ enter the compartment of the SavedFrame object here, because
|
||||
// we are checking against the caller's compartment's principals in
|
||||
// GetFirstSubsumedSavedFrame.
|
||||
|
||||
JS::RootedObject savedFrame(aCx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
|
||||
savedFrame = js::GetFirstSubsumedSavedFrame(aCx, savedFrame);
|
||||
nsCOMPtr<nsIStackFrame> stackFrame;
|
||||
if (savedFrame) {
|
||||
stackFrame = new JSStackFrame(savedFrame);
|
||||
} else {
|
||||
stackFrame = new StackFrame();
|
||||
}
|
||||
|
||||
stackFrame.forget(aSanitized);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* readonly attribute nsIStackFrame caller; */
|
||||
NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller)
|
||||
{
|
||||
|
@ -967,9 +967,30 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
|
||||
maxFrameCount = d;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> stack(cx);
|
||||
JSCompartment *targetCompartment = cx->compartment();
|
||||
if (args.length() >= 2) {
|
||||
if (!args[1].isObject()) {
|
||||
js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, args[0], JS::NullPtr(),
|
||||
"not an object", NULL);
|
||||
return false;
|
||||
}
|
||||
RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject()));
|
||||
if (!obj)
|
||||
return false;
|
||||
targetCompartment = obj->compartment();
|
||||
}
|
||||
|
||||
RootedObject stack(cx);
|
||||
{
|
||||
AutoCompartment ac(cx, targetCompartment);
|
||||
if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stack && !cx->compartment()->wrap(cx, &stack))
|
||||
return false;
|
||||
|
||||
args.rval().setObjectOrNull(stack);
|
||||
return true;
|
||||
}
|
||||
@ -2396,13 +2417,15 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
|
||||
" SavedStacks cache."),
|
||||
|
||||
JS_FN_HELP("saveStack", SaveStack, 0, 0,
|
||||
"saveStack()",
|
||||
" Capture a stack.\n"),
|
||||
"saveStack([maxDepth [, compartment]])",
|
||||
" Capture a stack. If 'maxDepth' is given, capture at most 'maxDepth' number\n"
|
||||
" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
|
||||
" with the given object's compartment."),
|
||||
|
||||
JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
|
||||
"enableTrackAllocations()",
|
||||
" Start capturing the JS stack at every allocation. Note that this sets an "
|
||||
" object metadata callback that will override any other object metadata "
|
||||
" Start capturing the JS stack at every allocation. Note that this sets an\n"
|
||||
" object metadata callback that will override any other object metadata\n"
|
||||
" callback that may be set."),
|
||||
|
||||
JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,
|
||||
|
@ -5,6 +5,44 @@ JavaScript call stack at a past moment of execution. Younger frames hold a
|
||||
reference to the frames that invoked them. The older tails are shared across
|
||||
many younger frames.
|
||||
|
||||
`SavedFrame` stacks should generally be captured, allocated, and live within the
|
||||
compartment that is being observed or debugged. Usually this is a content
|
||||
compartment.
|
||||
|
||||
## Capturing `SavedFrame` Stacks
|
||||
|
||||
### From C++
|
||||
|
||||
Use `JS::CaptureCurrentStack` declared in `jsapi.h`.
|
||||
|
||||
### From JS
|
||||
|
||||
Use `saveStack`, accessible via `Components.utils.getJSTestingFunction()`.
|
||||
|
||||
## Including and Excluding Chrome Frames
|
||||
|
||||
Consider the following `SavedFrame` stack. Arrows represent links from child to
|
||||
parent frame, `content.js` is from a compartment with content principals, and
|
||||
`chrome.js` is from a compartment with chrome principals.
|
||||
|
||||
function A from content.js
|
||||
|
|
||||
V
|
||||
function B from chrome.js
|
||||
|
|
||||
V
|
||||
function C from content.js
|
||||
|
||||
The content compartment will ever have one view of this stack: `A -> C`.
|
||||
|
||||
However, a chrome compartment has a choice: it can either take the same view
|
||||
that the content compartment has (`A -> C`), or it can view all stack frames,
|
||||
including the frames from chrome compartments (`A -> B -> C`). To view
|
||||
everything, use an `XrayWrapper`. This is the default wrapper. To see the stack
|
||||
as the content compartment sees it, waive the xray wrapper with
|
||||
`Components.utils.waiveXrays`:
|
||||
|
||||
const contentViewOfStack = Components.utils.waiveXrays(someStack);
|
||||
|
||||
## Accessor Properties of the `SavedFrame.prototype` Object
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace js {
|
||||
class PropertyName;
|
||||
class NativeObject;
|
||||
class ArrayObject;
|
||||
class GlobalObject;
|
||||
class PlainObject;
|
||||
class ScriptSourceObject;
|
||||
class Shape;
|
||||
@ -45,6 +46,7 @@ typedef JS::Rooted<JSAtom*> RootedAtom;
|
||||
typedef JS::Rooted<JSLinearString*> RootedLinearString;
|
||||
typedef JS::Rooted<PropertyName*> RootedPropertyName;
|
||||
typedef JS::Rooted<ArrayObject*> RootedArrayObject;
|
||||
typedef JS::Rooted<GlobalObject*> RootedGlobalObject;
|
||||
typedef JS::Rooted<PlainObject*> RootedPlainObject;
|
||||
typedef JS::Rooted<ScriptSourceObject*> RootedScriptSource;
|
||||
|
||||
|
@ -42,7 +42,7 @@ var count = 0;
|
||||
low .eval('function b() { check("b", extract(saveStack())); c(); }');
|
||||
mid .eval('function c() { check("cba", extract(saveStack())); d(); }');
|
||||
high.eval('function d() { check("dcba", extract(saveStack())); e(); }');
|
||||
eval('function e() { check("edcba", extract(saveStack())); f(); }'); // no principal, so checks skipped
|
||||
eval('function e() { check("ecba", extract(saveStack())); f(); }');
|
||||
low .eval('function f() { check("fb", extract(saveStack())); g(); }');
|
||||
mid .eval('function g() { check("gfecba", extract(saveStack())); h(); }');
|
||||
high.eval('function h() { check("hgfedcba", extract(saveStack())); }');
|
||||
|
@ -33,7 +33,7 @@ var high = newGlobal({ principal: 0xfffff });
|
||||
low .eval('function b() { check("b", saveStack().toString()); c(); }');
|
||||
mid .eval('function c() { check("cba", saveStack().toString()); d(); }');
|
||||
high.eval('function d() { check("dcba", saveStack().toString()); e(); }');
|
||||
eval('function e() { check("edcba", saveStack().toString()); f(); }'); // no principal, so checks skipped
|
||||
eval('function e() { check("ecba", saveStack().toString()); f(); }');
|
||||
low .eval('function f() { check("fb", saveStack().toString()); g(); }');
|
||||
mid .eval('function g() { check("gfecba", saveStack().toString()); h(); }');
|
||||
high.eval('function h() { check("hgfedcba", saveStack().toString()); }');
|
||||
|
23
js/src/jit-test/tests/saved-stacks/principals-03.js
Normal file
23
js/src/jit-test/tests/saved-stacks/principals-03.js
Normal file
@ -0,0 +1,23 @@
|
||||
// With arrows representing child-to-parent links, create a SavedFrame stack
|
||||
// like this:
|
||||
//
|
||||
// high.a -> low.b
|
||||
//
|
||||
// in `low`'s compartment and give `low` a reference to this stack. Assert the
|
||||
// stack's youngest frame's properties doesn't leak information about `high.a`
|
||||
// that `low` shouldn't have access to, and instead returns information about
|
||||
// `low.b`.
|
||||
|
||||
var low = newGlobal({ principal: 0 });
|
||||
var high = newGlobal({ principal: 0xfffff });
|
||||
|
||||
low.high = high;
|
||||
high.low = low;
|
||||
|
||||
high.eval("function a() { return saveStack(0, low); }");
|
||||
low.eval("function b() { return high.a(); }")
|
||||
|
||||
var stack = low.b();
|
||||
|
||||
assertEq(stack.functionDisplayName, "b");
|
||||
assertEq(stack.parent, null);
|
15
js/src/jit-test/tests/saved-stacks/principals-04.js
Normal file
15
js/src/jit-test/tests/saved-stacks/principals-04.js
Normal file
@ -0,0 +1,15 @@
|
||||
// Test what happens when a compartment gets a SavedFrame that it doesn't have
|
||||
// the principals to access any of its frames.
|
||||
|
||||
var low = newGlobal({ principal: 0 });
|
||||
var high = newGlobal({ principal: 0xfffff });
|
||||
|
||||
low.high = high;
|
||||
high.low = low;
|
||||
|
||||
high.eval("function a() { return saveStack(1, low); }");
|
||||
var stack = low.eval("high.a();")
|
||||
|
||||
assertEq(stack.functionDisplayName, null);
|
||||
assertEq(stack.parent, null);
|
||||
assertEq(stack.toString(), "");
|
@ -2622,6 +2622,17 @@ GetObjectEnvironmentObjectForFunction(JSFunction *fun);
|
||||
extern JS_FRIEND_API(JSPrincipals *)
|
||||
GetSavedFramePrincipals(JS::HandleObject savedFrame);
|
||||
|
||||
/*
|
||||
* Get the first SavedFrame object in this SavedFrame stack whose principals are
|
||||
* subsumed by the cx's principals. If there is no such frame, return nullptr.
|
||||
*
|
||||
* Do NOT pass a non-SavedFrame object here.
|
||||
*
|
||||
* The savedFrame and cx do not need to be in the same compartment.
|
||||
*/
|
||||
extern JS_FRIEND_API(JSObject *)
|
||||
GetFirstSubsumedSavedFrame(JSContext *cx, JS::HandleObject savedFrame);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
extern JS_FRIEND_API(bool)
|
||||
|
@ -111,6 +111,8 @@ IF_SAB(real,imaginary)(SharedFloat64Array, 50, js_InitViaClassSpec,
|
||||
IF_SAB(real,imaginary)(SharedUint8ClampedArray, 51, js_InitViaClassSpec, SHARED_TYPED_ARRAY_CLASP(Uint8Clamped)) \
|
||||
real(TypedArray, 52, js_InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
|
||||
IF_SAB(real,imaginary)(Atomics, 53, js_InitAtomicsClass, OCLASP(Atomics)) \
|
||||
real(SavedFrame, 54, js_InitViaClassSpec, &js::SavedFrame::class_) \
|
||||
|
||||
|
||||
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
|
||||
|
||||
|
@ -60,8 +60,6 @@ using mozilla::PodCopy;
|
||||
using mozilla::PodZero;
|
||||
using mozilla::RotateLeft;
|
||||
|
||||
typedef Rooted<GlobalObject *> RootedGlobalObject;
|
||||
|
||||
/* static */ BindingIter
|
||||
Bindings::argumentsBinding(ExclusiveContext *cx, InternalBindingsHandle bindings)
|
||||
{
|
||||
|
@ -321,7 +321,7 @@ class GlobalObject : public NativeObject
|
||||
NativeObject *getOrCreateObjectPrototype(JSContext *cx) {
|
||||
if (functionObjectClassesInitialized())
|
||||
return &getPrototype(JSProto_Object).toObject().as<NativeObject>();
|
||||
Rooted<GlobalObject*> self(cx, this);
|
||||
RootedGlobalObject self(cx, this);
|
||||
if (!ensureConstructor(cx, self, JSProto_Object))
|
||||
return nullptr;
|
||||
return &self->getPrototype(JSProto_Object).toObject().as<NativeObject>();
|
||||
@ -330,7 +330,7 @@ class GlobalObject : public NativeObject
|
||||
NativeObject *getOrCreateFunctionPrototype(JSContext *cx) {
|
||||
if (functionObjectClassesInitialized())
|
||||
return &getPrototype(JSProto_Function).toObject().as<NativeObject>();
|
||||
Rooted<GlobalObject*> self(cx, this);
|
||||
RootedGlobalObject self(cx, this);
|
||||
if (!ensureConstructor(cx, self, JSProto_Object))
|
||||
return nullptr;
|
||||
return &self->getPrototype(JSProto_Function).toObject().as<NativeObject>();
|
||||
@ -384,6 +384,13 @@ class GlobalObject : public NativeObject
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static NativeObject *getOrCreateSavedFramePrototype(JSContext *cx,
|
||||
Handle<GlobalObject*> global) {
|
||||
if (!ensureConstructor(cx, global, JSProto_SavedFrame))
|
||||
return nullptr;
|
||||
return &global->getPrototype(JSProto_SavedFrame).toObject().as<NativeObject>();
|
||||
}
|
||||
|
||||
static JSObject *getOrCreateArrayBufferPrototype(JSContext *cx, Handle<GlobalObject*> global) {
|
||||
if (!ensureConstructor(cx, global, JSProto_ArrayBuffer))
|
||||
return nullptr;
|
||||
@ -483,7 +490,7 @@ class GlobalObject : public NativeObject
|
||||
Value v = getSlotRef(slot);
|
||||
if (v.isObject())
|
||||
return &v.toObject();
|
||||
Rooted<GlobalObject*> self(cx, this);
|
||||
RootedGlobalObject self(cx, this);
|
||||
if (!init(cx, self))
|
||||
return nullptr;
|
||||
return &self->getSlot(slot).toObject();
|
||||
@ -561,7 +568,7 @@ class GlobalObject : public NativeObject
|
||||
}
|
||||
|
||||
JSObject *getOrCreateDataViewPrototype(JSContext *cx) {
|
||||
Rooted<GlobalObject*> self(cx, this);
|
||||
RootedGlobalObject self(cx, this);
|
||||
if (!ensureConstructor(cx, self, JSProto_DataView))
|
||||
return nullptr;
|
||||
return &self->getPrototype(JSProto_DataView).toObject();
|
||||
|
@ -16,12 +16,13 @@
|
||||
#include "jshashutil.h"
|
||||
#include "jsmath.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsscript.h"
|
||||
#include "prmjtime.h"
|
||||
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/StringBuffer.h"
|
||||
|
||||
#include "jscntxtinlines.h"
|
||||
@ -136,10 +137,22 @@ SavedFrame::HashPolicy::rekey(Key &key, const Key &newKey)
|
||||
key = newKey;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
SavedFrame::finishSavedFrameInit(JSContext *cx, HandleObject ctor, HandleObject proto)
|
||||
{
|
||||
// The only object with the SavedFrame::class_ that doesn't have a source
|
||||
// should be the prototype.
|
||||
proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
|
||||
|
||||
return FreezeObject(cx, proto);
|
||||
}
|
||||
|
||||
/* static */ const Class SavedFrame::class_ = {
|
||||
"SavedFrame",
|
||||
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT),
|
||||
JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) |
|
||||
JSCLASS_IS_ANONYMOUS,
|
||||
nullptr, // addProperty
|
||||
nullptr, // delProperty
|
||||
nullptr, // getProperty
|
||||
@ -147,7 +160,44 @@ SavedFrame::HashPolicy::rekey(Key &key, const Key &newKey)
|
||||
nullptr, // enumerate
|
||||
nullptr, // resolve
|
||||
nullptr, // convert
|
||||
SavedFrame::finalize
|
||||
SavedFrame::finalize, // finalize
|
||||
nullptr, // call
|
||||
nullptr, // hasInstance
|
||||
nullptr, // construct
|
||||
nullptr, // trace
|
||||
|
||||
// ClassSpec
|
||||
{
|
||||
GenericCreateConstructor<SavedFrame::construct, 0, JSFunction::FinalizeKind>,
|
||||
GenericCreatePrototype,
|
||||
SavedFrame::staticFunctions,
|
||||
SavedFrame::protoFunctions,
|
||||
SavedFrame::protoAccessors,
|
||||
SavedFrame::finishSavedFrameInit,
|
||||
ClassSpec::DontDefineConstructor
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ const JSFunctionSpec
|
||||
SavedFrame::staticFunctions[] = {
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
/* static */ const JSFunctionSpec
|
||||
SavedFrame::protoFunctions[] = {
|
||||
JS_FN("constructor", SavedFrame::construct, 0, 0),
|
||||
JS_FN("toString", SavedFrame::toStringMethod, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
/* static */ const JSPropertySpec
|
||||
SavedFrame::protoAccessors[] = {
|
||||
JS_PSG("source", SavedFrame::sourceProperty, 0),
|
||||
JS_PSG("line", SavedFrame::lineProperty, 0),
|
||||
JS_PSG("column", SavedFrame::columnProperty, 0),
|
||||
JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0),
|
||||
JS_PSG("parent", SavedFrame::parentProperty, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
/* static */ void
|
||||
@ -259,33 +309,67 @@ SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */ SavedFrame *
|
||||
SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName)
|
||||
// Return the first SavedFrame in the chain that starts with |frame| whose
|
||||
// principals are subsumed by |principals|, according to |subsumes|. If there is
|
||||
// no such frame, return nullptr.
|
||||
static SavedFrame *
|
||||
GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame)
|
||||
{
|
||||
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
|
||||
if (!subsumes)
|
||||
return frame;
|
||||
|
||||
JSPrincipals *principals = cx->compartment()->principals;
|
||||
|
||||
RootedSavedFrame rootedFrame(cx, frame);
|
||||
while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals()))
|
||||
rootedFrame = rootedFrame->getParent();
|
||||
|
||||
return rootedFrame;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(JSObject *)
|
||||
GetFirstSubsumedSavedFrame(JSContext *cx, HandleObject savedFrame)
|
||||
{
|
||||
if (!savedFrame)
|
||||
return nullptr;
|
||||
RootedSavedFrame frame(cx, &savedFrame->as<SavedFrame>());
|
||||
return GetFirstSubsumedFrame(cx, frame);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName,
|
||||
MutableHandleSavedFrame frame)
|
||||
{
|
||||
const Value &thisValue = args.thisv();
|
||||
|
||||
if (!thisValue.isObject()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
JSObject &thisObject = thisValue.toObject();
|
||||
if (!thisObject.is<SavedFrame>()) {
|
||||
JSObject *thisObject = CheckedUnwrap(&thisValue.toObject());
|
||||
if (!thisObject || !thisObject->is<SavedFrame>()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
|
||||
SavedFrame::class_.name, fnName, thisObject.getClass()->name);
|
||||
return nullptr;
|
||||
SavedFrame::class_.name, fnName,
|
||||
thisObject ? thisObject->getClass()->name : "object");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for SavedFrame.prototype, which has the same class as SavedFrame
|
||||
// instances, however doesn't actually represent a captured stack frame. It
|
||||
// is the only object that is<SavedFrame>() but doesn't have a source.
|
||||
if (thisObject.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull()) {
|
||||
if (thisObject->as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
|
||||
SavedFrame::class_.name, fnName, "prototype object");
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return &thisObject.as<SavedFrame>();
|
||||
// The caller might not have the principals to see this frame's data, so get
|
||||
// the first one they _do_ have access to.
|
||||
RootedSavedFrame rooted(cx, &thisObject->as<SavedFrame>());
|
||||
frame.set(GetFirstSubsumedFrame(cx, rooted));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the SavedFrame * from the current this value and handle any errors that
|
||||
@ -296,19 +380,24 @@ SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName)
|
||||
// - unsigned argc
|
||||
// - Value *vp
|
||||
// - const char *fnName
|
||||
// - Value defaultVal
|
||||
// These parameters will be defined after calling this macro:
|
||||
// - CallArgs args
|
||||
// - Rooted<SavedFrame *> frame (will be non-null)
|
||||
#define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \
|
||||
#define THIS_SAVEDFRAME(cx, argc, vp, fnName, defaultVal, args, frame) \
|
||||
CallArgs args = CallArgsFromVp(argc, vp); \
|
||||
RootedSavedFrame frame(cx, checkThis(cx, args, fnName)); \
|
||||
if (!frame) \
|
||||
return false
|
||||
RootedSavedFrame frame(cx); \
|
||||
if (!checkThis(cx, args, fnName, &frame)) \
|
||||
return false; \
|
||||
if (!frame) { \
|
||||
args.rval().set(defaultVal); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get source)", NullValue(), args, frame);
|
||||
args.rval().setString(frame->getSource());
|
||||
return true;
|
||||
}
|
||||
@ -316,7 +405,7 @@ SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
/* static */ bool
|
||||
SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get line)", NullValue(), args, frame);
|
||||
uint32_t line = frame->getLine();
|
||||
args.rval().setNumber(line);
|
||||
return true;
|
||||
@ -325,7 +414,7 @@ SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
/* static */ bool
|
||||
SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get column)", NullValue(), args, frame);
|
||||
uint32_t column = frame->getColumn();
|
||||
args.rval().setNumber(column);
|
||||
return true;
|
||||
@ -334,7 +423,7 @@ SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
/* static */ bool
|
||||
SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", NullValue(), args, frame);
|
||||
RootedAtom name(cx, frame->getFunctionDisplayName());
|
||||
if (name)
|
||||
args.rval().setString(name);
|
||||
@ -346,42 +435,27 @@ SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
/* static */ bool
|
||||
SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame);
|
||||
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
|
||||
JSPrincipals *principals = cx->compartment()->principals;
|
||||
|
||||
do
|
||||
frame = frame->getParent();
|
||||
while (frame && principals && subsumes &&
|
||||
!subsumes(principals, frame->getPrincipals()));
|
||||
|
||||
args.rval().setObjectOrNull(frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", NullValue(), args, frame);
|
||||
RootedSavedFrame parent(cx, frame->getParent());
|
||||
args.rval().setObjectOrNull(GetFirstSubsumedFrame(cx, parent));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ const JSPropertySpec SavedFrame::properties[] = {
|
||||
JS_PSG("source", SavedFrame::sourceProperty, 0),
|
||||
JS_PSG("line", SavedFrame::lineProperty, 0),
|
||||
JS_PSG("column", SavedFrame::columnProperty, 0),
|
||||
JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0),
|
||||
JS_PSG("parent", SavedFrame::parentProperty, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame);
|
||||
THIS_SAVEDFRAME(cx, argc, vp, "toString", StringValue(cx->runtime()->emptyString), args, frame);
|
||||
StringBuffer sb(cx);
|
||||
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
|
||||
JSPrincipals *principals = cx->compartment()->principals;
|
||||
DebugOnly<JSSubsumesOp> subsumes = cx->runtime()->securityCallbacks->subsumes;
|
||||
DebugOnly<JSPrincipals *> principals = cx->compartment()->principals;
|
||||
|
||||
RootedSavedFrame parent(cx);
|
||||
do {
|
||||
if (principals && subsumes && !subsumes(principals, frame->getPrincipals()))
|
||||
continue;
|
||||
MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals()));
|
||||
if (frame->isSelfHosted())
|
||||
continue;
|
||||
goto nextIteration;
|
||||
|
||||
{
|
||||
RootedAtom name(cx, frame->getFunctionDisplayName());
|
||||
if ((name && !sb.append(name))
|
||||
|| !sb.append('@')
|
||||
@ -394,7 +468,12 @@ SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} while ((frame = frame->getParent()));
|
||||
}
|
||||
|
||||
nextIteration:
|
||||
parent = frame->getParent();
|
||||
frame = GetFirstSubsumedFrame(cx, parent);
|
||||
} while (frame);
|
||||
|
||||
JSString *str = sb.finishString();
|
||||
if (!str)
|
||||
@ -403,12 +482,6 @@ SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ const JSFunctionSpec SavedFrame::methods[] = {
|
||||
JS_FN("constructor", SavedFrame::construct, 0, 0),
|
||||
JS_FN("toString", SavedFrame::toStringMethod, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
bool
|
||||
SavedStacks::init()
|
||||
{
|
||||
@ -460,12 +533,6 @@ SavedStacks::sweep(JSRuntime *rt)
|
||||
}
|
||||
|
||||
sweepPCLocationMap();
|
||||
|
||||
if (savedFrameProto.unbarrieredGet() &&
|
||||
IsObjectAboutToBeFinalizedFromAnyThread(savedFrameProto.unsafeGet()))
|
||||
{
|
||||
savedFrameProto.set(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -587,49 +654,17 @@ SavedStacks::getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup looku
|
||||
return frame;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
SavedStacks::getOrCreateSavedFramePrototype(JSContext *cx)
|
||||
{
|
||||
if (savedFrameProto)
|
||||
return savedFrameProto;
|
||||
|
||||
Rooted<GlobalObject *> global(cx, cx->compartment()->maybeGlobal());
|
||||
if (!global)
|
||||
return nullptr;
|
||||
|
||||
Rooted<SavedFrame *> proto(cx,
|
||||
NewObjectWithGivenProto<SavedFrame>(cx, global->getOrCreateObjectPrototype(cx), global));
|
||||
if (!proto
|
||||
|| !JS_DefineProperties(cx, proto, SavedFrame::properties)
|
||||
|| !JS_DefineFunctions(cx, proto, SavedFrame::methods)
|
||||
|| !FreezeObject(cx, proto))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The only object with the SavedFrame::class_ that doesn't have a source
|
||||
// should be the prototype.
|
||||
proto->setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
|
||||
|
||||
savedFrameProto.set(proto);
|
||||
return savedFrameProto;
|
||||
}
|
||||
|
||||
SavedFrame *
|
||||
SavedStacks::createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup)
|
||||
{
|
||||
RootedObject proto(cx, getOrCreateSavedFramePrototype(cx));
|
||||
RootedGlobalObject global(cx, cx->global());
|
||||
assertSameCompartment(cx, global);
|
||||
|
||||
RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
|
||||
assertSameCompartment(cx, proto);
|
||||
|
||||
RootedObject global(cx, cx->compartment()->maybeGlobal());
|
||||
if (!global)
|
||||
return nullptr;
|
||||
|
||||
assertSameCompartment(cx, global);
|
||||
|
||||
RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto, global));
|
||||
if (!frameObj)
|
||||
return nullptr;
|
||||
|
@ -14,16 +14,22 @@
|
||||
|
||||
namespace js {
|
||||
|
||||
class SavedFrame;
|
||||
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
|
||||
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
|
||||
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
|
||||
|
||||
class SavedFrame : public NativeObject {
|
||||
friend class SavedStacks;
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
static void finalize(FreeOp *fop, JSObject *obj);
|
||||
static const JSPropertySpec protoAccessors[];
|
||||
static const JSFunctionSpec protoFunctions[];
|
||||
static const JSFunctionSpec staticFunctions[];
|
||||
|
||||
// Prototype methods and properties to be exposed to JS.
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
static bool construct(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool sourceProperty(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool lineProperty(JSContext *cx, unsigned argc, Value *vp);
|
||||
@ -53,6 +59,7 @@ class SavedFrame : public NativeObject {
|
||||
class HandleLookup;
|
||||
|
||||
private:
|
||||
static bool finishSavedFrameInit(JSContext *cx, HandleObject ctor, HandleObject proto);
|
||||
void initFromLookup(HandleLookup lookup);
|
||||
|
||||
enum {
|
||||
@ -80,13 +87,10 @@ class SavedFrame : public NativeObject {
|
||||
bool parentMoved();
|
||||
void updatePrivateParent();
|
||||
|
||||
static SavedFrame *checkThis(JSContext *cx, CallArgs &args, const char *fnName);
|
||||
static bool checkThis(JSContext *cx, CallArgs &args, const char *fnName,
|
||||
MutableHandleSavedFrame frame);
|
||||
};
|
||||
|
||||
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
|
||||
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
|
||||
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
|
||||
|
||||
struct SavedFrame::HashPolicy
|
||||
{
|
||||
typedef SavedFrame::Lookup Lookup;
|
||||
@ -106,7 +110,6 @@ class SavedStacks {
|
||||
public:
|
||||
SavedStacks()
|
||||
: frames(),
|
||||
savedFrameProto(nullptr),
|
||||
allocationSamplingProbability(1.0),
|
||||
allocationSkipCount(0),
|
||||
// XXX: Initialize the RNG state to 0 so that random_initSeed is lazily
|
||||
@ -129,7 +132,6 @@ class SavedStacks {
|
||||
|
||||
private:
|
||||
SavedFrame::Set frames;
|
||||
ReadBarrieredObject savedFrameProto;
|
||||
double allocationSamplingProbability;
|
||||
uint32_t allocationSkipCount;
|
||||
uint64_t rngState;
|
||||
@ -137,9 +139,6 @@ class SavedStacks {
|
||||
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
|
||||
unsigned maxFrameCount = 0);
|
||||
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
|
||||
// |SavedFrame.prototype| is created lazily and held weakly. It should only
|
||||
// be accessed through this method.
|
||||
JSObject *getOrCreateSavedFramePrototype(JSContext *cx);
|
||||
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
|
||||
void chooseSamplingProbability(JSContext* cx);
|
||||
|
||||
|
@ -507,7 +507,7 @@ public:
|
||||
// Mapping of often used strings to jsid atoms that live 'forever'.
|
||||
//
|
||||
// To add a new string: add to this list and to XPCJSRuntime::mStrings
|
||||
// at the top of xpcjsruntime.cpp
|
||||
// at the top of XPCJSRuntime.cpp
|
||||
enum {
|
||||
IDX_CONSTRUCTOR = 0 ,
|
||||
IDX_TO_STRING ,
|
||||
|
108
js/xpconnect/tests/unit/test_xray_SavedFrame.js
Normal file
108
js/xpconnect/tests/unit/test_xray_SavedFrame.js
Normal file
@ -0,0 +1,108 @@
|
||||
// Bug 1117242: Test calling SavedFrame getters from globals that don't subsume
|
||||
// that frame's principals.
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
|
||||
const lowP = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
|
||||
const midP = [lowP, "http://other.com"];
|
||||
const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
|
||||
|
||||
const low = new Cu.Sandbox(lowP);
|
||||
const mid = new Cu.Sandbox(midP);
|
||||
const high = new Cu.Sandbox(highP);
|
||||
|
||||
function run_test() {
|
||||
// Test that the priveleged view of a SavedFrame from a subsumed compartment
|
||||
// is the same view that the subsumed compartment gets. Create the following
|
||||
// chain of function calls (with some intermediate system-principaled frames
|
||||
// due to implementation):
|
||||
//
|
||||
// low.lowF -> mid.midF -> high.highF -> high.saveStack
|
||||
//
|
||||
// Where high.saveStack gets monkey patched to create stacks in each of our
|
||||
// sandboxes.
|
||||
|
||||
Cu.evalInSandbox("function highF() { return saveStack(); }", high);
|
||||
|
||||
mid.highF = () => high.highF();
|
||||
Cu.evalInSandbox("function midF() { return highF(); }", mid);
|
||||
|
||||
low.midF = () => mid.midF();
|
||||
Cu.evalInSandbox("function lowF() { return midF(); }", low);
|
||||
|
||||
const expected = [
|
||||
{
|
||||
sandbox: low,
|
||||
frames: ["lowF"],
|
||||
},
|
||||
{
|
||||
sandbox: mid,
|
||||
frames: ["midF", "lowF"],
|
||||
},
|
||||
{
|
||||
sandbox: high,
|
||||
frames: ["getSavedFrameInstanceFromSandbox",
|
||||
"saveStack",
|
||||
"highF",
|
||||
"run_test/mid.highF",
|
||||
"midF",
|
||||
"run_test/low.midF",
|
||||
"lowF",
|
||||
"run_test",
|
||||
"_execute_test",
|
||||
null],
|
||||
}
|
||||
];
|
||||
|
||||
for (let { sandbox, frames } of expected) {
|
||||
high.saveStack = function saveStack() {
|
||||
return getSavedFrameInstanceFromSandbox(sandbox);
|
||||
};
|
||||
|
||||
const xrayStack = low.lowF();
|
||||
equal(xrayStack.functionDisplayName, "getSavedFrameInstanceFromSandbox",
|
||||
"Xrays should always be able to see everything.");
|
||||
|
||||
let waived = Cu.waiveXrays(xrayStack);
|
||||
do {
|
||||
ok(frames.length,
|
||||
"There should still be more expected frames while we have actual frames.");
|
||||
equal(waived.functionDisplayName, frames.shift(),
|
||||
"The waived wrapper should give us the stack's compartment's view.");
|
||||
waived = waived.parent;
|
||||
} while (waived);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a SavedFrame instance from inside the given sandbox.
|
||||
//
|
||||
// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't
|
||||
// available to sandboxes that don't have the system principal. The easiest way
|
||||
// to get the SavedFrame is to use the Debugger API to track allocation sites
|
||||
// and then do an allocation.
|
||||
function getSavedFrameInstanceFromSandbox(sandbox) {
|
||||
const dbg = new Debugger(sandbox);
|
||||
|
||||
dbg.memory.trackingAllocationSites = true;
|
||||
Cu.evalInSandbox("new Object", sandbox);
|
||||
const allocs = dbg.memory.drainAllocationsLog();
|
||||
dbg.memory.trackingAllocationSites = false;
|
||||
|
||||
ok(allocs[0], "We should observe the allocation");
|
||||
const { frame } = allocs[0];
|
||||
|
||||
if (sandbox !== high) {
|
||||
ok(Cu.isXrayWrapper(frame), "`frame` should be an xray...");
|
||||
equal(Object.prototype.toString.call(Cu.waiveXrays(frame)),
|
||||
"[object SavedFrame]",
|
||||
"...and that xray should wrap a SavedFrame");
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
@ -108,3 +108,4 @@ head = head_watchdog.js
|
||||
head = head_watchdog.js
|
||||
[test_writeToGlobalPrototype.js]
|
||||
[test_xrayed_iterator.js]
|
||||
[test_xray_SavedFrame.js]
|
@ -83,6 +83,7 @@ IsJSXraySupported(JSProtoKey key)
|
||||
case JSProto_Array:
|
||||
case JSProto_Function:
|
||||
case JSProto_TypedArray:
|
||||
case JSProto_SavedFrame:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -26,6 +26,10 @@ interface nsIStackFrame : nsISupports
|
||||
readonly attribute AUTF8String sourceLine;
|
||||
readonly attribute nsIStackFrame caller;
|
||||
|
||||
// Returns the first frame whose principals are subsumed by the caller's
|
||||
// principals.
|
||||
[noscript, implicit_jscontext] readonly attribute nsIStackFrame sanitized;
|
||||
|
||||
// Returns a formatted stack string that looks like the sort of
|
||||
// string that would be returned by .stack on JS Error objects.
|
||||
// Only works on JS-language stack frames.
|
||||
|
Loading…
Reference in New Issue
Block a user