Bug 1057994 - DataStore should not dispatch runnables in a worker when it is shutting down, r=bent

--HG--
rename : dom/datastore/tests/file_basic_worker.html => dom/datastore/tests/file_worker_close.html
rename : dom/datastore/tests/test_basic_worker.html => dom/datastore/tests/test_worker_close.html
This commit is contained in:
Andrea Marchesini 2014-12-17 14:49:36 +00:00
parent 16b9c8af1b
commit 8e36934a09
10 changed files with 323 additions and 55 deletions

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore</title>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var messages = [];
var worker = new Worker("file_worker_close.js");
worker.onmessage = function(event) {
messages.push(event.data)
if (event.data == 'DONE') {
worker.terminate();
// Fire message to the test_basic_worker.html.
for (var i = 0; i < messages.length; i++) {
alert(messages[i]);
}
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
function is(a, b, msg) {
postMessage((a == b ? 'OK' : 'KO')+ ' ' + msg)
}
var store;
navigator.getDataStores('foo').then(function(stores) {
is(stores.length, 1, "getDataStores('foo') returns 1 element");
is(stores[0].name, 'foo', 'The dataStore.name is foo');
is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
store = stores[0];
postMessage('DONE');
});
onclose = function() {
for (var i = 0; i < 100; ++i) {
store.get(123);
}
}

View File

@ -32,6 +32,8 @@ support-files =
file_bug1008044.html
file_bug957086.html
file_notify_system_message.html
file_worker_close.html
file_worker_close.js
[test_app_install.html]
skip-if = toolkit == 'gonk' # embed-apps doesn't work in the mochitest app
@ -59,3 +61,4 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
[test_bug1058108.html]
[test_notify_system_message.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || toolkit == 'win' #bug 1053662 - Timeout prone
[test_worker_close.html]

View File

@ -0,0 +1,130 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore - Worker.onclose</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var gHostedManifestURL = 'http://test/tests/dom/datastore/tests/file_app.sjs?testToken=file_worker_close.html';
var gApp;
function cbError() {
ok(false, "Error callback invoked");
finish();
}
function installApp() {
var request = navigator.mozApps.install(gHostedManifestURL);
request.onerror = cbError;
request.onsuccess = function() {
gApp = request.result;
runTest();
}
}
function uninstallApp() {
// Uninstall the app.
var request = navigator.mozApps.mgmt.uninstall(gApp);
request.onerror = cbError;
request.onsuccess = function() {
// All done.
info("All done");
runTest();
}
}
function testApp() {
var ifr = document.createElement('iframe');
ifr.setAttribute('mozbrowser', 'true');
ifr.setAttribute('mozapp', gApp.manifestURL);
ifr.setAttribute('src', gApp.manifest.launch_path);
var domParent = document.getElementById('container');
// Set us up to listen for messages from the app.
var listener = function(e) {
var message = e.detail.message;
if (/^OK/.exec(message)) {
ok(true, "Message from app: " + message);
} else if (/KO/.exec(message)) {
ok(false, "Message from app: " + message);
} else if (/DONE/.exec(message)) {
ok(true, "Messaging from app complete");
ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
domParent.removeChild(ifr);
runTest();
}
}
// This event is triggered when the app calls "alert".
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
domParent.appendChild(ifr);
}
var tests = [
// Permissions
function() {
SpecialPowers.pushPermissions(
[{ "type": "browser", "allow": 1, "context": document },
{ "type": "embed-apps", "allow": 1, "context": document },
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
},
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3],
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
runTest();
},
// No confirmation needed when an app is installed
function() {
SpecialPowers.autoConfirmAppInstall(() =>
SpecialPowers.autoConfirmAppUninstall(runTest));
},
// Installing the app
installApp,
// Run tests in app
testApp,
// Uninstall the app
uninstallApp
];
function runTest() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>
</body>
</html>

View File

@ -214,8 +214,8 @@ public:
};
WorkerFetchResolver::WorkerFetchResolver(WorkerPrivate* aWorkerPrivate, Promise* aPromise)
: mPromiseProxy(new PromiseWorkerProxy(aWorkerPrivate, aPromise))
{
mPromiseProxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
}
WorkerFetchResolver::~WorkerFetchResolver()

View File

@ -1299,6 +1299,32 @@ private:
PromiseWorkerProxy::RunCallbackFunc mFunc;
};
/* static */
already_AddRefed<PromiseWorkerProxy>
PromiseWorkerProxy::Create(WorkerPrivate* aWorkerPrivate,
Promise* aWorkerPromise,
const JSStructuredCloneCallbacks* aCb)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPromise);
nsRefPtr<PromiseWorkerProxy> proxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise, aCb);
// We do this to make sure the worker thread won't shut down before the
// promise is resolved/rejected on the worker thread.
if (!aWorkerPrivate->AddFeature(aWorkerPrivate->GetJSContext(), proxy)) {
// Probably the worker is terminating. We cannot complete the operation
// and we have to release all the resources.
proxy->mCleanedUp = true;
proxy->mWorkerPromise = nullptr;
return nullptr;
}
return proxy.forget();
}
PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate* aWorkerPrivate,
Promise* aWorkerPromise,
const JSStructuredCloneCallbacks* aCallbacks)
@ -1308,16 +1334,6 @@ PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate* aWorkerPrivate,
, mCallbacks(aCallbacks)
, mCleanUpLock("cleanUpLock")
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(mWorkerPromise);
// We do this to make sure the worker thread won't shut down before the
// promise is resolved/rejected on the worker thread.
if (!mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), this)) {
MOZ_ASSERT(false, "cannot add the worker feature!");
return;
}
}
PromiseWorkerProxy::~PromiseWorkerProxy()
@ -1352,6 +1368,36 @@ PromiseWorkerProxy::StoreISupports(nsISupports* aSupports)
mSupportsArray.AppendElement(supports);
}
namespace {
class PromiseWorkerProxyControlRunnable MOZ_FINAL
: public WorkerControlRunnable
{
nsRefPtr<PromiseWorkerProxy> mProxy;
public:
PromiseWorkerProxyControlRunnable(WorkerPrivate* aWorkerPrivate,
PromiseWorkerProxy* aProxy)
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mProxy(aProxy)
{
MOZ_ASSERT(aProxy);
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
{
mProxy->CleanUp(aCx);
return true;
}
private:
~PromiseWorkerProxyControlRunnable()
{}
};
} // anonymous namespace
void
PromiseWorkerProxy::RunCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
@ -1380,7 +1426,11 @@ PromiseWorkerProxy::RunCallback(JSContext* aCx,
Move(buffer),
aFunc);
runnable->Dispatch(aCx);
if (!runnable->Dispatch(aCx)) {
nsRefPtr<WorkerControlRunnable> runnable =
new PromiseWorkerProxyControlRunnable(mWorkerPrivate, this);
mWorkerPrivate->DispatchControlRunnable(runnable);
}
}
void

