Pull to refresh

DOM-shim для всех браузеров включая IE < 8

Reading time4 min
Views12K
Доброе время суток уважаемые хабражители.

Многие javascript-программисты сталкивались с не поддерживанием некоторых функций DOM JS API в некоторых браузерах (не будем показывать пальцем). Наверняка, многие знакомы с замечательными библиотеками es5-shim и DOM-shim для решения проблем совместимости между разными браузерами, а DOM-shim к тому же, «подтягивает» браузер до уровня DOM4.

В данной же статье я расскажу, как сделать DOM-shim в IE6 и IE7, чтобы навсегда забыть о существовании этих браузеров.

Проблемма


Тут и рассказывать нечего. IE ранее 9й версии не поддерживает очень много из стандартного DOM API:
  • addEventListener
  • removeEventListener
  • dispachEvent
  • classList
  • и т.д.

И если IE8 можно «подтянуть» имплементировав необходимый функционал с помощью DOM-shim, то IE6 и IE7, к сожалению, не поддерживаются этой библиотекой (на 24.11.2011 issues/29) из-за того, что в IE < 8 нету Node.prototype.

Решение


Собственно, решение достаточно очевидное и я использовал его в своих проектах еще год назад, когда только начал изучать javascript — behavior: url(.htc) [Introduction to DHTML Behaviors]. Но тогда мне пришлось отказаться от этого решения, потому что, это очень-очень сильно тормозило загрузку страницы — секунд 30 приходилось ждать, уже после того, как страница загрузилась для относительно небольшой страницы.
Спустя какое-то время, случайно наткнулся на информацию о том, как ускорить раз в 100 загрузку страниц с использованием behavior — оказывается нужно просто добавить lightweight=true и всё будет работать достаточно быстро.
Теперь сделать DOM-shim для IE < 8 не составит никакого труда, что я и сделал.

Приступим


Для начала нам понадобятся оригинальные es5-shim и DOM-shim, берём их и переделываем, исправляем баги и улучшаем эмуляцию Object.defineProperty:
<...>
if (!object.__defineGetter__) {
  if(descriptor["ielt8"]) {
    object["get" + property] = descriptor["get"];
    object["set" + property] = descriptor["set"];
  }
else throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
<...>

Вместо того, чтобы просто выбрасывать исключение ERR_ACCESSORS_NOT_SUPPORTED мы проверим на наличие специального флага и если он есть, сохраним геттер и сеттер под специальными именами.
Т.к. в IE < 8 нету Node.prototype (который и передаётся в Object.defineProperty), создадим специальный объект, в который будет аккумулировать наши геттеры и сеттеры:
var elementProto = window.HTMLElement && window.HTMLElement.prototype || 
      /*ie8*/window.Element && window.Element.prototype || 
      /*ielt8*/(window["_ielt8_Element_proto"] = {});

Передавать мы его будем как обычно, только добавим в description флаг «ielt8»:
Object.defineProperty(elementProto, "classList", {"get" : ..., "ielt8" : true}

Теперь мы собираем все геттеры нашей shim-библиотеки в один объект. Остаётся только «навесить» его на все элементы на странице.
Создадим dom-shim.ielt8.htc файл:
<PUBLIC:COMPONENT lightWeight="true">
<PUBLIC:PROPERTY NAME="classList" GET="getClassList" />
<SCRIPT>
getClassList = window._ielt8_Element_proto.getclassList
</SCRIPT>
</PUBLIC:COMPONENT>

и добавим его ко всем элементам на странице:
* {
 behavior: url(dom-shim.ielt8.htc)
}

И собственно всё. Теперь у нас есть Node.classList даже в IE6!

Аналогично добавляются следующие свойства:
  • addEventListener
  • removeEventListener
  • dispatchEvent
  • attributes (исправлено)
  • children (исправлено)
  • firstElementChild
  • lastElementChild
  • nextElementSibling
  • previousElementSibling
  • childElementCount
  • querySelectorAll
  • querySelector
  • insertAfter (нестандартная)
  • getElementsByClassName
  • compareDocumentPosition
  • DOCUMENT_POSITION_DISCONNECTED
  • DOCUMENT_POSITION_PRECEDING
  • DOCUMENT_POSITION_FOLLOWING
  • DOCUMENT_POSITION_CONTAINS
  • DOCUMENT_POSITION_CONTAINED_BY

Мелочи

1. Я исправляю attributes, для этого оригинальный attributes сохраняю в специальный контейнер в начале htc скрипта:
if(!this._)this._={};
//Save original attributes property
this._.__ielt8_attributes__=this.attributes;

Так вот, в IE6 реализация behavior отличается от IE7: в IE7 в скрипте доступны оригинальные переопределяемые в <PUBLIC:PROPERTY NAME="attributes" GET="getAttributes" /> свойства, а в IE6 недоступны — сразу вызывается геттер getAttributes.
Поэтому для IE6 делаем отдельный файл.

2. Нельзя навешивать behavior на все элементы ("*") т.к. в IE < 8 он навешивается на какой-то не поддерживаемый элемент и происходит ошибка. Для исправления, просто нужно указать все html теги в style:
<style>
html,body,div,span,object,iframe,h1,<...все html теги...>,textarea,input,select {
 behavior: url(dom-shim.ielt8.htc)
}
</style>


Итого


Ссылка на библиотеку: github.com/termi/ES5-DOM-SHIM
Хочу заметить, что моя библиотека для DOM-shim сильно отличается от DOM-shim в силу того, что я разрабатывал её последние 8 месяцев ничего не зная про DOM-shim (и даже про github :))

Рабочий пример можно скачать тут: github.com/termi/Microdata-JS (качаем, заходим в examples/microdataTemplater, открываем templaterTest.html)
Предложение добавить то, что получилось в DOM-shim: github.com/Raynos/DOM-shim/issues/29

update 27.11.2011:

Ограничения


.htc-файлы должны находится на том же домене, что и .html-файл. Например для файла example.org/index.html, .htc-файл должен находится на example.org, а для файла test.example.org/index.html на test.example.org. Это ограничение существенно усложняет использование библиотеки — нельзя просто выложить .htc-файл на статик-сервер и забыть о нём. Обязательно нужно убедится в наличии всех нужных .htc-файлов на тот же домен, где и сайт.
Same-domain limitation
update 19.12.2011:
Решение Same-domain limitation через nginx proxy
Tags:
Hubs:
+39
Comments13

Articles

Change theme settings