Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to do and design API feature detection in a general way?

14 views
Skip to first unread message

Gene Lian

unread,
Oct 17, 2013, 9:33:02 AM10/17/13
to dev-w...@lists.mozilla.org
Just firing some thoughts about how to make the content safely call APIs with backward compatibility. As you guys know, it'd be very painful to have API changes and to keep the backward compatibility at the same time. For example, if Gecko wants to modify an API like below:

navigator.fooAPI.oldFunc(...) -> navigator.fooAPI.newFunc(...)

To avoid breaking the compatibility, one of the possible solution is to make the content side temporally check the presences of functions/attributes to decide how to call the old/new APIs:

if (newFunc in navigator.fooAPI) {
navigator.fooAPI.newFunc(...);
} else if (oldFunc in navigator.fooAPI) {
navigator.fooAPI.oldFunc(...);
}

In this way, Gecko can safely deprecate the old APIs and Gaia can then remove this kind of temporal logic. This might work when we have only few functions/attributes under navigator.fooAPI. If we had many of them, unfortunately, we'd have lots of run-time checks per function/attribute basis, which doesn't look very clean and efficient. For example:

if (newFunc_1 in navigator.fooAPI) {
navigator.fooAPI.newFunc_1(...);
} else if (oldFunc_1 in navigator.fooAPI) {
navigator.fooAPI.oldFun_1(...);
}

if (newFunc_2 in navigator.fooAPI) {
navigator.fooAPI.newFunc_2(...);
} else if (oldFunc_2 in navigator.fooAPI) {
navigator.fooAPI.oldFun_2(...);
}

...

if (newFunc_N in navigator.fooAPI) {
navigator.fooAPI.newFunc_N(...);
} else if (oldFunc_N in navigator.fooAPI) {
navigator.fooAPI.oldFun_N(...);
}

where N could be a big number.

I wonder is that possible to provide a more generic way for the content side to do API feature detection? Supposing each API can provide some useful information like version number and can flip that number whenever API is changed, then Gaia can have a cleaner way to switch to call the corresponding APIs based on that version number:

switch (navigator.getVersion(navigator.fooAPI)) {
case 1:
navigator.fooAPI.oldFunc_1(...);
navigator.fooAPI.oldFunc_2(...);
...
navigator.fooAPI.oldFunc_N(...);
break;
case 2:
navigator.fooAPI.newFunc_1(...);
navigator.fooAPI.newFunc_2(...);
...
navigator.fooAPI.newFunc_N(...);
break;
}

which considers the API feature set as a whole based on the version information. I'm not sure if we used to discuss similar mechanisms or not. Any thought is appreciated. :)

Gene

Ehsan Akhgari

unread,
Oct 17, 2013, 10:20:34 AM10/17/13
to Gene Lian, dev-w...@lists.mozilla.org
On 2013-10-17 9:33 AM, Gene Lian wrote:
> Just firing some thoughts about how to make the content safely call APIs with backward compatibility. As you guys know, it'd be very painful to have API changes and to keep the backward compatibility at the same time. For example, if Gecko wants to modify an API like below:
>
> navigator.fooAPI.oldFunc(...) -> navigator.fooAPI.newFunc(...)

Is this a real use case? (It's still ok if it's just an abstract question!)

