如何在加载后从缓存中读取 iframe 的*原始*源

How to read the *original* source of an iframe, after it has loaded, from the cache

最通用的描述方式是我只想触发一个网络请求,只有在客户端看到视口的某个部分后,然后使用它并在 iframe 中显示它,在大多数情况下可能的有效方式。

给定一个 DOM 结构如下:

<!DOCTYPE html>
<html>
  <head />
  <body>
    ...
    <iframe loading="lazy" sandbox="" src="http://www.example.com" />
    <pre />
    ...
  </body>
</html>

我想在 pre 标签中向客户展示上面 iframe 的来源是什么样子的。 iframe 元素可以承载任意文档,它可以是文本的或二进制的,所有已知的是浏览器可以显示它。 iframe 的来源 URL 托管在同一来源。

我的目标是通过 Chromium“view-source:”URL 或类似的方式显示人们会看到的内容。

可能无法访问 .contentWindow.contentdocument 属性,因为它完全是沙盒化的,但即使我可以,文档的 outerHTML 也不够,并且使用XMLSerializer 显然会改变输出。此外,我相信浏览器可以编辑文档的某些区域,例如不必要的空格或格式设置。

我只是尝试了以下方法:

document
.body
.querySelector("iframe")
.addEventListener(
    "load",
    async ( { currentTarget: { src } } ) => {
        const data = await fetch(
            src, {
                cache: "only-if-cached"
            }
        );

        // ... use data
    }, {
        passive: true,
        once: true
    }
);

然而,提取失败。 URL 似乎不在浏览器的缓存中,但我不想发起新的网络请求,有没有一种有效的方法可以做到这一点?

我正在考虑使用 Intersection Observer 作为一个潜在的解决方案,因为它只会导致一个网络请求,但代码很长,而且似乎没有正常工作(我对观察者没有经验).

事件监听器和提取请求可能会发生一些事情。其他读者请注意,截至撰写本文时(2020 年 11 月),仅 Chrome/Chromium、Edge 和 Opera 支持 iframe 中的 lazy loading

此回答针对的是想同时使用延迟加载来动态加载内容的提问者。如果问题只是关于发出单个网络请求,则可以通过获取请求并通过 Blob 或数据 URI 或通过 srcdoc 设置 iframe 的 src 或使用 Service Workers(已探索)来完成下面一点)。

在添加事件侦听器之前可能加载的帧

您在框架声明后向其添加事件侦听器,如果框架在脚本被评估之前加载(如果它被缓存,则可能会出现这种情况),这可能会产生问题。

通常,您可以通过 contentWindowcontentDocument 属性 查看您的框架是否已经加载,如果是 运行 您在这些框架上的初始化代码,但由于框架是沙盒化的,因此无法访问这些属性。您可以做的是提前声明您的处理程序,然后在创建 iframe 时声明它:

async function loader(frame) {
    console.log(frame.src);
    // ...
}
<iframe src="frame.html" loading="lazy" sandbox="" onload="loader(this)"></iframe>

在获取请求中使用同源

According to MDN,only-if-cached只能在mode设置为same-origin时使用同源。提取请求看起来像:

await fetch(src, { mode: 'same-origin', cache: 'only-if-cached' });

结果

main.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      .spacer {
        height: 200vh;
        background: lightgray;
        text-align: center;
      }
    </style>
    <script>
      async function loader(frame) {
        var sourceCodeElementId = frame.dataset.code;
        var sourceCodeElement = document.getElementById(sourceCodeElementId);
        var response = await fetch(frame.src, {
          mode: 'same-origin',
          cache: 'only-if-cached'
        });
        if (response.status === 504) {
          // if it didn't load via cache, just get it normally
          response = await fetch(frame.src);
        }
        var text = await response.text();
        sourceCodeElement.textContent = text;
      }
    </script>
  </head>
  <body>
    <div class="spacer">Scroll Down</div>
    <iframe src="frame.html"
        loading="lazy"
        data-code="frame-source-code"
        sandbox=""
        onload="loader(this)">
    </iframe>
    <pre id="frame-source-code"></pre>
  </body>
</html>

样本frame.html

<!doctype html>
<html>
Frame
</html>

Optionally/Alternatively 使用 Service Worker

如果您担心的是网络请求,您可以使用 Service Workers, alternatively, or additionally to the above fixes. It takes a bit of work to get them up and running, but they can give you a finer control over network requests. Caching files 是一个常见的教程,因此如果您采用该路线,您应该能够找到相当数量的资源。