如何找出 Chrome 扩展程序中附加到特定 HTML 元素的事件侦听器类型?

发布于 2024-12-27 14:50:54 字数 4004 浏览 1 评论 0 原文

我在这里发布这个问题,因为我无法将其发布在官方 Chromium 扩展论坛上(或者在它被审核之前有一个很大的延迟)。我必须在 Chromium 扩展中检查是否有特定事件类型的侦听器附加到任意 HTML 元素。在 Firefox 中,我可以使用以下服务来获取此信息:

var listenerService = Components.classes["@mozilla.org/eventlistenerservice;1"]
            .getService(Components.interfaces.nsIEventListenerService);
var infos = listenerService.getListenerInfoFor(element, {});
var types = [];
for ( var i = 0; i < infos.length; ++i) {
  var info = infos[i].QueryInterface(Components.interfaces.nsIEventListenerInfo);
  types.push(info.type);
}

正如我在 Chromium 中看到的,没有类似的 API。因此我尝试过 以下技术(此处建议) ):

我创建了脚本 events_spy.js

(function(original) {
  Element.prototype.addEventListener = function(type, listener, useCapture) {
    if (typeof (this._handlerTypes) == 'undefined') {
      this._handlerTypes = {};
    }
    this._handlerTypes[type] = true;
    return original.apply(this, arguments);
  }
})(Element.prototype.addEventListener);

(function(original) {
  Element.prototype.removeEventListener = function(type, listener,useCapture) {
    if (typeof (this._handlerTypes) != 'undefined') {
      delete this._handlerTypes[type];
    }
    return original.apply(this, arguments);
  }
})(Element.prototype.removeEventListener);

我在 manifest.json 中声明此脚本,如下所示:

"content_scripts" : [{
  "matches" : [ "http://*/*", "https://*/*" ],
  "js" : [ "content/events_spy.js" ],
  "run_at" : "document_start",
  "all_frames" : true
},
...
]

然后,我在以下 HTML 页面:

<!DOCTYPE html>
<html>
 <head>
 </head>
 <body>
  <a id="test" href="#">Click here</a>
  <script type="application/javascript">
       document.getElementById("test").addEventListener("click", function()
{ alert("clicked"); }, false);
  </script>
 </body>
</html>

不幸的是,这不起作用 - 我看不到调试器停止 在我的自定义 addEventListener() 函数内部。我做错了什么?

谢谢!

编辑:最终(脏)解决方案,感谢@kdzwinel

var injectedJS = "\
(function(original) { \
  Element.prototype.addEventListener = function(type, listener, useCapture) { \
    var attr = this.getAttribute('_handlerTypes'); \
    var types = attr ? attr.split(',') : []; \
    var found = false; \
    for (var i = 0; i < types.length; ++i) { \
      if (types[i] == type) { \
        found = true; \         
        break; \                
      } \               
    } \         
    if (!found) { \
      types.push(type); \
    } \         
    this.setAttribute('_handlerTypes', types.join(',')); \
    return original.apply(this, arguments); \
  } \   
})(Element.prototype.addEventListener); \
\
(function(original) { \
  Element.prototype.removeEventListener = function(type, listener, useCapture) { \
    var attr = this.getAttribute('_handlerTypes'); \
    var types = attr ? attr.split(',') : []; \
    var removed = false; \
    for (var i = 0; i < types.length; ++i) { \
      if (types[i] == type) { \
        types.splice(i, 1); \   
        removed = true; \       
        break; \                
      } \               
    } \         
    if (removed) { \
      this.setAttribute('_handlerTypes', types.join(',')); \
    } \         
    return original.apply(this, arguments); \
  } \   
})(Element.prototype.removeEventListener); \
";

var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode(injectedJS));
document.documentElement.appendChild(script);

每个具有附加事件侦听器的 HTML 元素都将具有一个特殊属性“_handlerTypes”,其中包含逗号分隔的事件列表。并且可以从 Chrome 扩展程序的内容脚本访问此属性!

I'm posting this question here, since I can't get it posted on the official Chromium extension forums (or there's a terrific delay until it's being moderated). I have to check in Chromium extension whether there's a listener of a specific event type attached to an arbitrary HTML element. In Firefox I could use the following service to get this information:

