自己偵測瀏覽器對於API的支援

 Mon, 04 Oct 2010 10:31:57 +0800

最近開始想要用自己寫程式的方式,透過規格文件來學習HTML5。不過看過規格,發現許多API還是繼承自DOM規格,所以還是需要花時間回頭看一下DOM3的相關文件。

記得在John Resig及PPK的Blog或是書裡面,有看過一些對於瀏覽器相容性的建議,主要是關於不要用瀏覽器來決定是否支援某項功能,而應該直接偵測某項功能在某個瀏覽器是否可以使用。基於這樣的想法,之前也寫了個小程式來偵測瀏覽器對於html5 tag的支援。其實想法很簡單,只是從標準文件上找到某個物件具備的properties,然後測試這些東西實際在瀏覽器中是否存在。

從ECMA-262 Edition3裡面物件的properties就有[DontEnum]屬性,如果有這個屬性,使用for...in就無法列舉出來。不過只是不會列舉,並不是無法使用。之前偵測IE的window物件就有碰到這樣的問題,IE8把許多東西都設為[DontEnum],所以用for..in來偵測的話,很多功能就跑不出來。這樣,就得想辦法自己列舉該有的properties,檢查他是否在物件中為undefined。

HTML5中,所有的元素都是HTMLElement,這個Interface繼承了DOM的Element,而Element又繼承了DOM的Node Interface。同樣,在其他規格中也與DOM有關,例如Drag & Drop中,事件會繼承DOM3 Event的核心Event Interface,有這些線索,就可以根據規格一路追過來。因為要處理許多Interface,在寫程式的過程中就歸納了一些自己會用到的函數:

function row(cells) {
  return '<tr>' + cells.join('') + '</tr>\n';
}
function cell(text, style, col) {
  return '<td' + (col? ' colspan="'+col+'"':'') + (style? ' style="'+style+'"':'') + '>' + text + '</td>';
}
function DOMInspector(title, obj, attr) {
  var ret = {
    'title':title,
    'SupportedAttributes':[],
    'UnsupportAttributes':[],
    'SupportedMethods':[],
    'UnsupportMethods':[]
  };
  for(var i=0; i<attr.attr.length; i++) {
    if(obj[attr.attr[i]] !== undefined) {
      ret.SupportedAttributes.push(attr.attr[i]);
    } else {
      ret.UnsupportAttributes.push(attr.attr[i]);
    }
  }
  for(var i=0; i<attr.mthd.length; i++) {
    if(obj[attr.mthd[i]] !== undefined) {
      ret.SupportedMethods.push(attr.mthd[i]);
    } else {
      ret.UnsupportMethods.push(attr.mthd[i]);
    }
  }
  return ret;
}
function resultParser(test) {
  var str = '<table cellpadding="2" cellspacing="0" border="1">';
  for(var i in test) {
    if(i == 'title') {
      str += row([cell(test[i],null,2)]);
    } else {
      str += row([cell(i),cell(test[i].length>0? test[i].join('<br>'):' ')]);
    }
  }
  str += '</table>';
  return str;
}
有了這幾個函數,我在需要偵測瀏覽器對於DOM3 Core中定義的Node Interface時,只要針對這個interface的規格寫好一個property的對應就可以了:

function DOM3NodeInspector(obj) {
  var map = {
    attr : [
    'nodeName',
    'nodeValue',
    'nodeType',
    'parentNode',
    'childNodes',
    'firstChild',
    'lastChild',
    'previousSibling',
    'nextSibling',
    'attributes',
    'ownerDocument',
    'namespaceURI',
    'prefix',
    'localName',
    'baseURI',
    'textContent'
    ],
    mthd : [
    'insertBefore',
    'replaceChild',
    'removeChild',
    'appendChild',
    'hasChildNodes',
    'cloneNode',
    'normalize',
    'isSupported',
    'compareDocumentPosition',
    'isSameNode',
    'lookupPrefix',
    'isDefaultNamespace',
    'lookupNamespaceURI',
    'isEqualNode',
    'getFeature',
    'setUserData',
    'getUserData'
    ]
  };
  return DOMInspector('DOM3 Node Interface support.', obj, map);
};
接著可以呼叫resultParser把結果用html印出:
var obj = document.getElementById('test');
document.getElementById('panel').innerHTML = resultParser(DOM3NodeInspector(obj));
這樣就可以把結果放入到某個元素的innerHTML裡面來呈現。

例如,在Firefox4 Beta6上面跑的結果:

DOM3 Node Interface support.
SupportedAttributesnodeName
nodeValue
nodeType
parentNode
childNodes
firstChild
lastChild
previousSibling
nextSibling
attributes
ownerDocument
namespaceURI
prefix
localName
baseURI
textContent
UnsupportAttributes 
SupportedMethodsinsertBefore
replaceChild
removeChild
appendChild
hasChildNodes
cloneNode
normalize
isSupported
compareDocumentPosition
isSameNode
lookupPrefix
isDefaultNamespace
lookupNamespaceURI
isEqualNode
getFeature
setUserData
getUserData
UnsupportMethods 
在Chrome7裡面跑出來像這樣:
DOM3 Node Interface support.
SupportedAttributesnodeName
nodeValue
nodeType
parentNode
childNodes
firstChild
lastChild
previousSibling
nextSibling
attributes
ownerDocument
namespaceURI
prefix
localName
baseURI
textContent
UnsupportAttributes 
SupportedMethodsinsertBefore
replaceChild
removeChild
appendChild
hasChildNodes
cloneNode
normalize
isSupported
compareDocumentPosition
isSameNode
lookupPrefix
isDefaultNamespace
lookupNamespaceURI
isEqualNode
UnsupportMethodsgetFeature
setUserData
getUserData

嗯...看起來Chrome7對於DOM3-Core中Node的支援比Firefox4 Beta6的稍微差一些。