Dev : Extensions : Cross-Version Compatibility Techniques

From MozillaZine Knowledge Base
Revision as of 14:18, 24 October 2005 by Asqueella (talk | contribs) (→‎Introduction)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

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-), the column parameter is the id= attribute (a string) of the corresponding <treecol> element.
  • column.id string identifies <treecol>: In Mozilla 1.8 and later (1.8+), the column parameter is an object, and its .id property is the id= 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 a column parameter and an element parameter.
  • In Mozilla 1.8 and later (1.8+) the cycleHeader method passes just a column 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.