> To avoid breaking the compatibility, one of the possible solution is to make the content side temporally check the presences of functions/attributes to decide how to call the old/new APIs:
>
> if (newFunc in navigator.fooAPI) {
> navigator.fooAPI.newFunc(...);
> } else if (oldFunc in navigator.fooAPI) {
> navigator.fooAPI.oldFunc(...);
> }
>
> In this way, Gecko can safely deprecate the old APIs and Gaia can then remove this kind of temporal logic. This might work when we have only few functions/attributes under navigator.fooAPI. If we had many of them, unfortunately, we'd have lots of run-time checks per function/attribute basis, which doesn't look very clean and efficient. For example:
>
> if (newFunc_1 in navigator.fooAPI) {
> navigator.fooAPI.newFunc_1(...);
> } else if (oldFunc_1 in navigator.fooAPI) {
> navigator.fooAPI.oldFun_1(...);
> }
>
> if (newFunc_2 in navigator.fooAPI) {
> navigator.fooAPI.newFunc_2(...);
> } else if (oldFunc_2 in navigator.fooAPI) {
> navigator.fooAPI.oldFun_2(...);
> }
>
> ...
>
> if (newFunc_N in navigator.fooAPI) {
> navigator.fooAPI.newFunc_N(...);
> } else if (oldFunc_N in navigator.fooAPI) {
> navigator.fooAPI.oldFun_N(...);
> }
>
> where N could be a big number.

I think you're assuming that API changes are always in simple "rename"
forms. That's not necessarily the case, sometimes the semantics of the
API needs to change in a way that makes this kind of mechanical case
checking not work, and would sometimes make authors come up with
completely separate code paths, or other ways of degrading the
experience gracefully. I think the pattern you cite above is more
common when handling vendor prefixed APIs.

> I wonder is that possible to provide a more generic way for the content side to do API feature detection? Supposing each API can provide some useful information like version number and can flip that number whenever API is changed, then Gaia can have a cleaner way to switch to call the corresponding APIs based on that version number:
>
> switch (navigator.getVersion(navigator.fooAPI)) {
> case 1:
> navigator.fooAPI.oldFunc_1(...);
> navigator.fooAPI.oldFunc_2(...);
> ...
> navigator.fooAPI.oldFunc_N(...);
> break;
> case 2:
> navigator.fooAPI.newFunc_1(...);
> navigator.fooAPI.newFunc_2(...);
> ...
> navigator.fooAPI.newFunc_N(...);
> break;
> }
>
> which considers the API feature set as a whole based on the version information. I'm not sure if we used to discuss similar mechanisms or not. Any thought is appreciated. :)

This is not really a webby way of doing things. The falsity check is
the standard way of feature detection on the web, but it's mo commonly
used for new features which are not yet available everywhere (or dealing
with vendor prefixes) not for backwards compat.

That being said, breaking backwards compat is always hard. If you have
specific cases in mind, please let's discuss them concretely. This is
the kind of discussion which may be a bit hard to have in the abstract!

Cheers,
Ehsan

Jeremie Patonnier

unread,
Oct 17, 2013, 11:31:58 AM10/17/13
to Ehsan Akhgari, dev-w...@lists.mozilla.org, Gene Lian
A very common practice is also something like that :

var func = (navigator.fooAPI.newFunc ||
navigator.fooAPI.oldFunc).bind(navigator.fooAPI)

The call to bind is optional if the func does not need to preserve the
context
The most famous example of such practice is this one :
https://gist.github.com/paulirish/1579671

Best
Jeremie


2013/10/17 Ehsan Akhgari <ehsan....@gmail.com>

