Bug 925437 - Implement navigator.onLine on Workers. r=khuey sr=sicking

This commit is contained in:
Nikhil Marathe 2013-11-19 15:08:50 -08:00
parent 35ea1b2c2d
commit c3934ac5d1
16 changed files with 419 additions and 22 deletions

View File

@ -19,6 +19,8 @@ interface WorkerGlobalScope : EventTarget {
void close();
attribute OnErrorEventHandler onerror;
attribute EventHandler onoffline;
attribute EventHandler ononline;
// also has additional members in a partial interface
};

View File

@ -6,3 +6,4 @@ interface WorkerNavigator {
};
WorkerNavigator implements NavigatorID;
WorkerNavigator implements NavigatorOnLine;

View File

@ -17,17 +17,18 @@ NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerNavigator, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WorkerNavigator, Release)
/* static */ already_AddRefed<WorkerNavigator>
WorkerNavigator::Create()
WorkerNavigator::Create(bool aOnLine)
{
RuntimeService* rts = RuntimeService::GetService();
MOZ_ASSERT(rts);
const RuntimeService::NavigatorStrings& strings =
rts->GetNavigatorStrings();
const RuntimeService::NavigatorProperties& properties =
rts->GetNavigatorProperties();
nsRefPtr<WorkerNavigator> navigator =
new WorkerNavigator(strings.mAppName, strings.mAppVersion,
strings.mPlatform, strings.mUserAgent);
new WorkerNavigator(properties.mAppName, properties.mAppVersion,
properties.mPlatform, properties.mUserAgent,
aOnLine);
return navigator.forget();
}

View File

@ -18,15 +18,18 @@ class WorkerNavigator MOZ_FINAL : public nsWrapperCache
nsString mAppVersion;
nsString mPlatform;
nsString mUserAgent;
bool mOnline;
WorkerNavigator(const nsAString& aAppName,
const nsAString& aAppVersion,
const nsAString& aPlatform,
const nsAString& aUserAgent)
const nsAString& aUserAgent,
bool aOnline)
: mAppName(aAppName)
, mAppVersion(aAppVersion)
, mPlatform(aPlatform)
, mUserAgent(aUserAgent)
, mOnline(aOnline)
{
MOZ_COUNT_CTOR(WorkerNavigator);
SetIsDOMBinding();
@ -38,7 +41,7 @@ public:
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WorkerNavigator)
static already_AddRefed<WorkerNavigator>
Create();
Create(bool aOnLine);
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
@ -60,10 +63,12 @@ public:
{
aAppName = mAppName;
}
void GetAppVersion(nsString& aAppVersion) const
{
aAppVersion = mAppVersion;
}
void GetPlatform(nsString& aPlatform) const
{
aPlatform = mPlatform;
@ -76,10 +81,22 @@ public:
{
return false;
}
void GetUserAgent(nsString& aUserAgent) const
{
aUserAgent = mUserAgent;
}
bool OnLine() const
{
return mOnline;
}
// Worker thread only!
void SetOnLine(bool aOnline)
{
mOnline = aOnline;
}
};
END_WORKERS_NAMESPACE

View File

@ -1223,7 +1223,7 @@ bool RuntimeService::sDefaultPreferences[WORKERPREF_COUNT] = { false };
RuntimeService::RuntimeService()
: mMutex("RuntimeService::mMutex"), mObserved(false),
mShuttingDown(false), mNavigatorStringsLoaded(false)
mShuttingDown(false), mNavigatorPropertiesLoaded(false)
{
AssertIsOnMainThread();
NS_ASSERTION(!gRuntimeService, "More than one service!");
@ -1352,17 +1352,17 @@ RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
}
}
else {
if (!mNavigatorStringsLoaded) {
NS_GetNavigatorAppName(mNavigatorStrings.mAppName);
if (NS_FAILED(NS_GetNavigatorAppVersion(mNavigatorStrings.mAppVersion)) ||
NS_FAILED(NS_GetNavigatorPlatform(mNavigatorStrings.mPlatform)) ||
NS_FAILED(NS_GetNavigatorUserAgent(mNavigatorStrings.mUserAgent))) {
if (!mNavigatorPropertiesLoaded) {
NS_GetNavigatorAppName(mNavigatorProperties.mAppName);
if (NS_FAILED(NS_GetNavigatorAppVersion(mNavigatorProperties.mAppVersion)) ||
NS_FAILED(NS_GetNavigatorPlatform(mNavigatorProperties.mPlatform)) ||
NS_FAILED(NS_GetNavigatorUserAgent(mNavigatorProperties.mUserAgent))) {
JS_ReportError(aCx, "Failed to load navigator strings!");
UnregisterWorker(aCx, aWorkerPrivate);
return false;
}
mNavigatorStringsLoaded = true;
mNavigatorPropertiesLoaded = true;
}
nsPIDOMWindow* window = aWorkerPrivate->GetWindow();
@ -1660,6 +1660,10 @@ RuntimeService::Init()
NS_WARNING("Failed to register for memory pressure notifications!");
}
if (NS_FAILED(obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) {
NS_WARNING("Failed to register for offline notification event!");
}
NS_ASSERTION(!gRuntimeServiceDuringInit, "This should be null!");
gRuntimeServiceDuringInit = this;
@ -1911,6 +1915,10 @@ RuntimeService::Cleanup()
NS_WARNING("Failed to unregister for memory pressure notifications!");
}
if (NS_FAILED(obs->RemoveObserver(this,
NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) {
NS_WARNING("Failed to unregister for offline notification event!");
}
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
mObserved = false;
@ -2288,6 +2296,12 @@ RuntimeService::CycleCollectAllWorkers()
BROADCAST_ALL_WORKERS(CycleCollect, /* dummy = */ false);
}
void
RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline)
{
BROADCAST_ALL_WORKERS(OfflineStatusChangeEvent, aIsOffline);
}
// nsISupports
NS_IMPL_ISUPPORTS1(RuntimeService, nsIObserver)
@ -2319,6 +2333,10 @@ RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
CycleCollectAllWorkers();
return NS_OK;
}
if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
return NS_OK;
}
NS_NOTREACHED("Unknown observer topic!");
return NS_OK;

