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



考虑以下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)中会出现以下错误:

未捕获的DOMException:未能从"HTMLObjectElement"读取"contentDocument"属性:阻止了具有原点的帧"https://fiddle.jshell.net"访问跨原点帧。在setTimeout(https://fiddle.jshell.net/_display:77:19)

考虑到这一点;

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

  2. 有没有一种方法可以在不违反浏览器跨源策略的情况下以这种方式动态生成SVG?

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

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

WHATWG规范描述了如何访问内容文档,其中包括跨来源检查。WHATWG同源比较永远不会将传统方案主机端口"元组"原点视为等于"不透明"data:原点。

相反,使用BlobURL.createObjectURL生成一个相同来源的临时URL,其内容将被外部环境读取:

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

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

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

其他选项包括:

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

  2. 完全放弃<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>
    

    并且它将如您所期望的那样在CCD_ 16中呈现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();
    

    但是DOM模型将与<object>中呈现的SVG分离。要更改<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中的组件具有交互性,我认为这种方法是行不通的。CCD_ 24与其周围页面之间的分隔具有与CCD_ 25相同的嵌入关系。

因为object标记在HTML文档中定义了一个嵌入的对象,它不是文档本身的一部分,因此必须像框架一样尊重CORS

同源政策

这里明确指出,对象标签的内容被视为外部资源

HTML元素表示外部资源,可以将其视为图像、嵌套浏览上下文或插件处理的资源。

最新更新