A few more thoughts, specifically about the removal of callbacks in this
proposal:
One fairly simple use case is for an application to (a) check that it is
installed (if not, ask for installation), (b) check that its manifest is up
to date (if not, ask for reinstallation) and (c) at least look at the
receipt (I'll leave out verification, as it's a separate concern).
Here's what it would look like in this original API:
var manifestUrl = '/manifest.webapp';
function afterInstalled(app) {
... some stuff you'd do once you've confirmed everything
... you might use app.manifest.installData.receipts here, to verify
}
function failure(reason) {
... do something in case of an unresolvable failure
}
navigator.mozApps.amInstalled(function (app) {
if (! app) {
navigator.mozApps.install(manifestUrl, null, afterInstall, function ()
{failure('User rejected installation');});
return;
}
// You might know the version of the manifest, e.g., hardcode it via a
template, but
// for this stand-alone example we'll fetch it via XHR
var req = new XMLHttpRequest();
req.open('GET', manifestUrl);
req.onreadystatechange = function () {
if (req.readyState != 4) {
return;
}
if (req.status != 200) {
failure('Manifest missing');
return;
}
manifest = JSON.parse(req.responseText);
// Arguably in addition to version, you should be checking that the
manifest URL is what you expected
if (manifest.version != app.manifest.version) {
navigator.mozApps.install(manifestUrl, null, afterInstall, function
() {failure('Manifest update failed (denied?)');});
return;
}
afterInstall(app);
};
}, function () {failure('Could not call amInstalled');});
Here is the same functionality using this new API:
// keep manifestUrl, failure
function afterInstall(app) {
navigator.mozApps.onsuccess = null;
... and then the rest of the stuff
}
var appOrigin = location.protocol + '//' + location.host;
var expectVersion = null;
navigator.mozApps.onsuccess = function (apps) {
if (! apps.length) {
navigator.mozApps.install(manifestUrl);
checkInstall();
return;
}
var app = null;
for (var i=0; i<apps.length; i++) {
if (apps[i].origin == appOrigin) {
// Just hope there's no normalization errors here...
// if you are confident this can't be a store, then you could be sure
that so long as apps.length == 1, that apps[0] is this app
app = apps[i];
break;
}
}
if (! app) {
navigator.mozApps.install(manifestUrl);
checkInstall();
}
var req = new XMLHttpRequest();
req.open('GET', manifestUrl);
req.onreadystatechange = function () {
if (req.readyState != 4) {
return;
}
if (req.status != 200) {
failure('Manifest missing');
return;
}
manifest = JSON.parse(req.responseText);
// Arguably in addition to version, you should be checking that the
manifest URL is what you expected
if (manifest.version != app.manifest.version) {
expectVersion = app.manifest.version;
navigator.mozApps.install(manifestUrl);
checkInstall();
return;
}
afterInstall(app);
};
};
navigator.mozApps.enumerate();
navigator.mozApps.onerror = function (error) {
// Not sure if the error has useful text, and I'm not sure how to
interpret all the different codes (e.g., DENIED vs. PERMISSION_DENIED?)
failure(error.toString());
};
// OK, so this does the basic app checking, *except* afterInstall isn't
called in a number of cases
// for this we need one of two approaches that I can see. This one works
even with the API as documented
// (from what I can tell):
function checkInstall() {
var maxTime = 30*1000; // give it 30 seconds to succeed, otherwise we
consider it a failure
var start = new Date().getTime();
var timer = null;
var checker = function () {
if (new Date().getTime() - start > maxTime) {
failure('installation denied');
return;
}
navigator.mozApps.enumerate();
timer = setTimeout(checker, 500);
}
navigator.mozApps.onsuccess = function (apps) {
for (var i=0; i<apps.length; i++) {
// Again, if you are certain this can't be a store, and so apps[0]
(if it exists) must be this app, you can skip this test
if (apps[i].origin == appOrigin) {
if (expectVersion && apps[i].manifest.version != expectVersion) {
// We're doing an update, and the update still hasn't gone through
break;
}
clearTimeout(timer);
afterInstall(apps[i]);
return;
}
}
// Since nothing is ready yet, checker() will try again soon
};
checker();
}
// Another option, which the IDL suggests might not be allowed, but could
be allowed, is for an app to use the oninstall
// event to see the update. It seems reasonable (and useful) that the app
could see an event when the app itself is installed or uninstalled
// (but would not see events for other apps)
function checkInstall() {
var timer = setTimeout(function () {
failure("Installation canceled or otherwise didn't go through");
}, 30*1000);
navigator.mozApps.oninstall = function (app) {
if (app.origin == appOrigin) {
if (expectedVersion && expectedVersion != app.manifest.version) {
// A stale installation? Not sure what that would mean, but it's
not success
return;
}
navigator.mozApps.oninstall = null;
clearTimeout(timer);
afterInstall(app);
}
};
}
--------
My personal conclusion is that onsuccess and onerror is much more
convoluted when implementing basic logic, than using simple callbacks. And
arguably instead of setting and unsetting onsuccess, onerror, and
oninstall, I should have been nesting and reseting those attributes, like:
oldOnSuccess = navigator.mozApps.onsuccess;
navigator.mozApps.onsuccess = function (apps) {
... my stuff...
oldOnSuccess(apps);
};
// and after done:
navigator.mozApps.onsuccess = oldOnSuccess;
// Hah, only not! Because maybe someone else overwrote onsuccess in the
meantime!
Doing this in each place would have made the code a bit longer and more
complicated, and it's not at all clear that the behavior in that case is
correct - onsuccess is a return value that is either expected or not, it's
not something that can be nested really.
I'm not sure what is the motivation of using this pattern (as opposed to
the original callback style), except perhaps to be more "DOM-like"? Global
attributes for events are not common in DOM APIs that I've seen.
window.onerror is one example, but that only seems useful for general error
reporting (a kind of last resort error catcher), it's not something I see
widely used. DOM level 0 events use this pattern, but they are much more
"eventy" than this code, and of course we have a new and better way to
actually do these (addEventListener). XMLHttpRequest at first looks like
it uses this pattern, but XMLHttpRequest objects are instantiated locally,
they do not have global handlers, rendering them very different in practice
(and almost trivially translatable to and from a callback style).
Other new APIs do use a callback style, like geolocation, and notifications
(
https://developer.mozilla.org/en/DOM/notification) use the XHR approach
that avoid global handlers. Storage events are global, but at least use
addEventListener (and the core API is synchronous). IndexDB like XHR uses
locally instantiated objects. So I'm really not seeing any parallel
between this API and other DOM APIs, nor do I know what problem this is
fixing when compared to the original API. Another approach would be to
create an enumeration object, and an installation progress object, but
that's going to add a lot more surface area to the API.
Another simple pattern that the original API supports is present in
https://apps.mozillalabs.com/appdir/ - when you hit "install" and it
successfully installs then the button is changed to reflect the successful
installation. You'd have to do something very similar to checkInstall to
support this with this new API. The button confirmation, though a useful
bit of UI (and notably present in, e.g., the Android Market), is also a
similar issue to receipt management and handling what happens when you get
multiple overlapping installations - i.e., how can a store confirm that a
purchase completed successfully when it is deciding whether to complete or
revert the transaction.
On Fri, Jan 6, 2012 at 4:45 PM, Ian Bicking <
ia...@mozilla.com> wrote:
> On Fri, Jan 6, 2012 at 2:22 PM, Fabrice Desré <
fab...@mozilla.com> wrote:
>
>> I posted an updated IDL here:
>>
https://etherpad.mozilla.org/openwebapps-api
>>
>>