View File

@ -100,7 +100,7 @@ private:
static bool sDefaultPreferences[WORKERPREF_COUNT];
public:
struct NavigatorStrings
struct NavigatorProperties
{
nsString mAppName;
nsString mAppVersion;
@ -109,12 +109,12 @@ public:
};
private:
NavigatorStrings mNavigatorStrings;
NavigatorProperties mNavigatorProperties;
// True when the observer service holds a reference to this object.
bool mObserved;
bool mShuttingDown;
bool mNavigatorStringsLoaded;
bool mNavigatorPropertiesLoaded;
public:
NS_DECL_ISUPPORTS
@ -150,10 +150,10 @@ public:
void
ForgetSharedWorker(WorkerPrivate* aWorkerPrivate);
const NavigatorStrings&
GetNavigatorStrings() const
const NavigatorProperties&
GetNavigatorProperties() const
{
return mNavigatorStrings;
return mNavigatorProperties;
}
void
@ -239,6 +239,9 @@ public:
void
CycleCollectAllWorkers();
void
SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline);
private:
RuntimeService();
~RuntimeService();

View File

@ -73,6 +73,7 @@
#include "File.h"
#include "MessagePort.h"
#include "Navigator.h"
#include "Principal.h"
#include "RuntimeService.h"
#include "ScriptLoader.h"
@ -1693,6 +1694,26 @@ public:
}
};
class OfflineStatusChangeRunnable : public WorkerRunnable
{
public:
OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
mIsOffline(aIsOffline)
{
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->OfflineStatusChangeEventInternal(aCx, mIsOffline);
return true;
}
private:
bool mIsOffline;
};
class WorkerJSRuntimeStats : public JS::RuntimeStats
{
const nsACString& mRtPath;
@ -3035,6 +3056,56 @@ WorkerPrivateParent<Derived>::CycleCollect(JSContext* aCx, bool aDummy)
}
}
template <class Derived>
void
WorkerPrivateParent<Derived>::OfflineStatusChangeEvent(JSContext* aCx, bool aIsOffline)
{
AssertIsOnParentThread();
nsRefPtr<OfflineStatusChangeRunnable> runnable =
new OfflineStatusChangeRunnable(ParentAsWorkerPrivate(), aIsOffline);
if (!runnable->Dispatch(aCx)) {
NS_WARNING("Failed to dispatch offline status change event!");
JS_ClearPendingException(aCx);
}
}
void
WorkerPrivate::OfflineStatusChangeEventInternal(JSContext* aCx, bool aIsOffline)
{
AssertIsOnWorkerThread();
for (uint32_t index = 0; index < mChildWorkers.Length(); ++index) {
mChildWorkers[index]->OfflineStatusChangeEvent(aCx, aIsOffline);
}
mOnLine = !aIsOffline;
WorkerGlobalScope* globalScope = GlobalScope();
nsRefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
if (nav) {
nav->SetOnLine(mOnLine);
}
nsString eventType;
if (aIsOffline) {
eventType.AssignLiteral("offline");
} else {
eventType.AssignLiteral("online");
}
nsCOMPtr<nsIDOMEvent> event;
nsresult rv =
NS_NewDOMEvent(getter_AddRefs(event), globalScope, nullptr, nullptr);
NS_ENSURE_SUCCESS_VOID(rv);
rv = event->InitEvent(eventType, false, false);
NS_ENSURE_SUCCESS_VOID(rv);
event->SetTrusted(true);
globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
}
template <class Derived>
bool
WorkerPrivateParent<Derived>::RegisterSharedWorker(JSContext* aCx,
@ -3554,10 +3625,12 @@ WorkerPrivate::WorkerPrivate(JSContext* aCx,
if (aParent) {
aParent->AssertIsOnWorkerThread();
aParent->GetAllPreferences(mPreferences);
mOnLine = aParent->OnLine();
}
else {
AssertIsOnMainThread();
RuntimeService::GetDefaultPreferences(mPreferences);
mOnLine = !NS_IsOffline();
}
}

View File

@ -392,6 +392,9 @@ public:
void
CycleCollect(JSContext* aCx, bool aDummy);
void
OfflineStatusChangeEvent(JSContext* aCx, bool aIsOffline);
bool
RegisterSharedWorker(JSContext* aCx, SharedWorker* aSharedWorker);
@ -743,6 +746,7 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
#endif
bool mPreferences[WORKERPREF_COUNT];
bool mOnLine;
protected:
~WorkerPrivate();
@ -902,6 +906,9 @@ public:
void
CycleCollectInternal(JSContext* aCx, bool aCollectChildren);
void
OfflineStatusChangeEventInternal(JSContext* aCx, bool aIsOffline);
JSContext*
GetJSContext() const
{
@ -983,6 +990,13 @@ public:
return mPreferences[WORKERPREF_PROMISE];
}
bool
OnLine() const
{
AssertIsOnWorkerThread();
return mOnLine;
}
void
StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);

