Bug 1225470 Report a message to the console when a service worker waitUntil() is rejected. r=baku

This commit is contained in:
Ben Kelly 2015-11-19 13:15:17 -08:00
parent 5855a7439f
commit 60dc9e2371
4 changed files with 105 additions and 19 deletions

View File

@ -495,6 +495,7 @@ DOMInterfaces = {
'ExtendableEvent': {
'headerFile': 'mozilla/dom/ServiceWorkerEvents.h',
'nativeType': 'mozilla::dom::workers::ExtendableEvent',
'implicitJSContext': [ 'waitUntil' ],
},
'ExtendableMessageEvent': {

View File

@ -697,7 +697,9 @@ FetchEvent::RespondWith(JSContext* aCx, Promise& aArg, ErrorResult& aRv)
spec, line, column);
aArg.AppendNativeHandler(handler);
WaitUntil(aArg, aRv);
// Append directly to the lifecycle promises array. Don't call WaitUntil()
// because that will lead to double-reporting any errors.
mPromises.AppendElement(&aArg);
}
void
@ -739,6 +741,94 @@ FetchEvent::ReportCanceled()
NS_LITERAL_CSTRING("InterceptionCanceledWithURL"), &requestURL);
}
namespace {
class WaitUntilHandler final : public PromiseNativeHandler
{
WorkerPrivate* mWorkerPrivate;
const nsCString mScope;
nsCString mSourceSpec;
uint32_t mLine;
uint32_t mColumn;
nsString mRejectValue;
~WaitUntilHandler()
{
}
public:
NS_DECL_THREADSAFE_ISUPPORTS
WaitUntilHandler(WorkerPrivate* aWorkerPrivate, JSContext* aCx)
: mWorkerPrivate(aWorkerPrivate)
, mScope(mWorkerPrivate->WorkerName())
, mLine(0)
, mColumn(0)
{
mWorkerPrivate->AssertIsOnWorkerThread();
// Save the location of the waitUntil() call itself as a fallback
// in case the rejection value does not contain any location info.
nsJSUtils::GetCallingLocation(aCx, mSourceSpec, &mLine, &mColumn);
}
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
// do nothing, we are only here to report errors
}
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
mWorkerPrivate->AssertIsOnWorkerThread();
nsCString spec;
uint32_t line = 0;
uint32_t column = 0;
ExtractErrorValues(aCx, aValue, spec, &line, &column, mRejectValue);
// only use the extracted location if we found one
if (!spec.IsEmpty()) {
mSourceSpec = spec;
mLine = line;
mColumn = column;
}
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &WaitUntilHandler::ReportOnMainThread);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
NS_DispatchToMainThread(runnable.forget())));
}
void
ReportOnMainThread()
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
// TODO: Make the error message a localized string. (bug 1222720)
nsString message;
message.AppendLiteral("Service worker event waitUntil() was passed a "
"promise that rejected with '");
message.Append(mRejectValue);
message.AppendLiteral("'.");
// Note, there is a corner case where this won't report to the window
// that triggered the error. Consider a navigation fetch event that
// rejects waitUntil() without holding respondWith() open. In this case
// there is no controlling document yet, the window did call .register()
// because there is no documeny yet, and the navigation is no longer
// being intercepted.
swm->ReportToAllClients(mScope, message, NS_ConvertUTF8toUTF16(mSourceSpec),
EmptyString(), mLine, mColumn,
nsIScriptError::errorFlag);
}
};
NS_IMPL_ISUPPORTS0(WaitUntilHandler)
} // anonymous namespace
NS_IMPL_ADDREF_INHERITED(FetchEvent, ExtendableEvent)
NS_IMPL_RELEASE_INHERITED(FetchEvent, ExtendableEvent)
@ -753,7 +843,7 @@ ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
}
void
ExtendableEvent::WaitUntil(Promise& aPromise, ErrorResult& aRv)
ExtendableEvent::WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv)
{
MOZ_ASSERT(!NS_IsMainThread());
@ -762,6 +852,12 @@ ExtendableEvent::WaitUntil(Promise& aPromise, ErrorResult& aRv)
return;
}
// Append our handler to each waitUntil promise separately so we
// can record the location in script where waitUntil was called.
RefPtr<WaitUntilHandler> handler =
new WaitUntilHandler(GetCurrentThreadWorkerPrivate(), aCx);
aPromise.AppendNativeHandler(handler);
mPromises.AppendElement(&aPromise);
}

View File

@ -51,9 +51,9 @@ public:
class ExtendableEvent : public Event
{
protected:
nsTArray<RefPtr<Promise>> mPromises;
protected:
explicit ExtendableEvent(mozilla::dom::EventTarget* aOwner);
~ExtendableEvent() {}
@ -90,7 +90,7 @@ public:
}
void
WaitUntil(Promise& aPromise, ErrorResult& aRv);
WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv);
already_AddRefed<Promise>
GetPromise();

View File

@ -406,21 +406,10 @@ public:
NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
}
JS::Rooted<JSObject*> obj(aCx, workerPrivate->GlobalScope()->GetWrapper());
JS::ExposeValueToActiveJS(aValue);
js::ErrorReport report(aCx);
if (NS_WARN_IF(!report.init(aCx, aValue))) {
JS_ClearPendingException(aCx);
return;
}
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
xpcReport->Init(report.report(), report.message(),
/* aIsChrome = */ false, workerPrivate->WindowID());
RefPtr<AsyncErrorReporter> aer = new AsyncErrorReporter(xpcReport);
NS_DispatchToMainThread(aer);
// Note, all WaitUntil() rejections are reported to client consoles
// by the WaitUntilHandler in ServiceWorkerEvents. This ensures that
// errors in non-lifecycle events like FetchEvent and PushEvent are
// reported properly.
}
};