【问题标题】:Running a script in the context of an HTML custom element在 HTML 自定义元素的上下文中运行脚本
【发布时间】:2020-11-11 13:52:10
【问题描述】:

我正在编写一些 HTML5 自定义元素,其中包含被转译的代码,以便它可以在页面上实际运行(图形代码、乳胶等),但我似乎找不到任何页面或 Stackoverlow 问题,这解释了是否以及如何加载脚本,使其范围限定为自定义元素本身。

相反,我所能找到的只是“根本没有信息”,所以我使用了有点傻的“将它插入文档头部,重写,以便它首先从全局寄存器中获取正确的元素”,这是超级肮脏的。我工作,但如果能够以一种运行它们的方式将脚本注入到影子 DOM 中,并且知道它们正在运行的确切元素是什么,那就更好了。

现在,代码(简化形式)如下所示:

import { uuid } from "./uuid.js";
import { Parser } from "./code-parser.js";

class MyElement extends HTMLElement {
  constructor() {
    super();

    this.uid = uuid.v4();
    window[this.uid] = this;

    const jsCode = `
      import { Base, API, ... } from "./lib.js";
      class Example extends Base {
         constructor() {
           super(window[${this.uid}]);
           delete window[${this.uid}];
         }
         ${Parser.rewrite(this.textContent)}
      }
      new Example();
    `;

    const script = document.createElement(`script`);
    script.type = `module`;
    script.src = URL.createObjectURL(
      new Blob([jsCode], {type: `text/javascript`})
    );

    this.attachShadow({ mode: 'open' }).append(script);
  }
}

customElements.define(`my-element`, MyElement);
export { MyElement }

这当然有效,但通过(暂时)污染window 起作用。有没有办法将脚本元素附加到自定义元素或自定义元素的影子 DOM,以便在知道它正在运行的自定义元素或影子 DOM 的情况下执行?

编辑: 明确地说,这需要适用于使用依赖modules 的现代代码的脚本:任何import 语句仍然需要解决。另请注意,任何依赖于需要将unsafe-eval (6.1.11.3) 或unsafe-inline 添加到CSP script-src 指令的方法的东西都不能使用。

【问题讨论】:

  • 为什么你认为你必须附加一个新的
  • 因为实际脚本中的代码在任何地方都不存在。它是动态生成的。代码以字符串数据的形式存在,被重写为正确的 JS 并且 then 被注入。没有要导入的完整模块。
  • (我正在pomax.github.io/custom-graphics-element 上解决这个问题,以防您想了解与此问题无关但可能与您的兴趣相关的完整事件链)
  • 对不起,我真的没有时间完全检查你的 repo,但是快速阅读它,你不需要在
  • 不幸的是,我不能:理智的 CSP(例如,没有 unsafe-eval)不允许 eval()new Function() 以及鲜为人知的字符串执行形式 setTimeoutsetInterval。此元素旨在在任意站点上运行,因此它仍然必须在具有正常 CSP 的站点上运行。现在需要unsafe-inline 已经相当有问题了,我可能不得不将其切换为src="blob:..." 注入。无论如何,最初的问题仍然存在:注入影子 DOM 的脚本如何获得对该影子 DOM 或影子 DOM 所有者的引用?

标签: html custom-element


【解决方案1】:

更新 #2

如果 CSP 正在运行,以下解决方法将不起作用
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP



你可以注入 SCRIPT 元素,但肯定不是一帆风顺。

问题是SCRIPT 获得window 范围,而不是您的自定义元素范围

免责声明:
以下代码适用于 Chredge 和 FireFox。
但是我的直觉告诉我有一个潜在的问题,我就是无法解决这个问题

要选择正确的范围,您必须添加一个知道其在 shadowDOM 中位置的 TAG,并且可以使用 .getRootNode().host 获取自定义元素

<style onload=console.log(this)> 是候选者,但由于某种原因,它只会针对页面上的一个元素执行

所以我切换到<img src onerror=console.log(this)>

