Pull to refresh

Особенности кроссдоменного скриптинга на субдоменах с XML в Opera и некоторых других

Reading time5 min
Views1.1K
Недавно проделана работа по написанию пользовательского скрипта (браузеры Firefox, Chrome и Opera), в котором понадобилось обращаться к документу XML, находящемуся в старшем домене 2-го уровня из домена 3-го уровня. Работа открыла взгляд на некоторые особенности поведения браузеров, особенно, Оперы, причины которых до конца не выяснены. Но, так как такой скриптинг (чтение и запись в документы XML в наддомене) иногда необходим, хотел бы поделиться практическими результатами и показать открытые вопросы.

Условия задачи.


Есть страница HTML с не строгим Doctype (transitional), которая должна получить данные из некоторого XML, который, к тому же, мы не можем менять и вписывать в него свои скрипты (как если бы он был XHTML). Страницу HTML тоже не можем менять, внедряя, например, XML, а управляем всеми действиями лишь через пользовательский скрипт, запускаемый в момент onload. Первая мысль — нет ничего проще, классическая задача с XMLHttpRequest и перестройкой DOM по нашим нуждам.

Да, если бы XML был на том же домене, то нет ничего проще. Но домен другой, поэтому AJAX-request тут был бы возможен, но сложно-избыточным образом: 1) загрузить какой-то существующий простой HTML из наддомена в фрейм, 2) вписать скриптом в него скрипт AJAX, 3) прочитать XML по AJAX, 4) прочитать скриптом из поддомена полученное. Зачем делать 2 чтения, если можно обойтись одним чтением в фрейм одного XML? Подгружать код в фрейм не надо, только читать — тоже проще. Поэтому исключаем AJAX как метод, сопровождающий сложный способ и пробуем делать простым.

Чтение JS-данных и DOM-документа в наддомене, как известно, можно делать, указав document.domain в поддомене, равным старшему домену:

document.domain = 'сайт.ру';

Действительно, в FF и Chrome работа по этой схеме происходит без особенностей, и можно было бы об ней не писать, но в Опере возникли странные нерешённые проблемы (может быть, от XML?), которые были просто обойдены, но они экспериментально наблюдаются. О них и некоторых побочных результатах в Firefox — эта статья.

Поскольку скрипты пользовательские, IE в исследовании делать было нечего.

Работающий пример скрипта можно посмотреть в Firefox+GreeaseMonkey, Chrome или Opera, установив скрипт, описанный в статье (последнюю версию 1.3).

Процесс работы скрипта.


Будем говорить только о сути — о той части скрипта, которая относится к вопросу кроссдоменного доступа.

Мы полностью отказались от AJAX на XMLHttp, и теперь надо сделать фрейм, в который подгружается XML.

 if(!document.getElementsByName('ifr').length){ //создание фрейма
  var ifr=document.createElement('iframe');
  ifr.setAttribute('name', 'ifr');
  ifr.src = 'http://habrahabr.ru/api/profile/'+username+'/';
  ifr.style.display='none';
  document.body.appendChild(ifr);
 }

XML имеет такую структуру:

<?xml version="1.0"?>
<habrauser>
<login>spmbt</login>
<karma>24</karma>
<rating>59.3</rating>
<ratingPosition>1038</ratingPosition>
</habrauser>

Затем включаем счётчик периодических попыток чтения структуры XML, потому что событие onload в чужом фрейме ловить не можем. (На самом деле, в Опере это возможно, код и пояснения ниже, но пробы чтения дали ещё худшие результаты, о них позже.)

  win.habrKarmView.ii=20; //число попыток прочитать фрейм
  win.habrKarmView.ww = setInterval(showValue, 300);

Тут начинается самое интересное. Во-первых, чтобы не иметь ошибок, приходится, как сапёру, проверять дерево шаг за шагом (конечно, можно и ловить ошибки в try-catch).

  var f = document.getElementsByName('ifr');
  if(f && f[0] && f[0].contentDocument && f[0].contentDocument.getElementsByTagName('login')
    && f[0].contentDocument.getElementsByTagName('login')[0]
    && f[0].contentDocument.getElementsByTagName('karma')[0]){
   if( (f[0].contentDocument.u == username || !f[0].contentDocument.u) && self.opera
     || !self.opera && f[0].contentDocument.getElementsByTagName('login')[0].childNodes[0].nodeValue == username ){
    ...тело функции showValue - отображаем полученные из XML данные...
   }
  }

Почему во втором операторе if пришлось разделить «Оперную» и не-Оперную части? Потому что в Опере мы смогли записать в документ XML переменную u.

    if(self.opera) document.getElementsByName('ifr')[0].contentDocument.u = username;

В FF этого сделать не смогли (почему — вопрос открытый), но этого не очень хотелось, потому что есть вторая часть условия: длинное выражение, читающее ноду в теге — это тот же запомненный username, который читался в FF/Chrome без проблем. Что же за проблемы были в Опере?

Проблемы (вопрос второй) были странные и слабообъяснимые. Пытаясь читать логин вторым способом, по нодам, а не записанный заранее, получали существование ноды с логином, getElementsByTagName('login')[0], но отсутствие .childNodes — текста в логине. Т.е. так, как будто бы XML был вида

<habrauser>
<login />
<karma>24</karma>
<rating>59.3</rating>
<ratingPosition>1038</ratingPosition>
</habrauser>

Не помогали ни задержки, ни танцы типа чтения getElementsByTagName('habrauser')[0].childNodes[1] ([1] — потому что там текстовые ноды на месте переносов строк). <login /> казался пустым с точки зрения Оперы. Почему — второй открытый вопрос. (В фрейме он был непустым и виделся, если не писать ifr.style.display='none'; .)

Несмотря на непонятность поведения первой ноды, пришлось скрипт оставить для Оперы в таком виде — по случайному совпадению работоспособных альтернатив, мы всё равно получили заменитель — переменную u в документе. Но решение в общем виде, если бы <karma> стояла первой, было бы для Оперы невыполнимой задачей (если решать этим путём).

Наконец, третий вопрос и особенность Оперы и FF. В документе «Web Technologies for Opera Web Applications» я подсмотрел хак для подключения onload к фрейму. Что интересно, вид DOM-документа, видимого в момент onload, был ещё хуже. Не виделись ноды не только с логином, но и с кармой. Получается приблизительно такой эффект, как неточный выбор момента onload, но не совсем так — нода login[0].firstChild не видится продолжительное время, если не сказать, что всегда. Что с этим всем делать и как избежать? Может быть, Опера «захлёбывается» в длинной череде проверок нод и надо их делать как-то иначе? Никто не сталкивался с этой ситуацией?

Как будет видеться произвольный документ в Опере через ноды — теоретический вопрос. Пока на него нет желания отвечать, потому что непонятен сам смысл происходящего, поэтому предсказывать поведение и прощупывать результаты некуда.

Полезные знания и выводы.


1. Опера умеет писать в наддомен в фрейме через contentDocument и contentWindow новые переменные. Firefox не умеет, но через contentWindow при этом не выдаёт ошибку — просто undefined. (Вызов фрейма — в ипостаси документа: document.getElementsByName('ifr'), поэтому правильнее было бы обращение через contentDocument, но интересно, что FF не срабатывает, выдавая uncaught error.)
  if(self.opera)
   document.getElementsByName('ifr')[0].contentDocument.u = username;


2. Опера умеет делать хак для onload чужого документа, чтобы выполнить код в поддомене после формирования документа, Firefox не умеет. (Хотя пользы для XML у Оперы оказалось мало.)

  var ifr=document.createElement('iframe');
  ifr.src = 'http://habrahabr.ru/api/profile/'+username+'/';
  ifr.style.display='none';
  ifr.onload = function(){
   ...;
  }
  document.body.appendChild(ifr);


3. С Оперой или кодом проверки существования ноды, который приводит к эффекту нечитаемости первой текстовой ноды, в то время, когда она существует, надо разобраться — как правильно обращаться к нодам, не есть ли это влияние кроссдоменности, встречали ли другие разработчики подобные эффекты.
Tags:
Hubs:
Total votes 10: ↑8 and ↓2+6
Comments0

Articles