Bug 1095660 part 5. Change nsJSUtils::EvaluateString to take an explicit scope chain. r=bholley

This commit is contained in:
Boris Zbarsky 2014-11-12 17:04:29 -05:00
parent d76620aecc
commit e25768cbf9
8 changed files with 115 additions and 46 deletions

View File

@ -166,7 +166,7 @@ nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
nsresult
nsJSUtils::EvaluateString(JSContext* aCx,
const nsAString& aScript,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions& aCompileOptions,
const EvaluateOptions& aEvaluateOptions,
JS::MutableHandle<JS::Value> aRetValue,
@ -175,14 +175,14 @@ nsJSUtils::EvaluateString(JSContext* aCx,
const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
JS::SourceBufferHolder::NoOwnership);
return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions,
return EvaluateString(aCx, srcBuf, aEvaluationGlobal, aCompileOptions,
aEvaluateOptions, aRetValue, aOffThreadToken);
}
nsresult
nsJSUtils::EvaluateString(JSContext* aCx,
JS::SourceBufferHolder& aSrcBuf,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions& aCompileOptions,
const EvaluateOptions& aEvaluateOptions,
JS::MutableHandle<JS::Value> aRetValue,
@ -197,6 +197,8 @@ nsJSUtils::EvaluateString(JSContext* aCx,
MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult);
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
MOZ_ASSERT(aSrcBuf.get());
MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
aEvaluationGlobal);
// Unfortunately, the JS engine actually compiles scripts with a return value
// in a different, less efficient way. Furthermore, it can't JIT them in many
@ -206,13 +208,11 @@ nsJSUtils::EvaluateString(JSContext* aCx,
// set to false.
aRetValue.setUndefined();
JS::ExposeObjectToActiveJS(aScopeObject);
nsAutoMicroTask mt;
nsresult rv = NS_OK;
bool ok = false;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK);
NS_ENSURE_TRUE(ssm->ScriptAllowed(aEvaluationGlobal), NS_OK);
mozilla::Maybe<AutoDontReportUncaught> dontReport;
if (!aEvaluateOptions.reportUncaught) {
@ -221,32 +221,45 @@ nsJSUtils::EvaluateString(JSContext* aCx,
dontReport.emplace(aCx);
}
bool ok = true;
// Scope the JSAutoCompartment so that we can later wrap the return value
// into the caller's cx.
{
JSAutoCompartment ac(aCx, aScopeObject);
JSAutoCompartment ac(aCx, aEvaluationGlobal);
JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject);
if (aOffThreadToken) {
// Now make sure to wrap the scope chain into the right compartment.
JS::AutoObjectVector scopeChain(aCx);
if (!scopeChain.reserve(aEvaluateOptions.scopeChain.length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (size_t i = 0; i < aEvaluateOptions.scopeChain.length(); ++i) {
JS::ExposeObjectToActiveJS(aEvaluateOptions.scopeChain[i]);
scopeChain.infallibleAppend(aEvaluateOptions.scopeChain[i]);
if (!JS_WrapObject(aCx, scopeChain[i])) {
ok = false;
break;
}
}
if (ok && aOffThreadToken) {
JS::Rooted<JSScript*>
script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (script) {
if (aEvaluateOptions.needResult) {
ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue);
ok = JS_ExecuteScript(aCx, scopeChain, script, aRetValue);
} else {
ok = JS_ExecuteScript(aCx, rootedScope, script);
ok = JS_ExecuteScript(aCx, scopeChain, script);
}
} else {
ok = false;
}
} else {
} else if (ok) {
if (aEvaluateOptions.needResult) {
ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
aSrcBuf, aRetValue);
ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf, aRetValue);
} else {
ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
aSrcBuf);
ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf);
}
}
@ -290,28 +303,28 @@ nsJSUtils::EvaluateString(JSContext* aCx,
nsresult
nsJSUtils::EvaluateString(JSContext* aCx,
const nsAString& aScript,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions& aCompileOptions,
void **aOffThreadToken)
{
EvaluateOptions options;
EvaluateOptions options(aCx);
options.setNeedResult(false);
JS::RootedValue unused(aCx);
return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions,
return EvaluateString(aCx, aScript, aEvaluationGlobal, aCompileOptions,
options, &unused, aOffThreadToken);
}
nsresult
nsJSUtils::EvaluateString(JSContext* aCx,
JS::SourceBufferHolder& aSrcBuf,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions& aCompileOptions,
void **aOffThreadToken)
{
EvaluateOptions options;
EvaluateOptions options(aCx);
options.setNeedResult(false);
JS::RootedValue unused(aCx);
return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions,
return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions,
options, &unused, aOffThreadToken);
}

View File

@ -65,14 +65,17 @@ public:
const nsAString& aBody,
JSObject** aFunctionObject);
struct EvaluateOptions {
struct MOZ_STACK_CLASS EvaluateOptions {
bool coerceToString;
bool reportUncaught;
bool needResult;
JS::AutoObjectVector scopeChain;
explicit EvaluateOptions() : coerceToString(false)
, reportUncaught(true)
, needResult(true)
explicit EvaluateOptions(JSContext* cx)
: coerceToString(false)
, reportUncaught(true)
, needResult(true)
, scopeChain(cx)
{}
EvaluateOptions& setCoerceToString(bool aCoerce) {
@ -91,9 +94,12 @@ public:
}
};
// aEvaluationGlobal is the global to evaluate in. The return value
// will then be wrapped back into the compartment aCx is in when
// this function is called.
static nsresult EvaluateString(JSContext* aCx,
const nsAString& aScript,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions &aCompileOptions,
const EvaluateOptions& aEvaluateOptions,
JS::MutableHandle<JS::Value> aRetValue,
@ -101,7 +107,7 @@ public:
static nsresult EvaluateString(JSContext* aCx,
JS::SourceBufferHolder& aSrcBuf,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions &aCompileOptions,
const EvaluateOptions& aEvaluateOptions,
JS::MutableHandle<JS::Value> aRetValue,
@ -110,13 +116,13 @@ public:
static nsresult EvaluateString(JSContext* aCx,
const nsAString& aScript,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions &aCompileOptions,
void **aOffThreadToken = nullptr);
static nsresult EvaluateString(JSContext* aCx,
JS::SourceBufferHolder& aSrcBuf,
JS::Handle<JSObject*> aScopeObject,
JS::Handle<JSObject*> aEvaluationGlobal,
JS::CompileOptions &aCompileOptions,
void **aOffThreadToken = nullptr);

View File

@ -271,7 +271,7 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel,
JS::CompileOptions options(cx);
options.setFileAndLine(mURL.get(), 1)
.setVersion(JSVERSION_DEFAULT);
nsJSUtils::EvaluateOptions evalOptions;
nsJSUtils::EvaluateOptions evalOptions(cx);
evalOptions.setCoerceToString(true);
rv = nsJSUtils::EvaluateString(cx, NS_ConvertUTF8toUTF16(script),
globalJSObject, options, evalOptions, &v);

View File

@ -1553,7 +1553,12 @@ _evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result)
options.setFileAndLine(spec, 0)
.setVersion(JSVERSION_DEFAULT);
JS::Rooted<JS::Value> rval(cx);
nsJSUtils::EvaluateOptions evalOptions;
nsJSUtils::EvaluateOptions evalOptions(cx);
if (obj != js::GetGlobalForObjectCrossCompartment(obj) &&
!evalOptions.scopeChain.append(obj)) {
return false;
}
obj = js::GetGlobalForObjectCrossCompartment(obj);
nsresult rv = nsJSUtils::EvaluateString(cx, utf16script, obj, options,
evalOptions, &rval);

View File

@ -18,6 +18,7 @@
#include "nsXBLPrototypeBinding.h"
#include "mozilla/AddonPathService.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsGlobalWindow.h"
#include "xpcpublic.h"
@ -393,10 +394,6 @@ nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode,
nsAutoMicroTask mt;
// EvaluateString and JS_DefineUCProperty can both trigger GC, so
// protect |result| here.
nsresult rv;
nsAutoCString uriSpec;
aBindingDocURI->GetSpec(uriSpec);
@ -415,30 +412,34 @@ nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode,
JSAddonId* addonId = MapURIToAddonID(aBindingDocURI);
// First, enter the xbl scope, wrap the node, and use that as the scope for
// the evaluation.
Element* boundElement = nullptr;
nsresult rv = UNWRAP_OBJECT(Element, aBoundNode, boundElement);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// First, enter the xbl scope, build the element's scope chain, and use
// that as the scope chain for the evaluation.
JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, aBoundNode, addonId));
NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
JSAutoCompartment ac(cx, scopeObject);
JS::Rooted<JSObject*> wrappedNode(cx, aBoundNode);
if (!JS_WrapObject(cx, &wrappedNode))
return NS_ERROR_OUT_OF_MEMORY;
JS::Rooted<JS::Value> result(cx);
JS::CompileOptions options(cx);
options.setFileAndLine(uriSpec.get(), mLineNumber)
.setVersion(JSVERSION_LATEST);
nsJSUtils::EvaluateOptions evalOptions;
nsJSUtils::EvaluateOptions evalOptions(cx);
if (!nsJSUtils::GetScopeChainForElement(cx, boundElement,
evalOptions.scopeChain)) {
return NS_ERROR_OUT_OF_MEMORY;
}
rv = nsJSUtils::EvaluateString(cx, nsDependentString(mFieldText,
mFieldTextLength),
wrappedNode, options, evalOptions,
&result);
scopeObject, options, evalOptions, &result);
if (NS_FAILED(rv)) {
return rv;
}
// Now, enter the node's compartment, wrap the eval result, and define it on
// the bound node.
JSAutoCompartment ac2(cx, aBoundNode);

View File

@ -2,6 +2,7 @@
support-files =
file_bug944407.xml
file_bug950909.xml
file_fieldScopeChain.xml
[test_bug378518.xul]
[test_bug398135.xul]
@ -11,3 +12,4 @@ support-files =
[test_bug772966.xul]
[test_bug944407.xul]
[test_bug950909.xul]
[test_fieldScopeChain.html]

View File

@ -0,0 +1,8 @@
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="foo">
<implementation>
<field name="bar">baz</field>
</implementation>
</binding>
</bindings>

View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1095660
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug **/
SimpleTest.waitForExplicitFinish();
window.baz = 1;
document.baz = 2;
addLoadEvent(function() {
is(document.querySelector("pre").bar, 2,
"Should have document on field scope chain");
SimpleTest.finish();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1095660">Mozilla Bug </a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test" style="-moz-binding: url(file_fieldScopeChain.xml#foo)">
</pre>
</body>
</html>