Dev : Extensions : Cross-Version Compatibility Techniques: Difference between revisions

From MozillaZine Knowledge Base
Jump to navigationJump to search
No edit summary
(No difference)

Revision as of 19:53, 4 December 2004

Cross-Version Compatibility Techniques for Extensions

Introduction

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 (expected to include Firefox 1.1, Thunderbird 1.1, Sunbird 0.2)

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).

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;
   }
   ...
 };