为什么 XMLHttpRequest 的 document.body.innerText 属性与浏览器中的 document.body.innerText 属性不同? 从浏览器控制台为什么两种检索innerText的方法不同?

问题描述

提取document.body.innerText

我目前正在开展一个项目,以改善残障儿童的网络可访问性。但是,我最近在尝试从 XMLHttpRequest 响应中检索 document.body.innerText 属性时遇到了一个巨大的障碍。

XMLHttpRequest (responseXML.body.innerText) 的结果与直接从浏览器控制台调用 document.body.innerText 包含的结果不同。

使用 XMLHttpRequest

这是相关的代码示例,它目前在我的 Chrome 浏览器扩展程序 (Chrome Canary v91.0.4466.0,x86_64) 上运行

    if (!window.XMLHttpRequest) return;

    // Create new request
    var xhr = new XMLHttpRequest();

    // Setup callback
    xhr.onload = function () {
        console.log(this.responseXML.body.innerText));
    }

    // Get the HTML
    xhr.open('GET',searchResultURL);
    xhr.responseType = 'document';
    xhr.send();

在 StackOverFlow 的 Twitter page 上运行上述代码时,您会看到 this.responseXML.body.innerText 的值包含 CSS。

身体{ -ms-overflow-style: 滚动条; 溢出-y:滚动; overscroll-behavior-y:无; }

.errorContainer {
  background-color: #FFF;
  color: #0F1419;
  max-width: 600px;
  margin: 0 auto;
  padding: 10%;
  font-family: Helvetica,sans-serif;
  font-size: 16px;
}

.errorButton {
  margin: 3em 0;
}

.errorButton a {
  background: #1DA1F2;
  border-radius: 2.5em;
  color: white;
  padding: 1em 2em;
  text-decoration: none;
} ...

Full response string

从浏览器控制台

当您在浏览器控制台中检索 document.body.innerText 属性时,您将看到以下值:

"要查看键盘快捷键,请按问号 查看键盘快捷键 家 探索 通知 留言 轮廓 更多的 鸣叫 塞缪尔·艾略特·内桑森 @NathansonEliot 堆栈溢出 3,014 条推文 查看新推文 跟随 堆栈溢出 @堆栈溢出 通过为开发人员和所有技术人员提供服务,帮助编写未来的脚本。 纽约,NYstackoverflow.com 2010 年 4 月加入 14 关注 11.51 万粉丝 没有被您关注的任何人关注 推文 推文和回复 媒体 喜欢 Stack Overflow 的推文 你可能会喜欢 freeCodeCamp.org @freeCodeCamp 跟随 开发社区 @ThePracticalDev 跟随 展示更多 现在趋势 发生了什么 美国职业棒球大联盟 · 居住 红袜队的金莺队 红袜队趋势,加勒特理查兹 政治·趋势 他还活着 38.7K 推文 美国职业棒球大联盟·趋势 塞德里克·穆林斯 新冠肺炎 · 居住 COVID-19:马里兰州的新闻和更新 超级联赛 · 居住 曼联 VS 布莱顿霍夫阿尔比恩 趋势与#MUNBHA,#MUFC 展示更多 服务条款 隐私政策 Cookie 政策 广告信息 更多的 © 2021 Twitter,Inc."

为什么两种检索innerText的方法不同?

这还不清楚,所以如果有更多知识的人提供帮助,那就太好了。

解决方法

来自 MDN documentation of innerText(强调我的):

注意:innerText 很容易与 Node.textContent 混淆,但两者之间存在重要区别。 基本上,innerText 知道文本呈现的外观,而 textContent 不知道。

然而,XHR 对象的响应文档永远不会被渲染,因此它没有渲染的外观。 没有 CSS 应用于文档,甚至浏览器默认设置也没有。 此时,innerTexttextContent 的结果相同。

为了在获取资源的同时从 innerText 中受益,您可以强制浏览器呈现文档,例如在 <iframe> 中。 直接设置 <iframe> 就足够了,即没有任何 XHR 或 Fetch 请求。

const iframe = Object.assign(document.createElement("iframe"),{
    src: searchResultURL
  });

// This CSS ensures that the <iframe> is hidden away,but still rendered.
Object.assign(iframe.style,{
  position: "absolute",top: "-100%"
});
document.body.appendChild(iframe);

// This load listener ensures that the fetched document is rendered (i.e. CSS applied).
iframe.addEventListener("load",({target: {contentDocument: {body: {innerText}}}}) => {
  console.log(innerText); // Use the `innerText` here!
  iframe.remove();
});

请注意,此方法和类似方法仅在 CORS 标头允许访问框架资源时才有效。 否则不容易。

但是,这样做存在安全风险。 您可以尝试使用 sandbox attribute 来降低风险,但您必须禁用 <iframe> 内容的 JavaScript,同时仍允许 same-origin 访问该内容。 这并不能保证内容的 JS 代码会正确运行,获取预期内容还取决于发送的 cookie 和标头是否正确。

最好考虑一种不依赖于 innerText 行为的方法。


即使(可能)没有必要,也可以将 XHR 或更现代的 Fetch 混合到其中,以便您可以看到一些替代方法。

以下代码使用更现代的 Fetch API 而不是您原来的 XHR 方法。

fetch(searchResultURL)
  .then((resp) => resp.text())
  .then((text) => {
    const iframe = Object.assign(document.createElement("iframe"),{
        srcdoc: text
      });
    
    Object.assign(iframe.style,{
      position: "absolute",top: "-100%"
    });
    document.body.appendChild(iframe);
    iframe.addEventListener("load",({target: {contentDocument: {body: {innerText}}}}) => {
      console.log(innerText);
      iframe.remove();
    });
  });

或者,使用您原来的 XHR 方法和更旧的浏览器向后兼容的代码:

  • 删除 xhr.responseType = 'document';

  • 将其用作 load 侦听器(改用 addEventListener):

    xhr.addEventListener("load",function(){
      var iframe = document.createElement("iframe");
    
      iframe.srcdoc = this.responseText;
      iframe.style.position = "absolute";
      iframe.style.top = "-100%";
      iframe.addEventListener("load",function(){
        console.log(this.contentDocument.body.innerText);
        document.body.removeChild(iframe);
      });
      document.body.appendChild(iframe);
    });
    

这仍然需要 srcdoc 在您的浏览器中工作。 或者,还有 Blob API 具有更好的浏览器支持:

  • 在 Fetch 方法中:
    • resp.text()替换resp.blob()
    • .then((text) => {...} 替换为 .then((blob) => {...}
    • srcdoc: text替换src: URL.createObjectURL(blob)
  • 或者在 XHR 方法中:
    • iframe.srcdoc = this.responseText;替换iframe.src = URL.createObjectURL(new Blob([ this.responseText ],{ type: "text/html" }));

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...