通过删除 '<script type=“text/plain”... />' 的 type 属性延迟脚本加载,在 firefox 上不起作用

Delayed script loading by removing type attribute of `<script type="text/plain"... />` not working on firefox

提问人:Mo W. 提问时间:3/24/2023 更新时间:3/24/2023 访问量:188

问:

我目前正在为 NextJS 开发自定义 cookie 同意解决方案,它似乎已经很好地与这种方法配合使用:

在同意后应该加载的 head 中的所有脚本都是这样实现的: 阻止浏览器加载和执行脚本。 同意后,我删除属性,克隆节点,删除原始节点并将其附加到 .否则,浏览器不会注意到更改。<script src="iSetSomeCookies.js" type="text/plain" data-cookieconsent />type="text/plain"type<script><head>

这种方法适用于 Safari 和 Chrome,但不适用于 Firefox...... 代码被执行,属性也被删除并添加为新的脚本节点。但是在网络选项卡中,脚本之后不会加载。type

有没有办法让 Firefox 重新解析 DOM 并加载脚本?

这是我的脚本中在给予 cookie 同意后执行的部分:

const blockedScripts = document.querySelectorAll<HTMLScriptElement>(
  "script[data-cookieconsent]"
)
blockedScripts.forEach((script) => {
  script.removeAttribute("type")
  const copy = script.cloneNode()
  document.head.insertBefore(copy, script)
  script.remove()
})

我还尝试将类型更改为 and,但这仍然是相同的行为,Firefox似乎忽略了该更改。text/javascriptapplication/javascript

我已经看到了一些可用于在之后执行脚本的解决方案,但我不想使用 .eval(this.innerHtml)eval

javascript html dom firefox 浏览器

评论

0赞 T.J. Crowder 3/24/2023
“代码被执行,type 属性也被删除并添加为新的脚本节点。”在代码运行以更新节点之前还是之后?
0赞 dwi kristianto 3/24/2023
为什么不使用链接预加载/预取,然后在满足同意后将其转换为实际的脚本标签?
1赞 Mo W. 3/24/2023
对于“代码...”,我的意思是遍历 -nodes 的 forEach 循环,而不是脚本节点本身的代码。我描述它的方式可能有点令人困惑。<script>
0赞 Mo W. 3/24/2023
@dwikristianto我不确定这是否能解决我的问题。这里的问题不在于该机制没有修改脚本标签。上面的解决方案适用于 Safari 和 Chrome。只是 firefox 没有检测到头部添加/修改的脚本标签。

答:

1赞 T.J. Crowder 3/24/2023 #1

有趣的是,Firefox不会运行克隆脚本引用的代码(我可以验证我用你的原始代码看到了这种行为)。我认为最简单的事情就是创建新节点,这似乎有效:

const blockedScripts = document.querySelectorAll("script[data-cookieconsent]");
for (const blocked of blockedScripts) {
    const { parentElement, nextSibling } = blocked;
    blocked.remove();
    const script = document.createElement("script");
    for (const attr of blocked.attributes) {
        if (attr.name !== "type") {
            script.setAttributeNode(attr.cloneNode());
        }
    }
    parentElement.insertBefore(script, nextSibling);
}

该代码也不假定元素在元素中;相反,它使用原始元素的任何父元素。scripthead

这里是 TypeScript (必须在克隆属性后添加,因为 的返回类型是 ,并添加一个 以说服 TypeScript 不是:as AttrcloneNodeNodeifparentElementnull

const blockedScripts = document.querySelectorAll<HTMLScriptElement>("script[data-cookieconsent]");
for (const blocked of blockedScripts) {
    const { parentElement, nextSibling } = blocked;
    if (!parentElement) { // Will never be true
        throw new Error(`parentElement is null`);
    }
    blocked.remove();
    const script = document.createElement("script");
    for (const attr of blocked.attributes) {
        if (attr.name !== "type") {
            script.setAttributeNode(attr.cloneNode() as Attr);
        }
    }
    parentElement.insertBefore(script, nextSibling);
}

您可能更喜欢 non-nullish 断言运算符 on;我真的不喜欢使用它,但这里有一个很好的论据:parentElement

const blockedScripts = document.querySelectorAll<HTMLScriptElement>("script[data-cookieconsent]");
for (const blocked of blockedScripts) {
    const { parentElement, nextSibling } = blocked;
    blocked.remove();
    const script = document.createElement("script");
    for (const attr of blocked.attributes) {
        if (attr.name !== "type") {
            script.setAttributeNode(attr.cloneNode() as Attr);
        }
    }
    parentElement!.insertBefore(script, nextSibling);
}

评论

1赞 Mo W. 3/24/2023
我稍微修改了你的答案,但通常使用这个解决方案它可以工作!我想而不是做诀窍。document.createElement("script")script.cloneNode()
0赞 Naveed Ahmed 10/25/2023
这种方法是否也适用于 type=“module”?对我来说,脚本文件已下载,但似乎没有执行。