2012-01-18 10 views
5

Sto postando questa domanda qui, dal momento che non riesco a farlo pubblicare sui forum di estensione Chromium ufficiali (o c'è un ritardo eccezionale fino a quando non viene moderato). Devo controllare in estensione Chromium se esiste un listener di un tipo di evento specifico associato a un elemento HTML arbitrario. In Firefox ho potuto utilizzare il seguente servizio per ottenere queste informazioni:Come scoprire quali tipi di listener di eventi sono collegati a un elemento HTML specifico nell'estensione di Chrome?

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); 
} 

Per come la vedo in cromo non c'è API simili. Quindi ho provato il seguente tecnica (che era stato suggerito here):

Ho creato lo 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); 

dichiaro questo script in manifest.json come segue:

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

Quindi, eseguo il test della mia estensione sulla seguente pagina 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> 

Sfortunatamente, questo non funziona: non riesco a vedere che il debugger si arresta all'interno della mia funzione personalizzata addEventListener(). Che cosa sto facendo di sbagliato?

Grazie!

EDIT: Soluzione finale (sporco), grazie alla @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); 
elemento

Ogni HTML con un listener di eventi associati avrà un attributo speciale "_handlerTypes", che contiene separati da virgola elenco degli eventi . E questo attributo è accessibile dallo script dei contenuti dell'estensione di Chrome!

+0

Grazie molto per l'aggiornamento con la soluzione! –

risposta

1

Lo script funziona correttamente quando eseguo il test su un singolo file HTML autonomo. Tuttavia non funziona come estensione di Chrome a causa di questo criterio:

Gli script di contenuto vengono eseguiti in un ambiente speciale chiamato un mondo isolato. Hanno accesso al DOM della pagina in cui sono inseriti, ma non a nessuna variabile o funzione JavaScript creata dalla pagina. Osserva ogni script di contenuto come se non ci fosse nessun altro JavaScript in esecuzione sulla pagina su cui è in esecuzione. Lo stesso è vero al contrario: JavaScript in esecuzione sulla pagina non può chiamare alcuna funzione o accedere a qualsiasi variabile definita dagli script di contenuto. [source]

Tutto è in modalità sandbox per sicurezza ed evitare conflitti. Tutte le comunicazioni tra pagina e contenuto devono essere gestite tramite DOM.


Sembra che qualcuno ha avuto lo stesso problema come si fa e ha reso il lavoro:

ho risolto questo ottenendo il mio script contenuti per aggiungere un elemento nella pagina, che guarda il Elemento DOM e recupera i suoi listener di eventi utilizzando $ (nodo) .data di jQuery ("eventi"). Comunica indietro alla mia estensione aggiungendo un attributo a se stesso con i dati appropriati .Chiaramente questo funziona solo sulle pagine che collegano i gestori dell'evento utilizzando l'API jQuery, ma poiché è tutto ciò che uso, è una limitazione con cui posso convivere. Terribile hack Farò finta di non averlo mai scritto . Se siete interessati: github.com/grantyb/Google-Chrome-Link-URL-Extension [source]

tua estensione può funzionare ancora meglio se si riesce a utilizzare il events_spy.js invece di $(node).data("events"). Anche il modo in cui comunica tra la pagina e lo script del contenuto è brutto. Utilizzare la soluzione descritta in docs (sezione "Comunicazione con la pagina di incorporamento").

+0

Grazie per la spiegazione del perché il metodo che uso non funziona! Sai se c'è qualcosa che dovrei cercare di raggiungere il mio obiettivo? – spektom

+0

Sto ancora cercando ma senza fortuna finora. Domanda molto interessante però! –

+0

Controlla questo - http://www.quirksmode.org/js/events_advanced.html, sezione 'Quali gestori di eventi sono registrati?'. –

0

La console di Chrome ora supporta getEventListeners(), vedere https://code.google.com/p/accessibility-developer-tools/source/browse/src/audits/UnfocusableElementsWithOnClick.js per un esempio.

Detto questo, il mio attuale caso d'uso è far sì che lo script sopra collegato funzioni in un documento di Firefox piuttosto che in un'estensione di Chrome, il che significa che dovrò aggiungere i miei hack per la raccolta degli eventi. Sto ancora cercando un approccio semplice e decente. Se trovo uno, posterò un'altra patch per lo script sopra. (A questo punto sto pensando di creare un fork con Firefox integrato e alcuni altri metodi di supporto per analizzare i risultati dell'audit.)

0

Ho passato molto tempo a farlo funzionare, quindi pubblicare la mia soluzione (senza jQuery) ...

Prima di tutto, è necessario iniettare la versione "rattoppata" di addEventListener e removeEventListener prima della prima chiamata di javascript della pagina. Possiamo farlo solo dopo aver caricato il contenuto DOM, ma ne abbiamo bisogno prima che venga analizzato.

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); 
}); 

ascoltatori di eventi versione modificata di esercizi eventtarget sottoscritti all'interno dell'oggetto element.__eventListeners, che è inaccessibile da content_script.js. Ma è necessario per ottenere il giusto numero di eventi. Ogni oggetto iscritto all'evento ora avrà l'attributo __eventname (ad esempio: <div id="test" __click="1" __mouseover="2">, il valore indica il conteggio degli ascoltatori di eventi sottoscritti).

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)); 
} 

Nella mia soluzione che uso file separato eventtarget.js, ha avuto bisogno di essere inclusi nella sezione web_accessable_resources di file manifesto. Inoltre, è necessario impostare run_at su document_start per iscriversi all'evento DOMContentLoaded. Non sono necessarie ulteriori autorizzazioni.

manifest.json:

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

piccolo esempio di come funziona:

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