var listenerService = Components.classes["@mozilla.org/eventlistenerservice;1"]
            .getService(Components.interfaces.nsIEventListenerService);
var infos = listenerService.getListenerInfoFor(element, {});
var types = [];
for ( var i = 0; i < infos.length; ++i) {
  var info = infos[i].QueryInterface(Components.interfaces.nsIEventListenerInfo);
  types.push(info.type);
}

As I see in Chromium there's no similar API. Therefore I've tried the
following technique (which was suggested here):

I've created script events_spy.js:

(function(original) {
  Element.prototype.addEventListener = function(type, listener, useCapture) {
    if (typeof (this._handlerTypes) == 'undefined') {
      this._handlerTypes = {};
    }
    this._handlerTypes[type] = true;
    return original.apply(this, arguments);
  }
})(Element.prototype.addEventListener);

(function(original) {
  Element.prototype.removeEventListener = function(type, listener,useCapture) {
    if (typeof (this._handlerTypes) != 'undefined') {
      delete this._handlerTypes[type];
    }
    return original.apply(this, arguments);
  }
})(Element.prototype.removeEventListener);

I declare this script in manifest.json as follows:

"content_scripts" : [{
  "matches" : [ "http://*/*", "https://*/*" ],
  "js" : [ "content/events_spy.js" ],
  "run_at" : "document_start",
  "all_frames" : true
},
...
]

Then, I test my extension on the following HTML page:

<!DOCTYPE html>
<html>
 <head>
 </head>
 <body>
  <a id="test" href="#">Click here</a>
  <script type="application/javascript">
       document.getElementById("test").addEventListener("click", function()
{ alert("clicked"); }, false);
  </script>
 </body>
</html>

Unfortunately, this doesn't work - I can't see that debugger stops
inside of my custom addEventListener() function. What am I doing wrong?

Thanks!

EDIT: Final (dirty) solution, thanks to @kdzwinel

var injectedJS = "\
(function(original) { \
  Element.prototype.addEventListener = function(type, listener, useCapture) { \
    var attr = this.getAttribute('_handlerTypes'); \
    var types = attr ? attr.split(',') : []; \
    var found = false; \
    for (var i = 0; i < types.length; ++i) { \
      if (types[i] == type) { \
        found = true; \         
        break; \                
      } \               
    } \         
    if (!found) { \
      types.push(type); \
    } \         
    this.setAttribute('_handlerTypes', types.join(',')); \
    return original.apply(this, arguments); \
  } \   
})(Element.prototype.addEventListener); \
\
(function(original) { \
  Element.prototype.removeEventListener = function(type, listener, useCapture) { \
    var attr = this.getAttribute('_handlerTypes'); \
    var types = attr ? attr.split(',') : []; \
    var removed = false; \
    for (var i = 0; i < types.length; ++i) { \
      if (types[i] == type) { \
        types.splice(i, 1); \   
        removed = true; \       
        break; \                
      } \               
    } \         
    if (removed) { \
      this.setAttribute('_handlerTypes', types.join(',')); \
    } \         
    return original.apply(this, arguments); \
  } \   
})(Element.prototype.removeEventListener); \
";

var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode(injectedJS));
document.documentElement.appendChild(script);

Every HTML element that has an attached event listeners will have a special attribute "_handlerTypes", which contains comma separated list of events. And this attribute is accessible from Chrome extension's content script!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一袭白衣梦中忆 2025-01-03 14:50:54

当我在单个独立的 HTML 文件上测试您的脚本时,它运行良好。但由于以下政策,它不能作为 Chrome 扩展程序运行:

内容脚本在称为隔离世界的特殊环境中执行。它们可以访问所注入页面的 DOM,但不能访问该页面创建的任何 JavaScript 变量或函数。它看起来每个内容脚本都好像没有其他 JavaScript 在其运行的页面上执行。反之亦然:页面上运行的 JavaScript 无法调用任何函数或访问内容脚本定义的任何变量。
[来源]

为了安全和避免冲突,所有内容都被沙箱化。页面和内容脚本之间的所有通信都必须通过 DOM 处理。


