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!
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').
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With