Bug 916893 - Patch 1 - Notification on workers. r=khuey,wchen

Does not implement the Service Worker API - https://notifications.spec.whatwg.org/#service-worker-api
***
Folded:
Bug 916893 - Better ownership model. r=khuey
Fix for bug found by ASan where we were touching the NotificationFeature after releasing it.
This commit is contained in:
Nikhil Marathe 2015-06-25 11:36:53 -07:00
parent 7c43b1ac8d
commit 5aa559655d
16 changed files with 1449 additions and 205 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,9 @@
#define mozilla_dom_notification_h__
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/NotificationBinding.h"
#include "mozilla/dom/workers/bindings/WorkerFeature.h"
#include "nsIObserver.h"
@ -21,16 +23,92 @@ class nsIVariant;
namespace mozilla {
namespace dom {
class NotificationObserver;
class NotificationRef;
class WorkerNotificationObserver;
class Promise;
namespace workers {
class WorkerPrivate;
} // namespace workers
class Notification;
class NotificationFeature final : public workers::WorkerFeature
{
// Held alive by Notification itself before feature is added, and only
// released after feature is removed.
Notification* mNotification;
public:
explicit NotificationFeature(Notification* aNotification);
bool
Notify(JSContext* aCx, workers::Status aStatus) override;
};
/*
* Notifications on workers introduce some liveness issues. The property we
* are trying to satisfy is:
* Whenever a task is dispatched to the main thread to operate on
* a Notification, the Notification should be addrefed on the worker thread
* and a feature should be added to observe the worker lifetime. This main
* thread owner should ensure it properly releases the reference to the
* Notification, additionally removing the feature if necessary.
*
* To enforce the correct addref and release, along with managing the feature,
* we introduce a NotificationRef. Only one object may ever own
* a NotificationRef, so UniquePtr<> is used throughout. The NotificationRef
* constructor calls AddRefObject(). When it is destroyed (on any thread) it
* releases the Notification on the correct thread.
*
* Code should only access the underlying Notification object when it can
* guarantee that it retains ownership of the NotificationRef while doing so.
*
* The one kink in this mechanism is that the worker feature may be Notify()ed
* if the worker stops running script, even if the Notification's corresponding
* UI is still visible to the user. We handle this case with the following
* steps:
* a) Close the notification. This is done by blocking the worker on the main
* thread.
* b) Ask the observer to let go of its NotificationRef's underlying
* Notification without proper cleanup since the feature will handle the
* release. This is only OK because every notification has only one
* associated observer. The NotificationRef itself is still owned by the
* observer and deleted by the UniquePtr, but it doesn't do anything since
* the underlying Notification is null.
*
* To unify code-paths, we use the same NotificationRef in the main
* thread implementation too.
*
* Note that the Notification's JS wrapper does it's standard
* AddRef()/Release() and is not affected by any of this.
*
* There is one case related to the WorkerNotificationObserver having to
* dispatch WorkerRunnables to the worker thread which will use the
* Notification object. We can end up in a situation where an event runnable is
* dispatched to the worker, gets queued in the worker's event queue, but then,
* the worker yields to the main thread. Here the main thread observer is
* destroyed, which frees its NotificationRef. The NotificationRef dispatches
* a ControlRunnable to the worker, which runs before the event runnable,
* leading to the event runnable possibly not having a valid Notification
* reference.
* We solve this problem by having WorkerNotificationObserver's dtor
* dispatching a standard WorkerRunnable to do the release (this guarantees the
* ordering of the release is after the event runnables). All WorkerRunnables
* that get dispatched successfully are guaranteed to run on the worker before
* it shuts down. If that dispatch fails, the standard ControlRunnable based
* shutdown is acceptable since the already dispatched event runnables have
* already run or canceled (the worker is already past Running).
*
*/
class Notification : public DOMEventTargetHelper
{
friend class CloseNotificationRunnable;
friend class NotificationTask;
friend class NotificationPermissionRequest;
friend class NotificationObserver;
friend class NotificationStorageCallback;
friend class WorkerNotificationObserver;
public:
IMPL_EVENT_HANDLER(click)
@ -41,6 +119,10 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Notification, DOMEventTargetHelper)
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
// Returns if Notification.get() is allowed for the current global.
static bool IsGetEnabled(JSContext* aCx, JSObject* aObj);
static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOption,
@ -91,6 +173,8 @@ public:
nsIStructuredCloneContainer* GetDataCloneContainer();
static bool RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */);
static void RequestPermission(const GlobalObject& aGlobal,
const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
ErrorResult& aRv);
@ -117,13 +201,36 @@ public:
void InitFromBase64(JSContext* aCx, const nsAString& aData, ErrorResult& aRv);
void AssertIsOnTargetThread() const
{
MOZ_ASSERT(IsTargetThread());
}
workers::WorkerPrivate* mWorkerPrivate;
// Main thread only.
WorkerNotificationObserver* mObserver;
// The NotificationTask calls ShowInternal()/CloseInternal() on the
// Notification. At this point the task has ownership of the Notification. It
// passes this on to the Notification itself via mTempRef so that
// ShowInternal()/CloseInternal() may pass it along appropriately (or release
// it).
UniquePtr<NotificationRef> mTempRef;
void AddRefObject();
void ReleaseObject();
static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
ErrorResult& rv);
bool DispatchClickEvent();
protected:
Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl,
const NotificationBehavior& aBehavior, nsPIDOMWindow* aWindow);
const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal);
static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal,
const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions);
@ -158,27 +265,29 @@ protected:
}
static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
nsresult GetOriginWorker(nsString& aOrigin);
void GetAlertName(nsAString& aRetval)
{
aRetval = mAlertName;
}
nsString mID;
nsString mTitle;
nsString mBody;
NotificationDirection mDir;
nsString mLang;
nsString mTag;
nsString mIconUrl;
const nsString mID;
const nsString mTitle;
const nsString mBody;
const NotificationDirection mDir;
const nsString mLang;
const nsString mTag;
const nsString mIconUrl;
nsCOMPtr<nsIStructuredCloneContainer> mDataObjectContainer;
NotificationBehavior mBehavior;
const NotificationBehavior mBehavior;
// It's null until GetData is first called
nsCOMPtr<nsIVariant> mData;
nsString mAlertName;
// Main thread only.
bool mIsClosed;
// We need to make a distinction between the notification being closed i.e.
@ -193,6 +302,22 @@ private:
virtual ~Notification();
nsIPrincipal* GetPrincipal();
nsresult PersistNotification();
void UnpersistNotification();
bool IsTargetThread() const
{
return NS_IsMainThread() == !mWorkerPrivate;
}
void RegisterFeature();
void UnregisterFeature();
nsresult ResolveIconAndSoundURL(nsString&, nsString&);
UniquePtr<NotificationFeature> mFeature;
uint32_t mTaskCount;
};
} // namespace dom