View File

@ -99,7 +99,7 @@ WorkerGlobalScope::Navigator()
mWorkerPrivate->AssertIsOnWorkerThread();
if (!mNavigator) {
mNavigator = WorkerNavigator::Create();
mNavigator = WorkerNavigator::Create(mWorkerPrivate->OnLine());
MOZ_ASSERT(mNavigator);
}
@ -107,6 +107,15 @@ WorkerGlobalScope::Navigator()
return navigator.forget();
}
already_AddRefed<WorkerNavigator>
WorkerGlobalScope::GetExistingNavigator() const
{
mWorkerPrivate->AssertIsOnWorkerThread();
nsRefPtr<WorkerNavigator> navigator = mNavigator;
return navigator.forget();
}
void
WorkerGlobalScope::Close(JSContext* aCx)
{

View File

@ -60,8 +60,13 @@ public:
already_AddRefed<WorkerLocation>
Location();
already_AddRefed<WorkerNavigator>
Navigator();
already_AddRefed<WorkerNavigator>
GetExistingNavigator() const;
void
Close(JSContext* aCx);
@ -97,6 +102,8 @@ public:
void
Btoa(const nsAString& aBtoa, nsAString& aOutput, ErrorResult& aRv) const;
IMPL_EVENT_HANDLER(online)
IMPL_EVENT_HANDLER(offline)
IMPL_EVENT_HANDLER(close)
void

View File

@ -27,6 +27,9 @@ support-files =
multi_sharedWorker_sharedWorker.js
navigator_worker.js
newError_worker.js
onLine_worker.js
onLine_worker_child.js
onLine_worker_head.js
promise_worker.js
recursion_worker.js
recursiveOnerror_worker.js
@ -90,6 +93,7 @@ support-files =
[test_multi_sharedWorker_lifetimes.html]
[test_navigator.html]
[test_newError.html]
[test_onLine.html]
[test_promise.html]
[test_recursion.html]
[test_recursiveOnerror.html]

View File

@ -9,7 +9,8 @@ var supportedProps = [
"platform",
"product",
"taintEnabled",
"userAgent"
"userAgent",
"onLine"
];
for (var prop in navigator) {

View File

@ -0,0 +1,65 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
importScripts("onLine_worker_head.js");
var N_CHILDREN = 3;
var children = [];
var finishedChildrenCount = 0;
var lastTest = false;
for (var event of ["online", "offline"]) {
addEventListener(event,
makeHandler(
"addEventListener('%1', ..., false)",
event, 1, "Parent Worker"),
false);
}
onmessage = function(e) {
if (e.data === 'lastTest') {
children.forEach(function(w) {
w.postMessage({ type: 'lastTest' });
});
lastTest = true;
}
}
function setupChildren(cb) {
var readyCount = 0;
for (var i = 0; i < N_CHILDREN; ++i) {
var w = new Worker("onLine_worker_child.js");
children.push(w);
w.onerror = function(e) {
info("Error creating child " + e.message);
}
w.onmessage = function(e) {
if (e.data.type === 'ready') {
info("Got ready from child");
readyCount++;
if (readyCount === N_CHILDREN) {
cb();
}
} else if (e.data.type === 'finished') {
finishedChildrenCount++;
if (lastTest && finishedChildrenCount === N_CHILDREN) {
postMessage({ type: 'finished' });
children = [];
close();
}
} else if (e.data.type === 'ok') {
// Pass on test to page.
postMessage(e.data);
}
}
}
}
setupChildren(function() {
postMessage({ type: 'ready' });
});

View File

@ -0,0 +1,75 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
function info(text) {
dump("Test for Bug 925437: worker: " + text + "\n");
}
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
/**
* Returns a handler function for an online/offline event. The returned handler
* ensures the passed event object has expected properties and that the handler
* is called at the right moment (according to the gState variable).
* @param nameTemplate The string identifying the hanlder. '%1' in that
* string will be replaced with the event name.
* @param eventName 'online' or 'offline'
* @param expectedState value of gState at the moment the handler is called.
* The handler increases gState by one before checking
* if it matches expectedState.
*/
function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) {
prefix += ": ";
return function(e) {
var name = nameTemplate.replace(/%1/, eventName);
ok(e.constructor == Event, prefix + "event should be an Event");
ok(e.type == eventName, prefix + "event type should be " + eventName);
ok(!e.bubbles, prefix + "event should not bubble");
ok(!e.cancelable, prefix + "event should not be cancelable");
ok(e.target == self, prefix + "the event target should be the worker scope");
ok(eventName == 'online' ? navigator.onLine : !navigator.onLine, prefix + "navigator.onLine " + navigator.onLine + " should reflect event " + eventName);
if (custom) {
custom();
}
}
}
var lastTest = false;
function lastTestTest() {
if (lastTest) {
postMessage({ type: 'finished' });
close();
}
}
for (var event of ["online", "offline"]) {
addEventListener(event,
makeHandler(
"addEventListener('%1', ..., false)",
event, 1, "Child Worker", lastTestTest
),
false);
}
onmessage = function(e) {
if (e.data.type === 'lastTest') {
lastTest = true;
} else if (e.data.type === 'navigatorState') {
ok(e.data.state === navigator.onLine, "Child and parent navigator state should match");
}
}
postMessage({ type: 'ready' });

View File

@ -0,0 +1,43 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
function info(text) {
dump("Test for Bug 925437: worker: " + text + "\n");
}
function ok(test, message) {
postMessage({ type: 'ok', test: test, message: message });
}
/**
* Returns a handler function for an online/offline event. The returned handler
* ensures the passed event object has expected properties and that the handler
* is called at the right moment (according to the gState variable).
* @param nameTemplate The string identifying the hanlder. '%1' in that
* string will be replaced with the event name.
* @param eventName 'online' or 'offline'
* @param expectedState value of gState at the moment the handler is called.
* The handler increases gState by one before checking
* if it matches expectedState.
*/
function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) {
prefix += ": ";
return function(e) {
var name = nameTemplate.replace(/%1/, eventName);
ok(e.constructor == Event, prefix + "event should be an Event");
ok(e.type == eventName, prefix + "event type should be " + eventName);
ok(!e.bubbles, prefix + "event should not bubble");
ok(!e.cancelable, prefix + "event should not be cancelable");
ok(e.target == self, prefix + "the event target should be the worker scope");
ok(eventName == 'online' ? navigator.onLine : !navigator.onLine, prefix + "navigator.onLine " + navigator.onLine + " should reflect event " + eventName);
if (custom) {
custom();
}
}
}

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 925437: online/offline events tests.
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/licenses/publicdomain/
-->
<head>
<title>Test for Bug 925437 (worker online/offline events)</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=925437">Mozilla Bug 925437</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script class="testbody" type="text/javascript">
addLoadEvent(function() {
var w = new Worker("onLine_worker.js");
w.onmessage = function(e) {
if (e.data.type === 'ready') {
doTest();
} else if (e.data.type === 'ok') {
ok(e.data.test, e.data.message);
} else if (e.data.type === 'finished') {
SimpleTest.finish();
}
}
function doTest() {
var iosvc = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
.getService(SpecialPowers.Ci.nsIIOService2);
iosvc.manageOfflineStatus = false;
info("setting iosvc.offline = true");
iosvc.offline = true;
info("setting iosvc.offline = false");
iosvc.offline = false;
info("setting iosvc.offline = true");
iosvc.offline = true;
for (var i = 0; i < 10; ++i) {
iosvc.offline = !iosvc.offline;
}
info("setting iosvc.offline = false");
w.postMessage('lastTest');
iosvc.offline = false;
}
});
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>