简体   繁体   English

如何从扩展程序检测 Google Chrome 中的页面标题更改?

[英]How to detect page title change in Google Chrome from an extension?

I'm creating a Google Chrome extension and I need to detect when a page's title changes.我正在创建一个 Google Chrome 扩展程序,我需要检测页面标题何时发生变化。 The page's title is changed like in Twitter: (num) Twitter (see the screenshot below) - when a new tweet is posted, the number increments.页面标题的更改与 Twitter 类似: (num) Twitter (见下面的屏幕截图)- 当发布新推文时,数字会增加。 Example:例子:

在此处输入图片说明

I'm trying to detect the title changes of a URL that's loaded in one of my tabs and play a beep sound whenever there's a difference.我正在尝试检测加载到我的一个选项卡中的 URL 的标题更改,并在出现差异时播放哔声。 This check is to be done in a repeated interval and I think that can be accomplished using setTimeOut() function.此检查将在重复的时间间隔内完成,我认为可以使用setTimeOut()函数来完成。

I've created a manifest.json as follows:我创建了一个manifest.json如下:

{
  "manifest_version": 2,

  "name": "Detect Page Title Changes",
  "description": "Blah",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "background.html"
  },
  "permissions": [
    "tabs"
  ]
}

However, I'm clueless about the rest.但是,我对其余的一无所知。 I've searched through the docs 1 2 and tried the solutions on similar Stack Overflow threads such as this one I but couldn't find anything that suits my requirements.我搜索了文档1 2并在类似的 Stack Overflow 线程上尝试了解决方案,例如这个我,但找不到任何适合我的要求的东西。

Do you have any suggestions?你有什么建议吗? Please include an example, if possible.如果可能,请提供一个示例。

Instead of arguing in comments that a certain approach is better, let me be more constructive and add an answer by showing a particular implementation I co-wrote myself, and explain some gotchas you may run into.与其在评论中争论某种方法更好,不如让我更有建设性,并通过展示我自己共同编写的特定实现来添加答案,并解释您可能遇到的一些问题 Code snippets refer to a service different from Twitter, but the goal was the same.代码片段指的是与 Twitter 不同的服务,但目标是相同的。 In fact, this code's goal is to report the exact number of unread messages, so yours might be simpler.实际上,此代码的目标是报告未读消息的确切数量,因此您的目标可能更简单。

My approach is based on an answer here on SO, and instead of being polling-driven (check condition at fixed intervals) is event-driven (be notified of potential changes in condition).我的方法基于 SO 上的答案,而不是轮询驱动(以固定时间间隔检查条件)是事件驱动(通知条件的潜在变化)。

Advantages include immediate detection of a change (which would otherwise not be detected until the next poll) and not wasting resources on polls while the condition does not change.优点包括立即检测变化(否则直到下一次轮询才会检测到),并且不会在条件不变的情况下在轮询上浪费资源。 Admittedly, the second argument hardly applies here, but the first one still stands.诚然,第二个论点在这里几乎不适用,但第一个论点仍然成立。


Architecture at a glance:架构概览:

  1. Inject a content script into the page in question.将内容脚本注入相关页面。

  2. Analyze initial state of the title, report to background page via sendMessage .分析标题的初始状态,通过sendMessage报告给后台页面。

  3. Register a handler for a title change event.为标题更改事件注册处理程序。

  4. Whenever the event fires and the handler is called, analyze the new state of the title, report to background page via sendMessage .每当事件触发并调用处理程序时,分析标题的新状态,通过sendMessage报告给后台页面。


Already step 1 has a gotcha to it.步骤 1 已经有问题了。 Normal content script injection mechanism, when the content script is defined in the manifest, will inject it in pages upon navigation to a page that matches the URL.正常的内容脚本注入机制,当在清单中定义内容脚本时,会在导航到与 URL 匹配的页面时将其注入页面。

"content_scripts": [
  {
    "matches": [
      "*://theoldreader.com/*"
    ],
    "js": ["observer.js"],
    "run_at": "document_idle"
  }
]

This works pretty well, until your extension is reloaded .这很有效,直到您的扩展程序被重新加载 This can happen in development as you're applying changes you've made, or in deployed instances as it is auto-updated.这可能发生在开发中,因为您正在应用您所做的更改,或者在部署的实例中,因为它是自动更新的。 What happens then is that content scripts are not re-injected in existing open pages (until navigation happens, like a reload).然后发生的是内容脚本不会重新注入现有的打开页面(直到导航发生,如重新加载)。 Therefore, if you rely on manifest-based injection, you should also consider including programmatic injection into already-open tabs when extension initializes:因此,如果您依赖基于清单的注入,您还应该考虑在扩展初始化时将编程注入包含到已打开的选项卡中:

function startupInject() {
  chrome.tabs.query(
    {url: "*://theoldreader.com/*"},
    function (tabs) {
      for (var i in tabs) {
        chrome.tabs.executeScript(tabs[i].id, {file: "observer.js"});
      }
    }
  );
}

