Bug 1251369. Use an AutoJSAPI that reports its own exceptions around the main runloop in workers. r=khuey

The silly leading ": " on the error messages is due to bug 1251518.
This commit is contained in:
Boris Zbarsky
2016-02-26 15:23:13 -05:00
parent eeb98bb29d
commit 1c2e6f2f8f
8 changed files with 57 additions and 20 deletions

View File

@@ -24,7 +24,7 @@
worker.onerror = function(error) { worker.onerror = function(error) {
var msg = error.message; var msg = error.message;
if (msg.match(/^: NetworkError/) || msg.match(/Failed to load script/)) { if (msg.match(/^: NetworkError/) || msg.match(/Failed to load worker script/)) {
// this means CSP blocked it // this means CSP blocked it
msg = "blocked"; msg = "blocked";
} }

View File

@@ -2636,8 +2636,10 @@ WorkerThreadPrimaryRunnable::Run()
JSAutoRequest ar(cx); JSAutoRequest ar(cx);
mWorkerPrivate->DoRunLoop(cx); mWorkerPrivate->DoRunLoop(cx);
// The AutoJSAPI in DoRunLoop should have reported any exceptions left
JS_ReportPendingException(cx); // on cx. Note that we still need the JSAutoRequest above because
// AutoJSAPI on workers does NOT enter a request!
MOZ_ASSERT(!JS_IsExceptionPending(cx));
} }
BackgroundChild::CloseForCurrentThread(); BackgroundChild::CloseForCurrentThread();

View File

@@ -1777,7 +1777,7 @@ ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
mScriptLoader.mRv.MightThrowJSException(); mScriptLoader.mRv.MightThrowJSException();
if (NS_FAILED(loadInfo.mLoadResult)) { if (NS_FAILED(loadInfo.mLoadResult)) {
scriptloader::ReportLoadError(aCx, mScriptLoader.mRv, scriptloader::ReportLoadError(aCx, mScriptLoader.mRv,
loadInfo.mLoadResult); loadInfo.mLoadResult, loadInfo.mURL);
// Top level scripts only! // Top level scripts only!
if (mIsWorkerScript) { if (mIsWorkerScript) {
aWorkerPrivate->MaybeDispatchLoadFailedRunnable(); aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
@@ -2019,37 +2019,56 @@ ChannelFromScriptURLWorkerThread(JSContext* aCx,
return getter->GetResult(); return getter->GetResult();
} }
void ReportLoadError(JSContext* aCx, ErrorResult& aRv, nsresult aLoadResult) void ReportLoadError(JSContext* aCx, ErrorResult& aRv, nsresult aLoadResult,
const nsAString& aScriptURL)
{ {
MOZ_ASSERT(!aRv.Failed()); MOZ_ASSERT(!aRv.Failed());
switch (aLoadResult) { switch (aLoadResult) {
case NS_ERROR_FILE_NOT_FOUND: case NS_ERROR_FILE_NOT_FOUND:
case NS_ERROR_NOT_AVAILABLE: case NS_ERROR_NOT_AVAILABLE:
aRv.Throw(NS_ERROR_DOM_NETWORK_ERR); aLoadResult = NS_ERROR_DOM_NETWORK_ERR;
break; break;
case NS_ERROR_MALFORMED_URI: case NS_ERROR_MALFORMED_URI:
aLoadResult = NS_ERROR_DOM_SYNTAX_ERR; aLoadResult = NS_ERROR_DOM_SYNTAX_ERR;
MOZ_FALLTHROUGH; break;
case NS_BINDING_ABORTED: case NS_BINDING_ABORTED:
// Note: we used to pretend like we didn't set an exception for // Note: we used to pretend like we didn't set an exception for
// NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The
// other callsite, in WorkerPrivate::Constructor, never passed in // other callsite, in WorkerPrivate::Constructor, never passed in
// NS_BINDING_ABORTED. So just throw it directly here. Consumers will // NS_BINDING_ABORTED. So just throw it directly here. Consumers will
// deal as needed. // deal as needed. But note that we do NOT want to ThrowDOMException()
MOZ_FALLTHROUGH; // for this case, because that will make it impossible for consumers to
// realize that our error was NS_BINDING_ABORTED.
aRv.Throw(aLoadResult);
return;
case NS_ERROR_DOM_SECURITY_ERR: case NS_ERROR_DOM_SECURITY_ERR:
case NS_ERROR_DOM_SYNTAX_ERR: case NS_ERROR_DOM_SYNTAX_ERR:
aRv.Throw(aLoadResult); break;
case NS_ERROR_DOM_BAD_URI:
// This is actually a security error.
aLoadResult = NS_ERROR_DOM_SECURITY_ERR;
break; break;
default: default:
// We _could_ probably just throw aLoadResult on aRv, but let's preserve // For lack of anything better, go ahead and throw a NetworkError here.
// the old behavior for now and throw this string as an Error instead. // We don't want to throw a JS exception, because for toplevel script
JS_ReportError(aCx, "Failed to load script (nsresult = 0x%x)", aLoadResult); // loads that would get squelched.
aRv.StealExceptionFromJSContext(aCx); aRv.ThrowDOMException(NS_ERROR_DOM_NETWORK_ERR,
nsPrintfCString("Failed to load worker script at %s (nsresult = 0x%x)",
NS_ConvertUTF16toUTF8(aScriptURL).get(),
aLoadResult));
return;
} }
aRv.ThrowDOMException(aLoadResult,
NS_LITERAL_CSTRING("Failed to load worker script at \"") +
NS_ConvertUTF16toUTF8(aScriptURL) +
NS_LITERAL_CSTRING("\""));
} }
void void

View File

@@ -47,7 +47,8 @@ ChannelFromScriptURLWorkerThread(JSContext* aCx,
const nsAString& aScriptURL, const nsAString& aScriptURL,
nsIChannel** aChannel); nsIChannel** aChannel);
void ReportLoadError(JSContext* aCx, ErrorResult& aRv, nsresult aLoadResult); void ReportLoadError(JSContext* aCx, ErrorResult& aRv, nsresult aLoadResult,
const nsAString& aScriptURL);
void LoadMainScript(JSContext* aCx, const nsAString& aScriptURL, void LoadMainScript(JSContext* aCx, const nsAString& aScriptURL,
WorkerScriptType aWorkerScriptType, WorkerScriptType aWorkerScriptType,

View File

@@ -4032,7 +4032,7 @@ WorkerPrivate::Constructor(JSContext* aCx,
aWorkerType, stackLoadInfo.ptr()); aWorkerType, stackLoadInfo.ptr());
aRv.MightThrowJSException(); aRv.MightThrowJSException();
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
scriptloader::ReportLoadError(aCx, aRv, rv); scriptloader::ReportLoadError(aCx, aRv, rv, aScriptURL);
return nullptr; return nullptr;
} }
@@ -4378,6 +4378,14 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
mStatus = Running; mStatus = Running;
} }
// Now that we've done that, we can go ahead and set up our AutoJSAPI. We
// can't before this point, because it can't find the right JSContext before
// then, since it gets it from our mJSContext.
AutoJSAPI jsapi;
jsapi.Init();
MOZ_ASSERT(jsapi.cx() == aCx);
jsapi.TakeOwnershipOfErrorReporting();
EnableMemoryReporter(); EnableMemoryReporter();
InitializeGCTimers(); InitializeGCTimers();

View File

@@ -25,7 +25,7 @@ Tests of DOM Worker Threads
worker.onerror = function(event) { worker.onerror = function(event) {
is(event.target, worker); is(event.target, worker);
is(event.message, ": NetworkError: A network error occurred."); is(event.message, ': NetworkError: Failed to load worker script at "nonexistent_worker.js"');
event.preventDefault(); event.preventDefault();
SimpleTest.finish(); SimpleTest.finish();
}; };

View File

@@ -25,7 +25,7 @@ function test(script) {
worker.onerror = function(event) { worker.onerror = function(event) {
is(event.target, worker); is(event.target, worker);
is(event.message, ": NetworkError: A network error occurred."); ok(event.message.startsWith(": NetworkError: Failed to load worker script"))
event.preventDefault(); event.preventDefault();
runTests(); runTests();
}; };

View File

@@ -13,6 +13,8 @@
<script class="testbody" type="text/javascript"> <script class="testbody" type="text/javascript">
"use strict"; "use strict";
var loadErrorMessage = ': SecurityError: Failed to load worker script at "about:blank"';
function nextTest() { function nextTest() {
(function(){ (function(){
function workerfunc() { function workerfunc() {
@@ -35,10 +37,14 @@ function nextTest() {
return; return;
} }
w.onmessage = function(e) { w.onmessage = function(e) {
is(e.data.indexOf('Error: Failed to load script'), 0, "Error: Failed to load script"); is(e.data, loadErrorMessage,
"Should catch the error when loading inner script");
if (++i < 2) callworker(i); if (++i < 2) callworker(i);
else SimpleTest.finish(); else SimpleTest.finish();
}; };
w.onerrror = function(e) {
ok(false, "Should not get any errors from this worker");
}
} }
callworker(0); callworker(0);
})(); })();
@@ -48,7 +54,8 @@ try {
var worker = new Worker("about:blank"); var worker = new Worker("about:blank");
worker.onerror = function(e) { worker.onerror = function(e) {
e.preventDefault(); e.preventDefault();
ok(true, "Shouldn't success!"); is(e.message, loadErrorMessage,
"Should get the right error from the toplevel script");
nextTest(); nextTest();
} }