> On 2013-10-17 9:33 AM, Gene Lian wrote:
>
>> Just firing some thoughts about how to make the content safely call APIs
>> with backward compatibility. As you guys know, it'd be very painful to have
>> API changes and to keep the backward compatibility at the same time. For
>> example, if Gecko wants to modify an API like below:
>>
>> navigator.fooAPI.oldFunc(...) -> navigator.fooAPI.newFunc(...)
>>
>
> Is this a real use case? (It's still ok if it's just an abstract
> question!)
>
>
> To avoid breaking the compatibility, one of the possible solution is to
>> make the content side temporally check the presences of
>> functions/attributes to decide how to call the old/new APIs:
>>
>> if (newFunc in navigator.fooAPI) {
>> navigator.fooAPI.newFunc(...);
>> } else if (oldFunc in navigator.fooAPI) {
>> navigator.fooAPI.oldFunc(...);
>> }
>>
>> In this way, Gecko can safely deprecate the old APIs and Gaia can then
>> remove this kind of temporal logic. This might work when we have only few
>> functions/attributes under navigator.fooAPI. If we had many of them,
>> unfortunately, we'd have lots of run-time checks per function/attribute
>> basis, which doesn't look very clean and efficient. For example:
>>
>> if (newFunc_1 in navigator.fooAPI) {
>> navigator.fooAPI.newFunc_1(...**);
>> } else if (oldFunc_1 in navigator.fooAPI) {
>> navigator.fooAPI.oldFun_1(...)**;
>> }
>>
>> if (newFunc_2 in navigator.fooAPI) {
>> navigator.fooAPI.newFunc_2(...**);
>> } else if (oldFunc_2 in navigator.fooAPI) {
>> navigator.fooAPI.oldFun_2(...)**;
>> }
>>
>> ...
>>
>> if (newFunc_N in navigator.fooAPI) {
>> navigator.fooAPI.newFunc_N(...**);
>> } else if (oldFunc_N in navigator.fooAPI) {
>> navigator.fooAPI.oldFun_N(...)**;
>> }
>>
>> where N could be a big number.
>>
>
> I think you're assuming that API changes are always in simple "rename"
> forms. That's not necessarily the case, sometimes the semantics of the API
> needs to change in a way that makes this kind of mechanical case checking
> not work, and would sometimes make authors come up with completely separate
> code paths, or other ways of degrading the experience gracefully. I think
> the pattern you cite above is more common when handling vendor prefixed
> APIs.
>
>
> I wonder is that possible to provide a more generic way for the content
>> side to do API feature detection? Supposing each API can provide some
>> useful information like version number and can flip that number whenever
>> API is changed, then Gaia can have a cleaner way to switch to call the
>> corresponding APIs based on that version number:
>>
>> switch (navigator.getVersion(**navigator.fooAPI)) {
>> case 1:
>> navigator.fooAPI.oldFunc_1(...**);
>> navigator.fooAPI.oldFunc_2(...**);
>> ...
>> navigator.fooAPI.oldFunc_N(...**);
>> break;
>> case 2:
>> navigator.fooAPI.newFunc_1(...**);
>> navigator.fooAPI.newFunc_2(...**);
>> ...
>> navigator.fooAPI.newFunc_N(...**);
>> break;
>> }
>>
>> which considers the API feature set as a whole based on the version
>> information. I'm not sure if we used to discuss similar mechanisms or not.
>> Any thought is appreciated. :)
>>
>
> This is not really a webby way of doing things. The falsity check is the
> standard way of feature detection on the web, but it's mo commonly used for
> new features which are not yet available everywhere (or dealing with vendor
> prefixes) not for backwards compat.
>
> That being said, breaking backwards compat is always hard. If you have
> specific cases in mind, please let's discuss them concretely. This is the
> kind of discussion which may be a bit hard to have in the abstract!
>
> Cheers,
> Ehsan
>
>
> ______________________________**_________________
> dev-webapi mailing list
> dev-w...@lists.mozilla.org
> https://lists.mozilla.org/**listinfo/dev-webapi<https://lists.mozilla.org/listinfo/dev-webapi>
>



--
Jeremie
.............................
Web : http://jeremie.patonnier.net
Twitter : @JeremiePat <http://twitter.com/JeremiePat>

Julien Wajsberg

unread,
Oct 17, 2013, 11:53:14 AM10/17/13
to Jeremie Patonnier, Ehsan Akhgari, dev-w...@lists.mozilla.org, Gene Lian
Generally we can also try to implement the new API using the old one
(technique called polyfill) :)

So that the real code does not need to know the new API is not available
on older browser versions.
--
Julien
signature.asc

David Bruant

