Dev : Extensions : Cross-Version Compatibility Techniques
Contents
- 1 Cross-Version Compatibility Techniques for Extensions
Cross-Version Compatibility Techniques for Extensions
Introduction
- See also Adapting XUL Applications for Firefox_1.5 document on Devmo.
The simplest way to distribute an extension is to have a single version of the code for everyone. However, Mozilla program interfaces may change when new major versions are released. Interface changes challenge extension developers trying to maintain a single code base that installs and works across different Mozilla versions.
Fortunately, JavaScript is very forgiving, so writing code which works across interface changes can be straightforward. Behavior differences may sometimes be handled by choosing techniques that work in both versions. Parameter changes can be handled as follows:
- Type: If the type of a parameter changes, a method can still receive the parameter, and just needs code to detect whether the old type or new type was received, and act accordingly.
- Add/Omit Last: If an additional parameter is added or omitted at the end, a method can still provide a name for the parameter, and detect whether the name is bound to a value or not to act accordingly. (The same technique can be used to implement optional parameters with default values.)
- Add/Omit Non-last: If an additional parameter is added or omitted before the end, the method can bind parameters to positional names, then detect whether the full number of parameters was provided, and bind parameters to names accordingly.
Rarely is it necessary to branch into separate cases based on
navigator.userAgent.match("rv:([\\d.]+)")[1]
.
Below are some specific techniques that may be of use to extension authors dealing with particular changes.
Mozilla 1.7/1.8 Compatibility Techniques
Techniques for changes between:
- Mozilla 1.7.x (includes Firefox 1.0, Thunderbird 1.0)
- Mozilla 1.8.x (includes Firefox 1.5, Thunderbird 1.5)
getElementsByAttribute
: Traverse elements backwards when modifying eligibility
Change: getElementsByAttribute
result list changed
(bug 240186):
- 'Dead' result list: In Mozilla 1.7 and earlier (1.7-),
getElementsByAttribute
returned a 'dead' array of elements based on the state of the elements at the time the call was made. - 'Live' result list: In Mozilla 1.8 and later (1.8+),
getElementsByAtribute
returns a 'live' list of elements, whose contents change as soon as the elements change
So for example, to uncollapse all the collapsed elements your code might work as follows:
// 1.7- code: var elements = document.getElementsByAttribute("collapsed","true"); for (var i = 0; i < elements.length; i++) elements[i].setAttribute("collapsed", "false");
However, the 1.7- code won't work in 1.8+ because elements is a 'live' list. When you set the collapsed attribute to false, it is removed immediately from the list and all the elements after it shift left by one index, so incrementing i skips the next element.
The recommended way to do this in 1.8+ is as follows:
// 1.8+ code: var elements = document.getElementsByAttribute("collapsed","true"); while(elements.item(0)) // use method syntax to emphasize it is live elements.item(0).setAttribute("collapsed", "false");
However, the 1.8+ code won't work in 1.7- because in 1.7- elements is 'dead' list. Setting the collapsed attribute to false does not remove it from the list, so this will loop forever.
For compatibility with both 1.7- and 1.8+, traverse the list from the end toward the front, so the loop operates the same no matter whether later indexes are changed or not.
// 1.7-/1.8+ compatibility: elements is live in 1.8+ so traverse backwards var elements = document.getElementsByAttribute("collapsed","true"); for (var i = elements.length - 1; i >= 0; i--) elements.item(i).setAttribute("collapsed", "false");
The same applies if you modify element eligibility indirectly, say by removing the element from its parent.
// 1.7-/1.8+ compatibility: elements is live in 1.8+ so traverse backwards var elements = document.getElementsByAttribute("collapsed","true"); for (var i = elements.length - 1; i >= 0; i--) { var element = elements.item(i); element.parentNode.removeChild(element); }
<Tree>
(Table) parameter changes
The view object of an xul Tree
(also implements tables and
treetables) must implement methods such as as getCellText(row,
column)
. Some of its method parameters changed in Mozilla 1.8
(bug 221619).
A long post on mozdev mailing list detailing the changes
column/column.id (getCellText, cycleHeader
, etc.): test typeof column
Change: column
parameter of getCellText, cycleHeader
,
etc. changed into an object
:
column
string identifies <treecol>: In Mozilla 1.7 and earlier (1.7-), thecolumn
parameter is theid=
attribute (astring
) of the corresponding<treecol>
element.column.id
string identifies <treecol>: In Mozilla 1.8 and later (1.8+), thecolumn
parameter is anobject
, and its.id
property is theid=
of the corresponding<treecol>
element.
In Mozilla 1.7-, code might be written as follows:
var data = ...; document.getElementById("TreeId").view = { getCellText : function(row, column) { var datum = data[row]; // 1.7- code: switch( column ) { case "city-column": return data.city; case "country-column": return data.country; default: return null; } } };
The 1.7- code will fail in Mozilla 1.8+ because in 1.8+ the column is an
object
, not a column id=
attribute
string
.
In Mozilla 1.8+, code might be written as follows:
var data = ...; document.getElementById("TreeId").view = { getCellText : function(row, column) { var datum = data[row]; // 1.8+ code: switch( column.id ) { case "city-column": return data.city; case "country-column": return data.country; default: return null; } } };
The 1.8+ code will fail on Mozilla 1.7- because in 1.7- the
column
is a string
, not an
object
, so it has no .id
property.
For compatibility with 1.7 and 1.8, test whether the column
is an object
and use its .id
if so, otherwise
use the column
as the id:
var data = ...; document.getElementById("TreeId").view = { getCellText : function(row, column) { var datum = data[row]; // 1.8+ code: switch( typeof(column)=="object" ? column.id : column ) { case "city-column": return data.city; case "country-column": return data.country; default: return null; } } };
Testing typeof(column)=="object"
avoids strict JavaScript
warnings that appear with other approaches, such as using
column.id || column
(warning accessing nonexistent
property .id
).
cycleHeader(column [, element])
: test for presence of element
parameter
Change: element
parameter removed, moved to
column.element
- In Mozilla 1.7 and earlier (1.7-) the
cycleHeader
method passed acolumn
parameter and anelement
parameter. - In Mozilla 1.8 and later (1.8+) the
cycleHeader
method passes just acolumn
parameter, which is an object with an element property.
In 1.7-, the element was passed as a parameter:
// 1.7- code var data = ...; document.getElementById("TreeId").view = { cycleHeader : function(column, element) { ... } };
In 1.8+, the element is a property of the column:
// 1.8+ code var data = ...; document.getElementById("TreeId").view = { cycleHeader : function(column) { var element = column.element; ... } };
For compatibility, provide an element column, which will be bound only if the parameter is passed in 1.7-, and if it is unbound bind it to the column.element.
// 1.7-/1.8+ compatibility var data = ...; document.getElementById("TreeId").view = { cycleHeader : function(column, element) { // element passed only in Moz1.7- if (! element) element = column.element; //in Moz1.8+, get element from col ... } };
nsIContentHandler.handleContent: test if parameter4 is bound
Change: aCommand
parameter removed
(bug 239604)
In Mozilla 1.7 and earlier (1.7-), nsIContentHandler.handleContent
passes four parameters:
handler.handleContent = function(aContentType, aCommand, aWindowTarget, aRequest) { ... };
In Mozilla 1.8 and later (1.8+), the aCommand
parameter
is no longer passed, so there are three parameters
handler.handleContent = function(aContentType, aWindowTarget, aRequest) { ... };
For compatibility, provide four parameters, and test whether the fourth parameter was bound.
handler.handleContent = function(aContentType, param2, param3, param4) { var aWindowTarget, aRequest; if (param4 === undefined) {// Moz1.8+ uses 3 params: (aContentType, aWindowTarget, aRequest) aWindowTarget = param2; aRequest = param3; } else {// Moz1.7- uses 4 params: (aContentType, aCommand, aWindowTarget, aRequest) aWindowTarget = param3; aRequest = param4; } ... };
Extension and Theme IDs
Unlike Firefox and Thunderbird 1.0.x, versions 1.5b1 and above enforce the addon ID format specification. Only extensions and themes whose IDs are valid GUIDs or email-like strings will successfully install. See the documentation on Devmo for details of the formats. (Note that GUIDs must only use hexadecimal digits; other letters are not permitted.)
Any extensions or themes installed into Firefox or Thunderbird 1.0.x whose IDs are not compatible with the standard format will be completely ignored by versions 1.5. They will not appear in the Extensions or Themes managers; users will not be able to use the addons nor automatically update to newer versions. This is documented in bug 307517.
End users whose extensions or themes have been ignored in this way must reinstall a corrected version of the addon, that includes a proper ID.
Addon authors should check their addons' IDs are valid, and correct them if they are not, as soon as possible. Bug 271270 will ensure that all addons submitted to AMO have valid IDs.
(Greg K Nicholson wrote more about this.)
Existing addons with invalid IDs
These addons will simply disappear when upgrading to 1.5 (including 1.5 beta releases). Please add others as you discover them.
- Most of clav's extensions (not Link Toolbar)