下面的 SO sn-p 无法正确处理此代码。
这是一个 JSFiddle:https://jsfiddle.net/CustomElementsExamples/g134yp7v/

<template id=scriptContainer>

  <!--start-->
    <script>
      console.log("script",this); // window
      function run(scope) {
        const element = scope.getRootNode().host;
        element.ran();
      }
    </script>
    <img src onerror="run(this)">
  <!--end-->

</template>
<script>
  customElements.define('my-element',
    class extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode: 'open'})
            .append(scriptContainer.content.cloneNode(true));
      }
      ran() {
        const pre = document.createElement("PRE");
        pre.innerHTML = this.shadowRoot.innerHTML.replace(/</g,"&lt;");
        this.shadowRoot.append("shadowRoot content: " , this.id , pre );
      }
    });
</script>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>

更新 #1

是的,我知道有些地方不太对劲

   function run(scope) {
     const element = scope.getRootNode().host;
     element.ran();
   }

成为window. 全局函数,被页面中的每个新my-element 覆盖。

代码仍然有效,只是不要在函数中存储任何本地(元素)内容。
并且也许给它一个非常晦涩的函数名。

使用正确的自定义元素范围执行添加的 SCRIPT

为了不创建全局变量,将所有代码填充为 IMG onerror(INLine 脚本!!)中的 IIFE,
箭头函数确保正确的范围:

  <img src onerror="(()=>{
    this.onerror = null;// prevent endless loop if function generates an error
    const element = this.getRootNode().host;
    console.log('IIFE',element);
  })()">

OR 作为自定义元素方法:

  runScript(script) {
    const span = document.createElement("SPAN");
    const onerror = `this.onerror=null;const element=this.getRootNode().host;` + script;
    span.innerHTML = `<img src onerror="${onerror}">`;
    this.shadowRoot.append(span);
    setTimeout(()=>span.remove());
  }

致电:this.runScript(`console.log(666,element.id)`);

游乐场位于:https://jsfiddle.net/CustomElementsExamples/g134yp7v/

输出:

IMG IIFE 在正确的范围内运行

最后 2 个控制台行来自:

TWO.runScript(`console.log(${this.id}.id,'runs in',element.id)`);//duplicate line in console!
TWO.runScript(`console.log(${this.id}.id,'runs in',element.id)`);//duplicate line in console!

当您在自定义元素上调用 runScript 方法时,演示脚本在正确的范围内运行

【讨论】:

  • 嗯,如果让脚本甚至执行插入到影子 dom 中的所有内容,这可能会打开一种可能获得正确范围的方法,让我看看我是否可以使用你的第一个代码也可以让它发挥作用。
  • 又玩了一些,但问题并不是真正让脚本“根本”运行,而是整个“它到底是如何找到它的“所有者”而没有通过window" =(
  • 这就是需要 IMG 的原因,它将触发具有正确 this 范围(IMG)的onerror(或onload)事件所以从那里你可以做this.getRootNode().host播放我链接到的JSFiddle。是的,这是一个大写 W 的解决方法。但它有效
  • 嗯,看起来情况并非如此:如果我运行未修改的 jsbin,我会看到控制台日志显示 ONE 的脚本在两个(两个日志)中运行,并且脚本TWO 在 TWO 中运行(也是两个日志),因此当页面上存在多个自定义元素的实例时,这似乎无法可靠地工作。
  • 我在上面添加了截图;重要的部分是 IIFE 日志具有正确的范围那些TWO.runscript 行是一个奖励;他们创建 4 条日志行 所有 4 条在元素 TWO 范围内运行 证明当您调用 element.runScript( ... ) 时脚本在正确的范围内运行@ 不需要 ``runScript 方法,我添加它以显示可扩展性。我知道这个 IMG/范围变通办法有效;已经在 MS-SharePoint 世界中使用了很多年,在那里我们的脚本注入可能性有限
猜你喜欢
  • 2014-06-30
  • 2021-07-31
  • 2017-11-07
  • 1970-01-01
  • 1970-01-01
  • 2011-07-29
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多