如何从 DOM 获取数据、处理数据然后在 Chrome 扩展 MV3 中发回

How to fetch data from DOM, process it and then send it back in Chrome Extension MV3

我不知道什么是最好的表达方式,但我决定在这里做一个 post,因为我已经搜索了几天但找不到确切的答案我想做。

所以,基本上我正在尝试开发一个简单的 Chrome 扩展来检查 Reddit 上的用户是否是机器人。扩展将:

起初我试图在我的 background.js:

中做这样的事情
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === 'complete'){

        chrome.scripting.executeScript({
            target: { tabId: tabId},
            func: getAuthors,
        },
        (authors) =>{
            authors.forEach(element => {
                getAbout(element).then(about =>{
                    getComments(element).then(comments =>{
                        isBot(about,comments).then(response =>{   
                           //ADD TAG
                        });
                    });
                });
            });
        })
    }
});

函数getAuthors能够成功访问文档并检索数据,当我必须再次访问它时出现问题附加标签。

存储一组作者并将其传递给另一个 chrome.scripting.executeScript 不起作用,因为执行不会等待第一个 chrome.scripting.executeScript完成并立即执行。

我试过反转逻辑并使用消息传递,这次是在我的 contentScript.js:

chrome.runtime.sendMessage('get-names', (names) => {
   console.log('received user data', names);

   names.forEach(element => {
      element.parentElement.appendChild(tag);
   });
});

现在我有办法附加标签,但我无法首先发送数据来处理它,因为据我所知,您只能在 回应。 如果我改为从 background.js 发送消息,我最终会陷入与 scripting 及其异步执行类似的情况。

有没有简单的方法来做到这一点?

虽然可以使用后台脚本来实现此任务,但更简单更好的解决方案是在内容脚本中执行所有操作。

内容脚本可以立即对 DOM 进行更改,并且可以向当前站点的 URL 发出网络请求以获取用户的详细信息。但是网络请求将是异步的,否则会在页面加载中引入不愉快的延迟。

整体方案如下:

  1. 当 DOM 为空时,内容脚本在 document_start 加载。
  2. 它读取以前存储的关于它过去已经检查过的用户的数据。这里最好的存储是网站自己的 window.localStorage,因为它是同步的并且属于同一个内部选项卡进程,因此读取速度很快。
  3. 使用 MutationObserver 检测页面加载时添加的 <a> 元素以及之后添加的元素以支持新的 reddit UI(它动态构建页面)和各种 userscripts/extensions 自动扩展旧版 reddit 中的话题 UI.
  4. 为每个匹配元素获取缓存数据。
  5. 如果不存在,进行网络请求,应用结果,存储在存储中。

manifest.json:

{
  "content_scripts": [{
    "matches": ["*://www.reddit.com/*", "*://old.reddit.com/*"],
    "js": ["content.js"],
    "css": ["content.css"],
    "run_at": "document_start"
  }],

content.css:

a.is-bot::after {
  content: '[BOT]';
  color: red;
  margin-left: .25em;
}

content.js:

const CLASS = 'is-bot';
const LS_KEY = 'b:';
const USER_URL = location.origin + '/user/';
const SEL = `a[href^="/user/"], a[href^="${USER_URL}"]`;
const checkedElems = new WeakSet();
const promises = {};
new MutationObserver(onMutation)
  .observe(document, {subtree: true, childList: true});

function onMutation(mutations) {
  for (const {addedNodes} of mutations) {
    for (const n of addedNodes) {
      const toCheck = n.tagName === 'a'
        ? n.href.startsWith(USER_URL) && [n]
        : n.firstElementChild && n.querySelectorAll(SEL);
      for (const el of toCheck || []) {
        if (checkedElems.has(el)) continue;
        checkedElems.add(el);
        const name = el.href.slice(USER_URL.length);
        const isBot = localStorage[LS_KEY + name];
        if (isBot === '1') {
          el.classList.add(CLASS);
        } else if (isBot !== '0') {
          (promises[name] || (promises[name] = checkUser(el, name)))
            .then(res => res && el.classList.add(CLASS));
        }
      }
    }
  }
}

async function checkUser(el, name) {
  const isBot = await (await fetch(API_URL + name)).json();
  localStorage[LS_KEY + name] = isBot ? '1' : '0';
  delete promises[name];
  return isBot;
}