为什么使用HTMLObjectElement动态生成SVG会导致跨源错误?

gpm*_*dam 18 html javascript dom cors

请考虑以下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);
Run Code Online (Sandbox Code Playgroud)

(有关完整示例,请参阅此JSFiddle)

运行时,控制台(Google Chrome)中会出现以下错误:

未捕获的DOMException:无法从'HTMLObjectElement'读取'contentDocument'属性:阻止具有源" https://fiddle.jshell.net " 的帧访问跨源帧.at setTimeout(https://fiddle.jshell.net/_display:77:19)

考虑到这一点;

  1. 当尝试访问contentDocument完全动态创建的对象而没有外部资源时,为什么这被视为跨源请求?

  2. 有没有办法以这种方式动态生成SVG,而不会冒犯浏览器的跨源策略?

aps*_*ers 14

这里的问题是data:URL被视为具有与创建嵌入data:上下文的上下文的原点不同的唯一来源:

注意:现代浏览器将数据URL视为唯一的不透明原点,而不是继承负责导航的设置对象的来源.

WHATWG规范描述了如何访问内容文档,其中包括跨源检查.在WHATWG同源比较绝不会像传统方案主机端口"元组"起源等于"不透明" data:的起源.

相反,使用Blobwith URL.createObjectURL生成同源的临时URL,其内容可由外部环境读取:

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

我不知道原始data:URL 不允许这种方法的安全原因,但它似乎确实有效.(我想因为生成的URL只能由生成它的原点读取,而data:URL不知道如何只能通过其原始上下文的原始读取.)

另请注意,某些版本的Internet Explorer支持createObjectURL但错误地将生成的URL视为具有空原点,这会导致此方法失败.

其他选择是:

  1. 不要使用data:URL,而是使用与创建<object>元素的页面相同的源来提供SVG内容.

  2. 抛弃<object>contentDocument完全使用内联<svg>元素(小提琴):

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

    大多数浏览器都支持内联<svg>元素(特别是IE 9.0+;其他浏览器更早).这意味着你可以做到

    <div>
        <svg>
            ...
        </svg>
    </div>
    
    Run Code Online (Sandbox Code Playgroud)

    它只会<div>像你期望的那样将SVG文档呈现在内部.

  3. 根据您要对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();
    
    Run Code Online (Sandbox Code Playgroud)

    但是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)}`);
    
    Run Code Online (Sandbox Code Playgroud)

    这似乎不是超级高效的,因为它需要浏览器重新解析一个全新的SVG文档,但它将绕过安全限制.

    可以将其<object>视为可以接收SVG信息进行渲染的单向黑洞,但不会将任何信息暴露回来.然而,这不是一个信息问题,因为你得到的信息只是你所提供的<object>:没有任何东西contentDocument可以告诉你,你还不知道.

    但是,如果您想通过将侦听器附加到主页上执行代码的SVG结构中的组件来在SVG交互中创建组件,我认为这种方法不起作用.a <object>与其周围页面之间的分离具有与之相同的嵌入关系<iframe>.