unread,
Oct 17, 2013, 1:39:27 PM10/17/13
to Gene Lian, dev-w...@lists.mozilla.org
Le 17/10/2013 15:33, Gene Lian a écrit :
> Just firing some thoughts about how to make the content safely call APIs with backward compatibility. As you guys know, it'd be very painful to have API changes and to keep the backward compatibility at the same time. For example, if Gecko wants to modify an API like below:
>
> navigator.fooAPI.oldFunc(...) -> navigator.fooAPI.newFunc(...)
There are plenty of cases where we'd like to re-design APIs. One
recently happened on the WhatWG [1]. That's usually a bad idea as long
as both APIs cover the same functionality.
Is there a concrete use case?

> To avoid breaking the compatibility
Quite rare are the occasions to do that on the web. Is it a case of
privileged/certified API? For these, you can always decide to refuse
apps when they arrive in the marketplace (credit to Jérémie for this idea).

> if (newFunc in navigator.fooAPI) {
> navigator.fooAPI.newFunc(...);
> } else if (oldFunc in navigator.fooAPI) {
> navigator.fooAPI.oldFunc(...);
> }
As a temporary measure, that can work.

> If we had many of them, unfortunately, we'd have lots of run-time checks per function/attribute basis, which doesn't look very clean and efficient.
I agree with Julien, if it's a bunch of renaming or even if the new API
can be implemented on top of the old one, write a polyfill and only use
the new API. The polyfill will be isolated in its own file.

David

[1]
http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041080.html
(getContext being getContext2D + getContextWebGL in workers)

Tim Chien

unread,
Oct 18, 2013, 3:30:20 AM10/18/13
to David Bruant, dev-w...@lists.mozilla.org, Gene Lian
Gene,

Excellent questions :) I am glad you asked.

As many people here had give out answers on detect new API function
names, I will not re-emphasis that.

I want to add, for detect events, we look up properties on the event
target, like |if ('ontouchstart' in window) { ... }|.

Also, there is a famous feature detection library called Modernizr, a
database of feature detection you might say, can be used as a
reference.
https://github.com/Modernizr/Modernizr/tree/master/feature-detects
Dive into HTML5 also give out a nicely crafted table on feature detection.
http://diveintohtml5.info/everything.html

Just FYI.
> _______________________________________________
> dev-webapi mailing list
> dev-w...@lists.mozilla.org
> https://lists.mozilla.org/listinfo/dev-webapi



--
Tim Guan-tin Chien, Engineering Manager and Front-end Lead, Firefox
OS, Mozilla Corp. (Taiwan)

Salvador de la Puente González

unread,
Oct 28, 2013, 8:42:35 AM10/28/13
to Tim Chien, David Bruant, dev-w...@lists.mozilla.org, Gene Lian
Hello folks

IMHO, I think the inclussion of a semver [1] in APIs could improve the
development experience for all us. More now than before becuase now web
APIs evolve at different rates and with no dependency on the JavaScript
version behind. Althought we have techniques such as polyfills proxies I
think it is a great idea to have the version inside each API.

[1] http://semver.org/

Hope it helps.
________________________________

Este mensaje se dirige exclusivamente a su destinatario. Puede consultar nuestra política de envío y recepción de correo electrónico en el enlace situado más abajo.
This message is intended exclusively for its addressee. We only send and receive email on the basis of the terms set out at:
http://www.tid.es/ES/PAGINAS/disclaimer.aspx

Tim Chien

unread,
Oct 31, 2013, 12:06:14 AM10/31/13
to Salvador de la Puente González, dev-w...@lists.mozilla.org, David Bruant, Gene Lian
Versions is an entirely different topic. One of the characteristics of
the web is that there is no one linear version evolutions because
there is more than one implementations. Each implementations choose
their own priorities and do their own experimentation. If you are
looking at versions in APIs that version is only going to be useful if
you include the vendor tag, and it will not take long before you fall
into a situation similar to UA string detections.

Version is also susceptible to distortion, because we all know HTML5
is superior than CSS3 because 5 > 3. And Chrome 30 is better than
Firefox 24.

IMHO feature detection somethings s*x but that's a small price to pay
for the openness we enjoy.
0 new messages