看来有人遇到了与您相同的问题并使其工作:

我通过让我的内容脚本附加一个解决了这个问题
元素到页面中,该页面查找 DOM 元素并获取其
使用 jQuery 的 $(node).data("events") 的事件监听器。它沟通
回到我的扩展,通过向其自身添加一个属性
适当的数据。显然这仅适用于附加事件的页面
使用 jQuery API 的处理程序,但由于这是我使用过的所有内容,所以它是一个
我可以忍受的限制。可怕的黑客。我会假装我从未
写的。如果您有兴趣:
github.com/grantyb/Google-Chrome-Link-URL-Extension
[来源]

如果您设法使用 events_spy.js 而不是 $(node).data("events"),您的扩展程序可能会工作得更好。
此外,他在页面和内容脚本之间进行通信的方式也很丑陋。使用文档(“与嵌入页面通信”部分)中描述的解决方案。

Your script is working fine when I test it on a single, standalone HTML file. It is not working as a Chrome extension though because of this policy:

Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.
[source]

Everything is sandboxed for security and to avoid conflicts. All communication between page and content script must be handled via DOM.


It looks like someone had the same problem as you do and made it work:

I solved this by getting my content script to append a
element into the page, which looks up the DOM element and fetches its
event listeners using jQuery's $(node).data("events"). It communicates
back to my extension by adding an attribute to itself with the
appropriate data. Clearly this only works on pages that attach event
handlers using the jQuery API, but since that's all I ever use, it's a
limitation I can live with. Awful hack. I'm going to pretend I never
wrote it. If you're interested:
github.com/grantyb/Google-Chrome-Link-URL-Extension
[source]

Your extension may work even better if you manage to use your events_spy.js instead of $(node).data("events").
Also the way he communicates between page and content script is ugly. Use solution described in the docs (section 'Communication with the embedding page').

撩起发的微风 2025-01-03 14:50:54

Chrome 控制台现在支持 getEventListeners(),请参阅 https://code.google.com/p/accessibility-developer-tools/source/browse/src/audits/UnfocusableElementsWithOnClick.js举个例子。

也就是说,我当前的用例是让上面链接的脚本在 Firefox 文档而不是 Chrome 扩展中工作,这意味着我必须添加自己的事件收集技巧。我仍在寻找一种简单、体面的方法。如果我找到一个,我将发布上述脚本的另一个补丁。 (此时,我正在考虑创建一个内置 Firefox 支持的分支,以及更多一些用于解析审核结果的辅助方法。)

Chrome's console now supports getEventListeners(), see https://code.google.com/p/accessibility-developer-tools/source/browse/src/audits/UnfocusableElementsWithOnClick.js for an example.

That said, my current use-case is getting the above-linked script to work in a Firefox document rather than a Chrome extension, which means I'll have to add my own event-gathering hacks. I'm still hunting for a simple, decent approach. If I find one, I'll post another patch to the above script. (At this point I'm thinking of making a fork with Firefox support built-in and a few more helper methods for parsing the audit results.)

为你拒绝所有暧昧 2025-01-03 14:50:54

花了很多时间让它工作,所以发布我的解决方案(没有 jQuery)...

首先,我们需要在第一次调用页面的 javascript 之前注入 addEventListener 和 removeEventListener 的“修补”版本。我们只能在 DOM 内容加载之后执行此操作,但在解析之前我们需要它。

content_script.js

window.addEventListener('DOMContentLoaded', function() {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('src', chrome.extension.getURL('eventtarget.js'));
  // injected <script> tag must be parsed first!
  document.head.insertBefore(script, document.head.firstChild);
});

EventTarget 的修补版本将订阅的事件侦听器存储在 element.__eventListeners 对象内,该对象无法从 content_script.js 访问。但需要获得正确数量的事件。现在订阅事件的每个对象都将具有 __eventname 属性(例如:

,值表示订阅的事件侦听器的数量)。

eventtarget.js:

EventTarget.prototype.__eventListeners = [];

