Bug 1014023 - [Datastore] Notify apps of changes in datastore without being opened. r=baku, r=gene

This commit is contained in:
Sean Lin 2014-06-27 15:58:47 +08:00
parent d926cc34de
commit 0d34f59084
29 changed files with 589 additions and 110 deletions

View File

@ -886,6 +886,10 @@ pref("network.sntp.timeout", 30); // In seconds.
// Enable dataStore
pref("dom.datastore.enabled", true);
// When an entry is changed, use two timers to fire system messages in a more
// moderate pattern.
pref("dom.datastore.sysMsgOnChangeShortTimeoutSec", 10);
pref("dom.datastore.sysMsgOnChangeLongTimeoutSec", 60);
// DOM Inter-App Communication API.
pref("dom.inter-app-communication-api.enabled", true);

View File

@ -23,12 +23,33 @@ XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
"@mozilla.org/datastore-service;1",
"nsIDataStoreService");
XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
var kSysMsgOnChangeShortTimeoutSec =
Services.prefs.getIntPref("dom.datastore.sysMsgOnChangeShortTimeoutSec");
var kSysMsgOnChangeLongTimeoutSec =
Services.prefs.getIntPref("dom.datastore.sysMsgOnChangeLongTimeoutSec");
this.DataStoreChangeNotifier = {
children: [],
messages: [ "DataStore:Changed", "DataStore:RegisterForMessages",
"DataStore:UnregisterForMessages",
"child-process-shutdown" ],
// These hashes are used for storing the mapping between the datastore
// identifiers (name | owner manifest URL) and their correspondent timers.
// The object literal is defined as below:
//
// {
// "datastore name 1|owner manifest URL 1": timer1,
// "datastore name 2|owner manifest URL 2": timer2,
// ...
// }
sysMsgOnChangeShortTimers: {},
sysMsgOnChangeLongTimers: {},
init: function() {
debug("init");
@ -59,7 +80,8 @@ this.DataStoreChangeNotifier = {
},
broadcastMessage: function broadcastMessage(aData) {
debug("Broadast");
debug("broadcast");
this.children.forEach(function(obj) {
if (obj.store == aData.store && obj.owner == aData.owner) {
obj.mm.sendAsyncMessage("DataStore:Changed:Return:OK", aData);
@ -67,6 +89,69 @@ this.DataStoreChangeNotifier = {
});
},
broadcastSystemMessage: function(aStore, aOwner) {
debug("broadcastSystemMessage");
// Clear relevant timers.
var storeKey = aStore + "|" + aOwner;
var shortTimer = this.sysMsgOnChangeShortTimers[storeKey];
if (shortTimer) {
shortTimer.cancel();
delete this.sysMsgOnChangeShortTimers[storeKey];
}
var longTimer = this.sysMsgOnChangeLongTimers[storeKey];
if (longTimer) {
longTimer.cancel();
delete this.sysMsgOnChangeLongTimers[storeKey];
}
// Get all the manifest URLs of the apps which can access the datastore.
var manifestURLs = dataStoreService.getAppManifestURLsForDataStore(aStore);
var enumerate = manifestURLs.enumerate();
while (enumerate.hasMoreElements()) {
var manifestURL = enumerate.getNext().QueryInterface(Ci.nsISupportsString);
debug("Notify app " + manifestURL + " of datastore updates");
// Send the system message 'datastore-update-{store name}' to all the
// pages for these apps. With the manifest URL of the owner in the message
// payload, it notifies the consumer a sync operation should be performed.
systemMessenger.sendMessage("datastore-update-" + aStore,
{ owner: aOwner },
null,
Services.io.newURI(manifestURL, null, null));
}
},
// Use the following logic to broadcast system messages in a moderate pattern.
// 1. When an entry is changed, start a short timer and a long timer.
// 2. If an entry is changed while the short timer is running, reset it.
// Do not reset the long timer.
// 3. Once either fires, broadcast the system message and cancel both timers.
setSystemMessageTimeout: function(aStore, aOwner) {
debug("setSystemMessageTimeout");
var storeKey = aStore + "|" + aOwner;
// Reset the short timer.
var shortTimer = this.sysMsgOnChangeShortTimers[storeKey];
if (!shortTimer) {
shortTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.sysMsgOnChangeShortTimers[storeKey] = shortTimer;
} else {
shortTimer.cancel();
}
shortTimer.initWithCallback({ notify: this.broadcastSystemMessage.bind(this, aStore, aOwner) },
kSysMsgOnChangeShortTimeoutSec * 1000,
Ci.nsITimer.TYPE_ONE_SHOT);
// Set the long timer if necessary.
if (!this.sysMsgOnChangeLongTimers[storeKey]) {
var longTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.sysMsgOnChangeLongTimers[storeKey] = longTimer;
longTimer.initWithCallback({ notify: this.broadcastSystemMessage.bind(this, aStore, aOwner) },
kSysMsgOnChangeLongTimeoutSec * 1000,
Ci.nsITimer.TYPE_ONE_SHOT);
}
},
receiveMessage: function(aMessage) {
debug("receiveMessage ");
@ -89,6 +174,7 @@ this.DataStoreChangeNotifier = {
switch (aMessage.name) {
case "DataStore:Changed":
this.broadcastMessage(aMessage.data);
this.setSystemMessageTimeout(aMessage.data.store, aMessage.data.owner);
break;
case "DataStore:RegisterForMessages":

View File

@ -33,9 +33,11 @@
#include "nsIDocument.h"
#include "nsIDOMGlobalPropertyInitializer.h"
#include "nsIIOService.h"
#include "nsIMutableArray.h"
#include "nsIObserverService.h"
#include "nsIPermissionManager.h"
#include "nsIScriptSecurityManager.h"
#include "nsISupportsPrimitives.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h"
#include "nsIURI.h"
@ -375,6 +377,24 @@ GetDataStoreInfosEnumerator(const uint32_t& aAppId,
return PL_DHASH_NEXT;
}
PLDHashOperator
GetAppManifestURLsEnumerator(const uint32_t& aAppId,
DataStoreInfo* aInfo,
void* aUserData)
{
AssertIsInMainProcess();
MOZ_ASSERT(NS_IsMainThread());
auto* manifestURLs = static_cast<nsIMutableArray*>(aUserData);
nsCOMPtr<nsISupportsString> manifestURL(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
if (manifestURL) {
manifestURL->SetData(aInfo->mManifestURL);
manifestURLs->AppendElement(manifestURL, false);
}
return PL_DHASH_NEXT;
}
// This class is useful to enumerate the add permissions for each app.
class MOZ_STACK_CLASS AddPermissionsData
{
@ -1057,6 +1077,31 @@ DataStoreService::GetDataStoreInfos(const nsAString& aName,
return NS_OK;
}
NS_IMETHODIMP
DataStoreService::GetAppManifestURLsForDataStore(const nsAString& aName,
nsIArray** aManifestURLs)
{
ASSERT_PARENT_PROCESS()
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIMutableArray> manifestURLs = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!manifestURLs) {
return NS_ERROR_OUT_OF_MEMORY;
}
HashApp* apps = nullptr;
if (mStores.Get(aName, &apps)) {
apps->EnumerateRead(GetAppManifestURLsEnumerator, manifestURLs.get());
}
if (mAccessStores.Get(aName, &apps)) {
apps->EnumerateRead(GetAppManifestURLsEnumerator, manifestURLs.get());
}
*aManifestURLs = manifestURLs;
NS_ADDREF(*aManifestURLs);
return NS_OK;
}
bool
DataStoreService::CheckPermission(nsIPrincipal* aPrincipal)
{

View File

@ -7,8 +7,9 @@
interface nsIDOMWindow;
interface nsIPrincipal;
interface nsIArray;
[scriptable, uuid(9b59c49a-0cd7-11e4-b096-74d02b97e723)]
[scriptable, uuid(79944b1c-187d-11e4-abb6-74d02b97e723)]
interface nsIDataStoreService : nsISupports
{
void installDataStore(in unsigned long appId,
@ -27,5 +28,7 @@ interface nsIDataStoreService : nsISupports
in DOMString name,
in DOMString owner);
nsIArray getAppManifestURLsForDataStore(in DOMString name);
boolean checkPermission(in nsIPrincipal principal);
};

View File

@ -0,0 +1,122 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore - notify updates with system messages</title>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
var gStore;
var gChangeId = null;
var gChangeOperation = null;
var gIsSystemMessageFired = false;
function is(a, b, msg) {
alert((a === b ? 'OK' : 'KO') + ' ' + msg)
}
function ok(a, msg) {
alert((a ? 'OK' : 'KO')+ ' ' + msg)
}
function cbError() {
alert('KO error');
}
function finish() {
alert('DONE');
}
function testGetDataStores() {
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');
gStore = stores[0];
runTest();
}, cbError);
}
function testStoreAdd(value, expectedId) {
gStore.add(value).then(function(id) {
is(id, expectedId, "store.add() is called");
}, cbError);
}
function eventListener(evt) {
ok(evt instanceof DataStoreChangeEvent, "DataStoreChangeEvent has been received");
ok(evt, "OnChangeListener is called with data");
is(/[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}/.test(evt.revisionId), true, "event.revisionId returns something");
is(evt.id, gChangeId, "OnChangeListener is called with the right ID: " + evt.id);
is(evt.operation, gChangeOperation, "OnChangeListener is called with the right operation:" + evt.operation + " " + gChangeOperation);
runTest();
}
function onDatastoreUpdateFoo(message) {
gIsSystemMessageFired = true;
ok(true, "System message 'datastore-update-foo' has been received");
runTest();
}
var tests = [
// Test for GetDataStore.
testGetDataStores,
// Add onchange = function.
function() {
gStore.onchange = eventListener;
is(gStore.onchange, eventListener, "onChange is set");
runTest();
},
// Set system message handler.
function() {
navigator.mozSetMessageHandler('datastore-update-foo', onDatastoreUpdateFoo);
runTest();
},
// Add.
function() { gChangeId = 1; gChangeOperation = 'added';
testStoreAdd({ number: 42 }, 1); },
// Remove event listener.
function() {
gStore.removeEventListener('change', eventListener);
runTest();
},
// Wait enough amount of time for the system message to fire.
function() {
setTimeout(runTest, 10000);
},
// Ensure the system message has fired and no more pending ones.
function() {
ok(!navigator.mozHasPendingMessage('datastore-update-foo'), "No more pending system message");
ok(gIsSystemMessageFired, "The system message has fired");
runTest();
}
];
function runTest() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
runTest();
</script>
</pre>
</body>
</html>

View File

@ -30,6 +30,7 @@ support-files =
file_sync_common.js
file_bug1008044.html
file_bug957086.html
file_notify_system_message.html
[test_app_install.html]
[test_readonly.html]
@ -50,3 +51,5 @@ support-files =
[test_transactions.html]
[test_bug1008044.html]
[test_bug957086.html]
[test_notify_system_message.html]
skip-if = toolkit == 'gonk' # b2g

View File

@ -10,10 +10,6 @@
<div id="container"></div>
<script type="application/javascript;version=1.7">
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
var gBaseURL = 'http://test/tests/dom/datastore/tests/';
@ -26,8 +22,14 @@
{ "type": "webapps-manage", "allow": 1, "context": document }],
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]]}, function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
gGenerator.next(); });
});

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,12 +77,18 @@
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3],
["dom.ipc.browser_frames.oop_by_default", true],
["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();
@ -117,10 +123,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -81,12 +81,18 @@
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3],
["dom.ipc.browser_frames.oop_by_default", true],
["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();
@ -124,10 +130,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -82,11 +82,17 @@
// 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();
@ -124,10 +130,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -75,12 +75,17 @@
// 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);
},
// Enabling mozBrowser
function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
},
@ -134,10 +139,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,6 +77,8 @@
// 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", false]]}, runTest);
},
@ -89,6 +91,10 @@
},
function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
runTest();
@ -123,10 +129,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -121,12 +121,17 @@
// 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);
},
// Enabling mozBrowser
function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
},
@ -169,10 +174,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -0,0 +1,138 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore - notify updates with system messages</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_notify_system_message.html';
var gHostedPageURL = 'http://mochi.test:8888/tests/dom/datastore/tests/file_notify_system_message.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.sysmsg.enabled", true],
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true],
["dom.mozBrowserFramesEnabled", true]]}, runTest);
},
function() {
SpecialPowers.setAllAppsLaunchable(true);
// No confirmation needed when an app is installed.
SpecialPowers.autoConfirmAppInstall(runTest);
},
function() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
SpecialPowers.Cu.import("resource://gre/modules/Services.jsm");
var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
.getService(SpecialPowers.Ci.nsIIOService);
var systemMessenger = SpecialPowers.Cc["@mozilla.org/system-message-internal;1"]
.getService(SpecialPowers.Ci.nsISystemMessagesInternal);
systemMessenger.registerPage("datastore-update-foo",
ioService.newURI(gHostedPageURL, null, null),
ioService.newURI(gHostedManifestURL, null, null));
}
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