On the other end, content script instances that were active at the time of extension reload are not terminated, but are orphaned : any sendMessage or similar request will fail.另一方面,在扩展重新加载时处于活动状态的内容脚本实例不会终止,而是孤立的:任何sendMessage或类似请求都将失败。 It is, therefore, recommended to always check for exceptions when trying to communicate with the parent extension, and self-terminate (by removing handlers) if it fails:因此,建议在尝试与父扩展通信时始终检查异常,如果失败则自行终止(通过删除处理程序):

try {
  chrome.runtime.sendMessage({'count' : count});
} catch(e) { // Happens when parent extension is no longer available or was reloaded
  console.warn("Could not communicate with parent extension, deregistering observer");
  observer.disconnect();
}

Step 2 also has a gotcha to it, though it depends on the specifics of the service you're watching.第 2 步也有一个问题,尽管这取决于您正在观看的服务的具体情况。 Some pages inside the scope of the content script will not show the number of unread items, but it does not mean that there are no new messages.内容脚本范围内的某些页面不会显示未读项目的数量,但这并不意味着没有新消息。

After observing how the web service works, I concluded that if the title changes to something without navigation, it's safe to assume the new value if correct, but for the initial title "no new items" should be ignored as unreliable.在观察 Web 服务的工作方式后,我得出结论,如果标题更改为没有导航的内容,如果正确,假设新值是安全的,但对于初始标题“没有新项目”应该被忽略为不可靠。

So, the analysis code accounts for whether it's the initial reading or handling an update:因此,分析代码考虑了它是初始读取还是处理更新:

function notify(title, changed) {
  // ...
  var match = /^\((\d+)\)/.exec(title);
  var match_zero = /^The Old Reader$/.exec(title);

  if (match && match[1]) {
    count = match[1];
  } else if (match_zero && changed) {
    count = 0;
  }
  // else, consider that we don't know the count
  //...
}

It is called with the initial title and changed = false in step 2.在第 2 步中使用初始标题调用它并changed = false


Steps 3 & 4 are the main answer to "how to watch for title changes" (in an event-driven way).第 3 步和第 4 步是“如何观察标题更改”的主要答案(以事件驱动的方式)。

var target = document.querySelector('head > title');

var observer = new window.MutationObserver(
  function(mutations) {
    mutations.forEach(
      function(mutation){
        notify(mutation.target.textContent, true);
      }
    );
  }
);

observer.observe(target, { subtree: true, characterData: true, childList: true });

For specifics as to why certain options of observer.observe are set, see the original answer .有关为什么设置了某些observer.observe选项的详细信息,请参阅原始答案

Note that notify is called with changed = true , so going from "(1) The Old Reader" to "The Old Reader" without navigation is considered to be a "true" change to zero unread messages.请注意, notify是使用changed = true调用的,因此从“(1) The Old Reader”到“The Old Reader”没有导航被认为是对零未读消息的“真实”更改。

Put chrome.tabs.onUpdated.addListener in your background script:chrome.tabs.onUpdated.addListener放在您的后台脚本中:

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
   console.log(changeInfo);
});

changeInfo is an object which includes title changes, eg here: changeInfo是一个包含标题更改的对象,例如:

这个

Can then filter on the object so that an action only occurs if changeInfo includes a title change.然后可以对对象进行过滤,以便只有在changeInfo包含标题更改时才会发生操作。 For additional manipulation, eg responding to page title changes with page content / actions, you can send a message to content script from inside the listener after whatever conditions are met.对于其他操作,例如响应页面内容/操作的页面标题更改,您可以在满足任何条件后从侦听器内部向内容脚本发送消息。

  1. Create an event page .创建一个活动页面

  2. Create a content script that gets injected into a webpage when a webpage loads.创建一个在网页加载时注入网页的内容脚本

  3. Within the content script, use setInterval to poll the page to see if window.document.title changes.在内容脚本中,使用setInterval轮询页面以查看window.document.title更改。

  4. If the title has changed, use chrome.runtime.sendMessage to send a message to your event page.如果标题已更改,请使用chrome.runtime.sendMessage向您的活动页面发送消息。

  5. On your event page, listen for messages with chrome.runtime.onMessage and play a sound .在您的活动页面上,使用chrome.runtime.onMessage收听消息并播放声音

After researching Chrome's tabs API, it doesn't look like anything stands out to help you directly.在研究了 Chrome 的 tabs API 之后,看起来没有什么可以直接帮助你。 However, you should be able to attach an event listener to the title node of the tab(s) you're interested in. The DOMSubtreeModified mutation event works in Chrome, and a quick test in a normal html document proves to work for me - should be no different from within an extension.但是,您应该能够将事件侦听器附加到您感兴趣的选项卡的标题节点。 DOMSubtreeModified 突变事件在 Chrome 中有效,并且在普通 html 文档中的快速测试证明对我有用 -应该与扩展内没有什么不同。

var title = document.getElementsByTagName('title')[0];

if (title) {
    title.addEventListener('DOMSubtreeModified', function (e) {
        // title changed
    }, false);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM