Почему динамическая генерация SVG с использованием HTMLObjectElement приводит к ошибке Cross-Origin?

Рассмотрим следующий фрагмент кода JavaScript:

const app = document.getElementById('root');
const svg = `<svg version="1.1" id="Layer_1"...`;
const obj = document.createElement('object');

obj.setAttribute('type', 'image/svg+xml');
obj.setAttribute('data', `data:image/svg+xml; base64,${btoa(svg)}`);

app.appendChild(obj);

setTimeout(() => {
  console.log(obj.contentDocument.querySelector('svg'));
}, 1500);

(см. этот JSFiddle для полного примера)

При запуске в консоли (Google Chrome) отображается следующая ошибка:

Uncaught DOMException: Не удалось прочитать свойство contentDocument из "HTMLObjectElement": заблокирован кадр с источником " https://fiddle.jshell.net" из доступ к кадру скрещивания.     at setTimeout (https://fiddle.jshell.net/_display:77:19)

С учетом этого;

  • Почему это считается запросом на кросс-начало при попытке доступа к contentDocument объекта, который был создан полностью динамически, без внешних ресурсов?

  • Есть ли способ генерировать SVG динамически таким образом, не нарушая политику перекрестного происхождения браузеров?

Ответы

Ответ 1

Проблема заключается в том, что data: URL-адреса рассматриваются как имеющие уникальное происхождение, которые отличаются от источника контекста, который создал встроенный data: контекст:

Примечание. URL-адреса данных считаются уникальными непрозрачными источниками современных браузеров, а не наследуют происхождение объекта настроек, ответственного за навигацию.

Спецификация WHATWG описывает способ доступа к документам контента, который включает проверку перекрестного происхождения. Сравнение WHATWG одинакового происхождения никогда не будет относиться к традиционному происхождению кортежа традиционной схемы-хозяина как к "непрозрачному" data: происхождению.

Вместо этого используйте Blob с URL.createObjectURL для создания временного URL-адреса того же происхождения, содержимое которого будет доступно для чтения внешней средой:

var svgUrl = URL.createObjectURL(new Blob([svg], {'type':'image/svg+xml'}));
obj.setAttribute('data', svgUrl);

Я не знаю причины безопасности, почему этот подход разрешен, а raw data: - нет, но он работает. (Я думаю, потому что сгенерированный URL-адрес доступен только для источника, который сгенерировал его, тогда как URL data: не знает, как читать только оригинал его исходного контекста.)

Обратите внимание, что некоторые версии Internet Explorer поддерживают createObjectURL, но ошибочно обрабатывают сгенерированные URL-адреса как имеющие нулевое начало, что приведет к сбою этого подхода.

Другие варианты:

  • Не используйте URL data: и вместо этого используйте SVG-контент из того же источника, что и ваша страница, которая создает элемент <object>.

  • Отключить <object> и contentDocument в целом и вместо встроенный <svg> элемент (fiddle):

    const obj = document.createElement('div');
    obj.innerHTML = svg;
    app.appendChild(obj);
    setTimeout(() => {
      console.log(obj.querySelector('svg'));
    }, 1500);
    

    Большинство браузеров поддерживают встроенные элементы <svg> (особенно IE 9.0+, другие браузеры намного раньше). Это означает, что вы можете сделать

    <div>
        <svg>
            ...
        </svg>
    </div>
    

    и он просто отобразит документ SVG внутри <div>, как и следовало ожидать.

  • В зависимости от того, что вы хотите сделать с SVG, вы можете загрузить его в DOMParser и выполнить DOM-исследование/манипуляции внутри парсера.

    var oParser = new DOMParser();
    var svgDOM = oParser.parseFromString(svg, "text/xml");
    console.log(svgDOM.documentElement.querySelector('path'));
    svgDOM.documentElement.querySelector('path').remove();
    

    Но модель DOM будет отделена от SVG, представленной в <object>. Чтобы изменить <object>, вам необходимо сериализовать проанализированную структуру DOM и повторно нажать ее в свойстве data:

    var oSerializer = new XMLSerializer();
    var sXML = oSerializer.serializeToString(svgDOM);
    obj.setAttribute('data', `data:image/svg+xml; base64,${btoa(sXML)}`);
    

    Это не кажется суперэффективным, потому что браузер нуждается в повторном анализе совершенно нового документа SVG, но он обойдутся ограничениями безопасности.

    Подумайте о <object> как односторонней черной дыре, которая может получать информацию SVG для рендеринга, но не будет выводить какую-либо информацию обратно. Однако это неинформационная проблема, поскольку у вас есть информация, которую вы только что подали в <object>: ничего, что contentDocument не может сказать вам, что вы еще не знаете.

    Однако, если вы хотите сделать компоненты в SVG-интерактивном, подключив слушателей к компонентам в структуре SVG, которые выполняют код на главной странице, я не думаю, что этот подход будет работать. Разделение между <object> и его окружением страницы имеет такое же отношение вложения, как и <iframe>.

Ответ 2

потому что тег объекта определяет внедренный объект в документе HTML, он не является частью самого документа и поэтому должен уважать CORS как фрейм

Политика одинакового происхождения

здесь четко заявляет, что содержимое тега объекта считается внешним ресурсом

Элемент HTML представляет внешний ресурс, который можно рассматривать как изображение, вложенный контекст просмотра или ресурс, который должен обрабатывать плагин.