@ -77,12 +77,18 @@
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3],
["dom.ipc.browser_frames.oop_by_default", true],
["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();
@ -117,10 +123,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -105,12 +105,18 @@
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3],
["dom.ipc.browser_frames.oop_by_default", true],
["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();
@ -147,10 +153,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -30,6 +30,10 @@
}
function runTest() {
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
@ -95,12 +99,10 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
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);
</script>

View File

@ -77,11 +77,17 @@
// 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.setBoolPref("dom.mozBrowserFramesEnabled", true);
runTest();
},
@ -115,10 +121,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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.setBoolPref("dom.mozBrowserFramesEnabled", true);
runTest();
},
@ -115,10 +121,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -77,11 +77,17 @@
// 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();
@ -116,10 +122,6 @@
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>

View File

@ -192,31 +192,48 @@ SystemMessageInternal.prototype = {
// clean it up from the pending message queue when apps receive it.
let messageID = gUUIDGenerator.generateUUID().toString();
debug("Sending " + aType + " " + JSON.stringify(aMessage) +
" for " + aPageURI.spec + " @ " + aManifestURI.spec +
'; extra: ' + JSON.stringify(aExtra));
let result = this._sendMessageCommon(aType,
aMessage,
messageID,
aPageURI.spec,
aManifestURI.spec,
aExtra);
debug("Returned status of sending message: " + result);
// Don't need to open the pages and queue the system message
// which was not allowed to be sent.
if (result === MSG_SENT_FAILURE_PERM_DENIED) {
return;
let manifestURL = aManifestURI.spec;
let pageURLs = [];
if (aPageURI) {
pageURLs.push(aPageURI.spec);
} else {
// Send this message to all the registered pages of the app if |aPageURI|
// is not specified.
for (let i = 0; i < this._pages.length; i++) {
let page = this._pages[i];
if (page.type === aType && page.manifestURL === manifestURL) {
pageURLs.push(page.pageURL);
}
}
}
let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec);
if (page) {
// Queue this message in the corresponding pages.
this._queueMessage(page, aMessage, messageID);
pageURLs.forEach(function(aPageURL) {
debug("Sending " + aType + " " + JSON.stringify(aMessage) +
" for " + aPageURL + " @ " + manifestURL +
'; extra: ' + JSON.stringify(aExtra));
this._openAppPage(page, aMessage, aExtra, result);
}
let result = this._sendMessageCommon(aType,
aMessage,
messageID,
aPageURL,
manifestURL,
aExtra);
debug("Returned status of sending message: " + result);
// Don't need to open the pages and queue the system message
// which was not allowed to be sent.
if (result === MSG_SENT_FAILURE_PERM_DENIED) {
return;
}
let page = this._findPage(aType, aPageURL, manifestURL);
if (page) {
// Queue this message in the corresponding pages.
this._queueMessage(page, aMessage, messageID);
this._openAppPage(page, aMessage, aExtra, result);
}
}, this);
},
broadcastMessage: function(aType, aMessage, aExtra) {

View File

@ -15,7 +15,8 @@ Cu.import("resource://gre/modules/PermissionsTable.jsm");
Cu.import("resource://gre/modules/PermissionSettings.jsm");
this.EXPORTED_SYMBOLS = ["SystemMessagePermissionsChecker",
"SystemMessagePermissionsTable"];
"SystemMessagePermissionsTable",
"SystemMessagePrefixPermissionsTable"];
function debug(aStr) {
// dump("SystemMessagePermissionsChecker.jsm: " + aStr + "\n");
@ -122,6 +123,18 @@ this.SystemMessagePermissionsTable = {
}
};
// This table maps system message prefix to permission(s), indicating only
// the system messages with specified prefixes granted by the page's permissions
// are allowed to be registered or sent to that page. Note the empty permission
// set means this type of system message is always permitted.
//
// Note that this table is only used when the permission checker can't find a
// match in SystemMessagePermissionsTable listed above.
this.SystemMessagePrefixPermissionsTable = {
"datastore-update-": { },
};
this.SystemMessagePermissionsChecker = {
/**
* Return all the needed permission names for the given system message.
@ -140,16 +153,26 @@ this.SystemMessagePermissionsChecker = {
let permNames = SystemMessagePermissionsTable[aSysMsgName];
if (permNames === undefined) {
debug("'" + aSysMsgName + "' is not associated with permissions. " +
"Please add them to the SystemMessagePermissionsTable.");
return null;
// Try to look up in the prefix table.
for (let sysMsgPrefix in SystemMessagePrefixPermissionsTable) {
if (aSysMsgName.indexOf(sysMsgPrefix) === 0) {
permNames = SystemMessagePrefixPermissionsTable[sysMsgPrefix];
break;
}
}
if (permNames === undefined) {
debug("'" + aSysMsgName + "' is not associated with permissions. " +
"Please add them to the SystemMessage[Prefix]PermissionsTable.");
return null;
}
}
let object = { };
for (let permName in permNames) {
if (PermissionsTable[permName] === undefined) {
debug("'" + permName + "' for '" + aSysMsgName + "' is invalid. " +
"Please correct it in the SystemMessagePermissionsTable.");
"Please correct it in the SystemMessage[Prefix]PermissionsTable.");
return null;
}
@ -157,7 +180,7 @@ this.SystemMessagePermissionsChecker = {
let access = permNames[permName];
if (!access || !Array.isArray(access)) {
debug("'" + permName + "' is not associated with access array. " +
"Please correct it in the SystemMessagePermissionsTable.");
"Please correct it in the SystemMessage[Prefix]PermissionsTable.");
return null;
}
object[permName] = appendAccessToPermName(permName, access);

View File

@ -13,10 +13,12 @@ interface nsIDOMWindow;
interface nsISystemMessagesInternal : nsISupports
{
/*
* Allow any internal user to broadcast a message of a given type.
* Allow any internal user to send a message of a given type to a given page
* of an app. The message will be sent to all the registered pages of the app
* when |pageURI| is not specified.
* @param type The type of the message to be sent.
* @param message The message payload.
* @param pageURI The URI of the page that will be opened.
* @param pageURI The URI of the page that will be opened. Nullable.
* @param manifestURI The webapp's manifest URI.
* @param extra Extra opaque information that will be passed around in the observer
* notification to open the page.