如何编写将内容脚本应用于单页面应用程序的Chrome扩展?

我目前正在编写一个扩展,它将把与RegEx模式(特别是德国电话号码的RegEx模式)匹配的每个字符串转换为可点击的链接,方法是在元素的href中添加tel:,并在需要时使用锚标记将其括起来。

在过去的三天里,我一直在努力让我们公司用于他们联系人的One Single Page App正常工作。但每当我将我的扩展加载到浏览器中,然后重新启动浏览器时,它一开始并不应用我的内容脚本。访问页面后,我首先需要刷新页面。

我正在使用以下文件:

清单.json:

{
  "name": "testExtension",
  "short_name": "testExtension",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "Replace telephone numbers with clickable links.",
  "author": "Ngelus",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "browser_action": {
    "default_icon": "icons/icon48.png",
    "default_title": "testExtension"
  },
  "default_locale": "en",
  "permissions": [
    "tabs",
    "activeTab",
    "<all_urls>",
    "*://*.examplecontactsapp.de/*",
    "storage",
    "webRequest",
    "webNavigation"
  ],
  "content_scripts": [
    {
      "matches": [
        "*://web.examplecontactsapp.de/contacts",
        "*://web.examplecontactsapp.de/contacts*"
      ],
      "js": ["src/inject/contentscript.js"]
    }
  ],
  "background": {
    "scripts": ["src/bg/background.js"],
    "persistent": true
  }
}

后台.js:

let currentUrl = '';
let tabId;

chrome.webRequest.onCompleted.addListener(
  function (details) {
    const parsedUrl = new URL(details.url);

    if (currentUrl && currentUrl.indexOf(parsedUrl.pathname) > -1 && tabId) {
      chrome.tabs.sendMessage(tabId, { type: "pageRendered" });
    }
  },
  { urls: ['*://web.examplecontactsapp.de/contacts*'] }
);

chrome.webNavigation.onHistoryStateUpdated.addListener(
  (details) => {
    tabId = details.tabId;
    currentUrl = details.url;
  },
  {
    url: [
      {
        hostSuffix: 'examplecontactsapp.de',
      },
    ],
  }
);

ContScript.js:

var readyStateCheckInterval = setInterval(function () {
  if (!location.href.startsWith('https://web.examplecontactsapp.de/contacts')) return;
  if (document.readyState === 'complete') {
    clearInterval(readyStateCheckInterval);
    console.log(
      `[---testExtension---]: replacing all telephone numbers with clickable links...`
    );
    replaceNumbersWithLinks();
  }
}, 10);

chrome.runtime.onMessage.addListener(function (request) {
  if (request && request.type === 'pageRendered') {
    const readyStateCheckInterval = setInterval(function () {
      if (document.readyState === 'complete') {
        clearInterval(readyStateCheckInterval);
        console.log(
          `[---testExtension---]: replacing all telephone numbers with clickable links...`
        );
        replaceNumbersWithLinks();
      }
    }, 10);
  }
});

function replaceNumbersWithLinks() {
  document
    .querySelectorAll(
      'body > main > div.page-content > div > table > tbody > tr > td > p > a'
    )
    .forEach((a) => {
      var b = a.innerText.replaceAll(' ', '').trim();
      a.removeAttribute('data-phonenumber');
      if (b != '') a.href = 'tel:' + b;
    });
}

这样做的正确方式是什么? 如何在我每次访问该单页面应用程序时使其工作?

提前感谢大家。


解决方案

使matches匹配清单中的整个站点.json:

{
  "content_scripts": [
    {
      "matches": [
        "*://web.examplecontactsapp.de/*"
      ],
      "js": ["src/inject/contentscript.js"]
    }
  ],

使用MutationWatch,并使用中等速度的querySelector检查Conentscript.js中每个突变的选择器,然后继续执行速度较慢的querySelectorAll on Success:

const SEL = 'a[data-phonenumber]';
const mo = new MutationObserver(onMutation);
observe();

function onMutation() {
  if (document.querySelector(SEL)) {
    mo.disconnect();
    replaceNumbersWithLinks();
    observe();
  }
}

function observe() {
  mo.observe(document, {
    subtree: true,
    childList: true,
  });
}

function replaceNumbersWithLinks() {
  document.querySelectorAll(SEL).forEach((a) => {
    a.removeAttribute('data-phonenumber');
    const b = a.textContent.replaceAll(' ', '').trim();
    if (b) a.href = 'tel:' + b;
  });
}

相关文章