Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out what event listener types attached to specific HTML element in Chrome extension?

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!

like image 437
Michael Spector Avatar asked Nov 13 '22 11:11

Michael Spector


1 Answers

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').

like image 114
Konrad Dzwinel Avatar answered Jan 11 '23 05:01

Konrad Dzwinel