View File

@ -65,9 +65,10 @@ class PromiseWorkerProxy : public PromiseNativeHandler,
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PromiseWorkerProxy)
public:
PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
Promise* aWorkerPromise,
const JSStructuredCloneCallbacks* aCallbacks = nullptr);
static already_AddRefed<PromiseWorkerProxy>
Create(workers::WorkerPrivate* aWorkerPrivate,
Promise* aWorkerPromise,
const JSStructuredCloneCallbacks* aCallbacks = nullptr);
workers::WorkerPrivate* GetWorkerPrivate() const;
@ -87,6 +88,10 @@ protected:
virtual bool Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE;
private:
PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
Promise* aWorkerPromise,
const JSStructuredCloneCallbacks* aCallbacks = nullptr);
virtual ~PromiseWorkerProxy();
// Function pointer for calling Promise::{ResolveInternal,RejectInternal}.

View File

@ -137,10 +137,40 @@ protected:
}
};
// A DataStoreRunnable to run DataStore::Get(...) on the main thread.
class DataStoreGetRunnable MOZ_FINAL : public DataStoreRunnable
class DataStoreProxyRunnable : public DataStoreRunnable
{
public:
DataStoreProxyRunnable(WorkerPrivate* aWorkerPrivate,
const nsMainThreadPtrHandle<DataStore>& aBackingStore,
Promise* aWorkerPromise)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseWorkerProxy =
PromiseWorkerProxy::Create(aWorkerPrivate, aWorkerPromise);
}
bool Dispatch(JSContext* aCx)
{
if (mPromiseWorkerProxy) {
return DataStoreRunnable::Dispatch(aCx);
}
// If the creation of mProxyWorkerProxy failed, the worker is terminating.
// In this case we don't want to dispatch the runnable and we should stop
// the promise chain here.
return true;
}
protected:
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
};
// A DataStoreRunnable to run DataStore::Get(...) on the main thread.
class DataStoreGetRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
Sequence<OwningStringOrUnsignedLong> mId;
ErrorResult& mRv;
@ -150,7 +180,7 @@ public:
Promise* aWorkerPromise,
const Sequence<OwningStringOrUnsignedLong>& aId,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mRv(aRv)
{
MOZ_ASSERT(aWorkerPrivate);
@ -159,9 +189,6 @@ public:
if (!mId.AppendElements(aId)) {
mRv.Throw(NS_ERROR_OUT_OF_MEMORY);
}
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:
@ -177,9 +204,8 @@ protected:
};
// A DataStoreRunnable to run DataStore::Put(...) on the main thread.
class DataStorePutRunnable MOZ_FINAL : public DataStoreRunnable
class DataStorePutRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
JSAutoStructuredCloneBuffer mObjBuffer;
const StringOrUnsignedLong& mId;
const nsString mRevisionId;
@ -194,7 +220,7 @@ public:
const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mId(aId)
, mRevisionId(aRevisionId)
, mRv(aRv)
@ -207,9 +233,6 @@ public:
JS_ClearPendingException(aCx);
mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
}
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:
@ -244,9 +267,8 @@ protected:
};
// A DataStoreRunnable to run DataStore::Add(...) on the main thread.
class DataStoreAddRunnable MOZ_FINAL : public DataStoreRunnable
class DataStoreAddRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
JSAutoStructuredCloneBuffer mObjBuffer;
const Optional<StringOrUnsignedLong>& mId;
const nsString mRevisionId;
@ -261,7 +283,7 @@ public:
const Optional<StringOrUnsignedLong>& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mId(aId)
, mRevisionId(aRevisionId)
, mRv(aRv)
@ -274,9 +296,6 @@ public:
JS_ClearPendingException(aCx);
mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
}
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:
@ -312,9 +331,8 @@ protected:
// A DataStoreRunnable to run DataStore::Remove(...) on the main
// thread.
class DataStoreRemoveRunnable MOZ_FINAL : public DataStoreRunnable
class DataStoreRemoveRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
const StringOrUnsignedLong& mId;
const nsString mRevisionId;
ErrorResult& mRv;
@ -326,16 +344,13 @@ public:
const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mId(aId)
, mRevisionId(aRevisionId)
, mRv(aRv)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:
@ -351,9 +366,8 @@ protected:
};
// A DataStoreRunnable to run DataStore::Clear(...) on the main thread.
class DataStoreClearRunnable MOZ_FINAL : public DataStoreRunnable
class DataStoreClearRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
const nsString mRevisionId;
ErrorResult& mRv;
@ -363,15 +377,12 @@ public:
Promise* aWorkerPromise,
const nsAString& aRevisionId,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mRevisionId(aRevisionId)
, mRv(aRv)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:
@ -626,9 +637,8 @@ WorkerDataStore::GetRevisionId(JSContext* aCx,
}
// A DataStoreRunnable to run DataStore::GetLength(...) on the main thread.
class DataStoreGetLengthRunnable MOZ_FINAL : public DataStoreRunnable
class DataStoreGetLengthRunnable MOZ_FINAL : public DataStoreProxyRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
ErrorResult& mRv;
public:
@ -636,14 +646,11 @@ public:
const nsMainThreadPtrHandle<DataStore>& aBackingStore,
Promise* aWorkerPromise,
ErrorResult& aRv)
: DataStoreRunnable(aWorkerPrivate, aBackingStore)
: DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
, mRv(aRv)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
}
protected:

View File

@ -82,7 +82,19 @@ public:
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise);
PromiseWorkerProxy::Create(aWorkerPrivate, aWorkerPromise);
}
bool Dispatch(JSContext* aCx)
{
if (mPromiseWorkerProxy) {
return DataStoreCursorRunnable::Dispatch(aCx);
}
// If the creation of mProxyWorkerProxy failed, the worker is terminating.
// In this case we don't want to dispatch the runnable and we should stop
// the promise chain here.
return true;
}
protected:

View File

@ -223,12 +223,26 @@ public:
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
// this might return null if the worker has started the close handler.
mPromiseWorkerProxy =
new PromiseWorkerProxy(aWorkerPrivate,
aWorkerPromise,
&kGetDataStoresStructuredCloneCallbacks);
PromiseWorkerProxy::Create(aWorkerPrivate,
aWorkerPromise,
&kGetDataStoresStructuredCloneCallbacks);
}
bool Dispatch(JSContext* aCx)
{
if (mPromiseWorkerProxy) {
return WorkerMainThreadRunnable::Dispatch(aCx);
}
// If the creation of mProxyWorkerProxy failed, the worker is terminating.
// In this case we don't want to dispatch the runnable and we should stop
// the promise chain here.
return true;
}
protected:
virtual bool
MainThreadRun() MOZ_OVERRIDE