View File

@ -33,6 +33,7 @@ FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
'/dom/ipc',
'/dom/workers',
]
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']

View File

@ -11,17 +11,18 @@
* related or neighboring rights to this work.
*/
[Pref="dom.webnotifications.enabled",
Constructor(DOMString title, optional NotificationOptions options),
[Constructor(DOMString title, optional NotificationOptions options),
Exposed=(Window,Worker),
Func="mozilla::dom::Notification::PrefEnabled",
UnsafeInPrerendering]
interface Notification : EventTarget {
[GetterThrows]
static readonly attribute NotificationPermission permission;
[Throws]
[Throws, Func="mozilla::dom::Notification::RequestPermissionEnabledForScope"]
static void requestPermission(optional NotificationPermissionCallback permissionCallback);
[Throws]
[Throws, Func="mozilla::dom::Notification::IsGetEnabled"]
static Promise<sequence<Notification>> get(optional GetNotificationOptions filter);
attribute EventHandler onclick;

View File

@ -158,6 +158,7 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
#endif
#define PREF_DOM_CACHES_ENABLED "dom.caches.enabled"
#define PREF_DOM_WORKERNOTIFICATION_ENABLED "dom.webnotifications.workers.enabled"
#define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
#define PREF_INTL_ACCEPT_LANGUAGES "intl.accept_languages"
#define PREF_SERVICEWORKERS_ENABLED "dom.serviceWorkers.enabled"
@ -1902,6 +1903,10 @@ RuntimeService::Init()
WorkerPrefChanged,
PREF_DOM_CACHES_ENABLED,
reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
NS_FAILED(Preferences::RegisterCallbackAndCall(
WorkerPrefChanged,
PREF_DOM_WORKERNOTIFICATION_ENABLED,
reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
NS_FAILED(Preferences::RegisterCallbackAndCall(
WorkerPrefChanged,
PREF_SERVICEWORKERS_ENABLED,
@ -2117,6 +2122,10 @@ RuntimeService::Cleanup()
WorkerPrefChanged,
PREF_DOM_CACHES_ENABLED,
reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
NS_FAILED(Preferences::UnregisterCallback(
WorkerPrefChanged,
PREF_DOM_WORKERNOTIFICATION_ENABLED,
reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
#if DUMP_CONTROLLED_BY_PREF
NS_FAILED(Preferences::UnregisterCallback(
WorkerPrefChanged,
@ -2651,16 +2660,17 @@ RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure)
#ifdef DUMP_CONTROLLED_BY_PREF
if (key == WORKERPREF_DUMP) {
key = WORKERPREF_DUMP;
sDefaultPreferences[WORKERPREF_DUMP] =
sDefaultPreferences[key] =
Preferences::GetBool(PREF_DOM_WINDOW_DUMP_ENABLED, false);
}
#endif
if (key == WORKERPREF_DOM_CACHES) {
key = WORKERPREF_DOM_CACHES;
sDefaultPreferences[WORKERPREF_DOM_CACHES] =
Preferences::GetBool(PREF_DOM_CACHES_ENABLED, false);
} else if (key == WORKERPREF_DOM_WORKERNOTIFICATION) {
sDefaultPreferences[key] =
Preferences::GetBool(PREF_DOM_WORKERNOTIFICATION_ENABLED, false);
} else if (key == WORKERPREF_SERVICEWORKERS) {
key = WORKERPREF_SERVICEWORKERS;
sDefaultPreferences[WORKERPREF_SERVICEWORKERS] =
@ -2670,9 +2680,6 @@ RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure)
sDefaultPreferences[key] =
Preferences::GetBool(PREF_INTERCEPTION_ENABLED, false);
}
// This function should never be registered as a callback for a preference it
// does not handle.
MOZ_ASSERT(key != WORKERPREF_COUNT);
RuntimeService* rts = RuntimeService::GetService();
if (rts) {

View File

@ -1245,6 +1245,13 @@ public:
AssertIsOnWorkerThread();
return mPreferences[WORKERPREF_INTERCEPTION_ENABLED];
}
bool
DOMWorkerNotificationEnabled() const
{
AssertIsOnWorkerThread();
return mPreferences[WORKERPREF_DOM_WORKERNOTIFICATION];
}
bool
OnLine() const

View File

@ -199,6 +199,7 @@ enum WorkerPreference
WORKERPREF_DOM_CACHES, // dom.caches.enabled
WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled
WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled
WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled
WORKERPREF_COUNT
};

View File

@ -41,6 +41,10 @@ support-files =
navigator_languages_worker.js
navigator_worker.js
newError_worker.js
notification_worker.js
notification_worker_child-child.js
notification_worker_child-parent.js
notification_permission_worker.js
onLine_worker.js
onLine_worker_child.js
onLine_worker_head.js
@ -158,6 +162,9 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure
[test_navigator_languages.html]
skip-if = buildapp == 'mulet'
[test_newError.html]
[test_notification.html]
[test_notification_child.html]
[test_notification_permission.html]
[test_onLine.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only failure
[test_performance_user_timing.html]

View File

@ -0,0 +1,56 @@
function info(message) {
dump("INFO: " + message + "\n");
}
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
function is(a, b, message) {
postMessage({ type: 'is', test1: a, test2: b, message: message });
}
if (self.Notification) {
var steps = [
function (done) {
info("Test notification permission");
ok(typeof Notification === "function", "Notification constructor exists");
ok(Notification.permission === "denied", "Notification.permission is denied.");
var n = new Notification("Hello");
n.onerror = function(e) {
ok(true, "error called due to permission denied.");
done();
}
},
];
onmessage = function(e) {
var context = {};
(function executeRemainingTests(remainingTests) {
if (!remainingTests.length) {
postMessage({type: 'finish'});
return;
}
var nextTest = remainingTests.shift();
var finishTest = executeRemainingTests.bind(null, remainingTests);
var startTest = nextTest.call.bind(nextTest, context, finishTest);
try {
startTest();
// if no callback was defined for test function,
// we must manually invoke finish to continue
if (nextTest.length === 0) {
finishTest();
}
} catch (e) {
ok(false, "Test threw exception! " + nextTest + " " + e);
finishTest();
}
})(steps);
}
} else {
ok(true, "Notifications are not enabled in workers on the platform.");
postMessage({ type: 'finish' });
}

View File

@ -0,0 +1,89 @@
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
function is(a, b, message) {
postMessage({ type: 'is', test1: a, test2: b, message: message });
}
if (self.Notification) {
var steps = [
function () {
ok(typeof Notification === "function", "Notification constructor exists");
ok(Notification.permission, "Notification.permission exists");
ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist");
},
function (done) {
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
ok(notification !== undefined, "Notification exists");
is(notification.onclick, null, "onclick() should be null");
is(notification.onshow, null, "onshow() should be null");
is(notification.onerror, null, "onerror() should be null");
is(notification.onclose, null, "onclose() should be null");
is(typeof notification.close, "function", "close() should exist");
is(notification.dir, options.dir, "auto should get set");
is(notification.lang, options.lang, "lang should get set");
is(notification.body, options.body, "body should get set");
is(notification.tag, options.tag, "tag should get set");
is(notification.icon, options.icon, "icon should get set");
// store notification in test context
this.notification = notification;
notification.onshow = function () {
ok(true, "onshow handler should be called");
done();
};
},
function (done) {
var notification = this.notification;
notification.onclose = function () {
ok(true, "onclose handler should be called");
done();
};
notification.close();
},
];
onmessage = function(e) {
var context = {};
(function executeRemainingTests(remainingTests) {
if (!remainingTests.length) {
postMessage({type: 'finish'});
return;
}
var nextTest = remainingTests.shift();
var finishTest = executeRemainingTests.bind(null, remainingTests);
var startTest = nextTest.call.bind(nextTest, context, finishTest);
try {
startTest();
// if no callback was defined for test function,
// we must manually invoke finish to continue
if (nextTest.length === 0) {
finishTest();
}
} catch (e) {
ok(false, "Test threw exception! " + nextTest + " " + e);
finishTest();
}
})(steps);
}
} else {
ok(true, "Notifications are not enabled in workers on the platform.");
postMessage({ type: 'finish' });
}

View File

@ -0,0 +1,92 @@
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
function is(a, b, message) {
postMessage({ type: 'is', test1: a, test2: b, message: message });
}
if (self.Notification) {
var steps = [
function () {
ok(typeof Notification === "function", "Notification constructor exists");
ok(Notification.permission, "Notification.permission exists");
ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist");
//ok(typeof Notification.get === "function", "Notification.get exists");
},
function (done) {
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
ok(notification !== undefined, "Notification exists");
is(notification.onclick, null, "onclick() should be null");
is(notification.onshow, null, "onshow() should be null");
is(notification.onerror, null, "onerror() should be null");
is(notification.onclose, null, "onclose() should be null");
is(typeof notification.close, "function", "close() should exist");
is(notification.dir, options.dir, "auto should get set");
is(notification.lang, options.lang, "lang should get set");
is(notification.body, options.body, "body should get set");
is(notification.tag, options.tag, "tag should get set");
is(notification.icon, options.icon, "icon should get set");
// store notification in test context
this.notification = notification;
notification.onshow = function () {
ok(true, "onshow handler should be called");
done();
};
},
function (done) {
var notification = this.notification;
notification.onclose = function () {
ok(true, "onclose handler should be called");
done();
};
notification.close();
},
];
onmessage = function(e) {
var context = {};
(function executeRemainingTests(remainingTests) {
if (!remainingTests.length) {
postMessage({type: 'finish'});
return;
}
var nextTest = remainingTests.shift();
var finishTest = executeRemainingTests.bind(null, remainingTests);
var startTest = nextTest.call.bind(nextTest, context, finishTest);
try {
startTest();
// if no callback was defined for test function,
// we must manually invoke finish to continue
if (nextTest.length === 0) {
finishTest();
}
} catch (e) {
ok(false, "Test threw exception! " + nextTest + " " + e);
finishTest();
}
})(steps);
}
} else {
ok(true, "Notifications are not enabled in workers on the platform.");
postMessage({ type: 'finish' });
}

View File

@ -0,0 +1,26 @@
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
function is(a, b, message) {
postMessage({ type: 'is', test1: a, test2: b, message: message });
}
if (self.Notification) {
var child = new Worker("notification_worker_child-child.js");
child.onerror = function(e) {
ok(false, "Error loading child worker " + e);
postMessage({ type: 'finish' });
}
child.onmessage = function(e) {
postMessage(e.data);
}
onmessage = function(e) {
child.postMessage('start');
}
} else {
ok(true, "Notifications are not enabled in workers on the platform.");
postMessage({ type: 'finish' });
}

View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=916893
-->
<head>
<title>Bug 916893</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="text/javascript">
SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
function runTest() {
MockServices.register();
var w = new Worker("notification_worker.js");
w.onmessage = function(e) {
if (e.data.type === 'finish') {
MockServices.unregister();
SimpleTest.finish();
} else if (e.data.type === 'ok') {
ok(e.data.test, e.data.message);
} else if (e.data.type === 'is') {
is(e.data.test1, e.data.test2, e.data.message);
}
}
SimpleTest.waitForExplicitFinish();
// turn on testing pref (used by notification.cpp, and mock the alerts
SpecialPowers.setBoolPref("notification.prompt.testing", true);
w.postMessage('start')
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{"set": [["dom.webnotifications.workers.enabled", true]]},
runTest
);
</script>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=916893
-->
<head>
<title>Bug 916893 - Test Notifications in child workers.</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="text/javascript">
SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
function runTest() {
MockServices.register();
var w = new Worker("notification_worker_child-parent.js");
w.onmessage = function(e) {
if (e.data.type === 'finish') {
MockServices.unregister();
SimpleTest.finish();
} else if (e.data.type === 'ok') {
ok(e.data.test, e.data.message);
} else if (e.data.type === 'is') {
is(e.data.test1, e.data.test2, e.data.message);
}
}
SimpleTest.waitForExplicitFinish();
// turn on testing pref (used by notification.cpp, and mock the alerts
SpecialPowers.setBoolPref("notification.prompt.testing", true);
w.postMessage('start')
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{"set": [["dom.webnotifications.workers.enabled", true]]},
runTest
);
</script>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=916893
-->
<head>
<title>Bug 916893 - Make sure error is fired on Notification if permission is denied.</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="text/javascript">
SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
function runTest() {
MockServices.register();
var w = new Worker("notification_permission_worker.js");
w.onmessage = function(e) {
if (e.data.type === 'finish') {
SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
MockServices.unregister();
SimpleTest.finish();
} else if (e.data.type === 'ok') {
ok(e.data.test, e.data.message);
} else if (e.data.type === 'is') {
is(e.data.test1, e.data.test2, e.data.message);
}
}
SimpleTest.waitForExplicitFinish();
// turn on testing pref (used by notification.cpp, and mock the alerts
SpecialPowers.setBoolPref("notification.prompt.testing", true);
SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
w.postMessage('start')
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{"set": [["dom.webnotifications.workers.enabled", true]]},
runTest
);
</script>
</body>
</html>

View File

@ -4323,6 +4323,7 @@ pref("notification.feature.enabled", false);
// Web Notification
pref("dom.webnotifications.enabled", true);
pref("dom.webnotifications.workers.enabled", false);
// Alert animation effect, name is disableSlidingEffect for backwards-compat.
pref("alerts.disableSlidingEffect", false);