EventTarget.prototype.__addEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function() {
  var found = false;
  if(!this.__eventListeners[arguments[0]])
   this.__eventListeners[arguments[0]] = [];
  var key;
  for(key in this.__eventListeners[arguments[0]]) {
    found = this.__eventListeners[arguments[0]][key] === arguments[1];
    if(found)
     break;
  }
  if(!found)
   this.__eventListeners[arguments[0]].push(arguments[1]);
  if(this.setAttribute)
   this.setAttribute('__'+arguments[0], this.__eventListeners[arguments[0]].length);
  return(this.__addEventListener.apply(this, arguments));
}

EventTarget.prototype.__removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function() {
  var found = false;
  if(!this.__eventListeners[arguments[0]])
   this.__eventListeners[arguments[0]] = [];
  var key;
  for(key in this.__eventListeners[arguments[0]]) {
    found = this.__eventListeners[arguments[0]][key] === arguments[1];
    if(found)
     break;
  }
  if(found)
  this.__eventListeners[arguments[0]].splice(key, 1);
  if(this.setAttribute)
   this.setAttribute('__'+arguments[0], this.__eventListeners[arguments[0]].length);
  return(this.__removeEventListener.apply(this, arguments));
}

在我的解决方案中,我使用单独的文件eventtarget.js,它需要包含在清单文件的web_accessable_resources部分中。另请注意,run_at 必须设置为 document_start 才能订阅 DOMContentLoaded 事件。不需要额外的权限。

ma​​nifest.json:

...
"web_accessible_resources": ["eventtarget.js"],
"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["content_script.js"],
    "run_at": "document_start",
    "all_frames": true
  }
],
...

其工作原理的小例子:

if(document.body.getAttribute('__wheel') > 0)
 console.log('document.body subscribed to mouse wheel event');

Spent much time to get it work, so publishing my solution (without jQuery)...

First of all, we need to inject "patched" version of addEventListener and removeEventListener before the first call of page's javascript. We can do it only after DOM content is loaded, but we need it before it's parsed.

content_script.js:

window.addEventListener('DOMContentLoaded', function() {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('src', chrome.extension.getURL('eventtarget.js'));
  // injected <script> tag must be parsed first!
  document.head.insertBefore(script, document.head.firstChild);
});

Patched version of EventTarget stores subscribed event listeners inside the element.__eventListeners object, which is inaccessable from content_script.js. But it's needed to get the right number of events. Each object subscribed on event now will have __eventname attribute (for example: <div id="test" __click="1" __mouseover="2">, the value indicates count of event listeners subscribed).

eventtarget.js:

EventTarget.prototype.__eventListeners = [];

EventTarget.prototype.__addEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function() {
  var found = false;
  if(!this.__eventListeners[arguments[0]])
   this.__eventListeners[arguments[0]] = [];
  var key;
  for(key in this.__eventListeners[arguments[0]]) {
    found = this.__eventListeners[arguments[0]][key] === arguments[1];
    if(found)
     break;
  }
  if(!found)
   this.__eventListeners[arguments[0]].push(arguments[1]);
  if(this.setAttribute)
   this.setAttribute('__'+arguments[0], this.__eventListeners[arguments[0]].length);
  return(this.__addEventListener.apply(this, arguments));
}

EventTarget.prototype.__removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function() {
  var found = false;
  if(!this.__eventListeners[arguments[0]])
   this.__eventListeners[arguments[0]] = [];
  var key;
  for(key in this.__eventListeners[arguments[0]]) {
    found = this.__eventListeners[arguments[0]][key] === arguments[1];
    if(found)
     break;
  }
  if(found)
  this.__eventListeners[arguments[0]].splice(key, 1);
  if(this.setAttribute)
   this.setAttribute('__'+arguments[0], this.__eventListeners[arguments[0]].length);
  return(this.__removeEventListener.apply(this, arguments));
}

In my solution i use separate file eventtarget.js, it's needed to be included in the web_accessable_resources section of manifest file. Also note, run_at must be set to document_start to subscribe to DOMContentLoaded event. No additional permissions is needed.

manifest.json:

...
"web_accessible_resources": ["eventtarget.js"],
"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["content_script.js"],
    "run_at": "document_start",
    "all_frames": true
  }
],
...

Little example of how it works:

if(document.body.getAttribute('__wheel') > 0)
 console.log('document.body subscribed to mouse wheel event');
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文