2012-01-18 11 views
5

Zamieszczam tutaj to pytanie, ponieważ nie mogę go opublikować na oficjalnych forach rozszerzeń Chromium (lub jest to straszne opóźnienie, dopóki nie zostanie on moderowany). Muszę sprawdzić rozszerzenie Chromium, czy odbiornik określonego typu zdarzenia jest dołączony do dowolnego elementu HTML. W Firefoksie można używać następujące usługi, aby uzyskać te informacje:Jak dowiedzieć się, jakie typy detektorów zdarzeń są dołączone do określonego elementu HTML w rozszerzeniu 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); 
} 

Jak widzę w Chromium nie ma podobny interfejs API. Dlatego starałem się następujące techniki (który został zaproponowany here):

Mam utworzonego skryptu 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); 

Oświadczam ten skrypt w manifest.json następująco:

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

Następnie testuję rozszerzenie na następującą stronę 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> 

Niestety, to nie działa - nie widzę, że debugger zatrzymuje się wewnątrz mojej niestandardowej funkcji addEventListener(). Co ja robię źle?

Dzięki!

EDIT: Finał (brudny) rozwiązanie, dzięki @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); 
elementem

Każdy HTML, który ma załączony detektory zdarzeń będzie miało szczególny atrybut „_handlerTypes”, który zawiera listę oddzielonych przecinkami wydarzeń . A ten atrybut jest dostępny ze skryptu treści rozszerzenia Chrome!

+0

Dzięki bardzo za aktualizację z rozwiązania! –

Odpowiedz

1

Twój skrypt działa poprawnie, gdy testuję go na pojedynczym, autonomicznym pliku HTML. Nie działa on jednak jako rozszerzenie Chrome, ale z powodu tych zasad:

Skrypty treści są uruchamiane w specjalnym środowisku zwanym izolowanym światem. Mają dostęp do DOM strony, do której są wstrzykiwani, ale nie do żadnych zmiennych JavaScript ani funkcji tworzonych przez stronę. Wygląda na każdy skrypt treści tak, jakby na stronie, na której jest uruchomiony, nie ma innych skryptów JavaScript. To samo dotyczy odwrotnej strony: JavaScript działający na stronie nie może wywoływać żadnych funkcji ani dostępu do zmiennych zdefiniowanych przez skrypty treści. [source]

Wszystko jest piaskownicy dla bezpieczeństwa oraz w celu uniknięcia konfliktów. Cała komunikacja między stroną i skryptem treści musi być obsługiwana przez DOM.


Wygląda jakby ktoś miał ten sam problem jak ty i uczynił to działa:

Rozwiązałem to przez coraz mój skrypt treści dołączyć element na stronę, która wygląda w górę DOM element i pobiera detektory zdarzeń przy użyciu $ (node) .data jQuery ("events"). Komunikuje się z numerem z powrotem na moje rozszerzenie, dodając do siebie atrybut z właściwymi danymi .Oczywiście działa to tylko na stronach, które dołączają obsługę zdarzeń przy użyciu interfejsu API jQuery, ale ponieważ to wszystko, czego kiedykolwiek używam, jest to ograniczenie, z którym mogę żyć. Okropny hack. Zamierzam udawać, że nigdy nie napisałem tego. Jeśli jesteś zainteresowany: github.com/grantyb/Google-Chrome-Link-URL-Extension [source]

Twoje rozszerzenie może działać jeszcze lepiej, jeśli uda się wykorzystać swój events_spy.js zamiast $(node).data("events"). Również sposób, w jaki komunikuje się między skryptem strony i treści jest brzydki. Użyj rozwiązania opisanego w docs (sekcja "Komunikacja ze stroną osadzania").

+0

Dzięki za wyjaśnienie, dlaczego metoda, z której korzystam, nie działa! Czy wiesz, czy jest coś, co powinienem spróbować osiągnąć mój cel? – spektom

+0

Wciąż szukam, ale nie mam szczęścia. Bardzo fajne pytanie! –

+0

Sprawdź to - http://www.quirksmode.org/js/events_advanced.html, sekcja "Które procedury obsługi zdarzeń są zarejestrowane?". –

0

Konsola Chrome obsługuje teraz getEventListeners(), patrz np. https://code.google.com/p/accessibility-developer-tools/source/browse/src/audits/UnfocusableElementsWithOnClick.js.

To powiedziawszy, moja obecna sytuacja polega na tym, że powyższy skrypt działa w dokumencie Firefox, a nie w rozszerzeniu Chrome, co oznacza, że ​​będę musiał dodać własne hacki zbierające zdarzenia. Wciąż poluję na proste, przyzwoite podejście. Jeśli ją znajdę, dodam kolejną poprawkę do powyższego skryptu. (W tym momencie myślę o zrobieniu widelca z wbudowanym wsparciem dla Firefoksa i kilkoma dodatkowymi metodami analizowania wyników kontroli.)

0

Zużyłem dużo czasu, aby to działało, więc publikuję moje rozwiązanie (bez jQuery) ...

Przede wszystkim musimy wstrzyknąć "poprawioną" wersję addEventListener i removeEventListener przed pierwszym wywołaniem javascript strony wywołania. Możemy to zrobić tylko po wczytaniu treści DOM, ale potrzebujemy jej, zanim zostanie ona parsowana.

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

poprawionych wersji sklepach eventtarget subskrybowanych detektory zdarzeń wewnątrz obiektu element.__eventListeners, który jest inaccessable od content_script.js. Ale potrzebna jest odpowiednia liczba zdarzeń. Każdy obiekt subskrybowany na zdarzenie ma teraz atrybut __eventname (na przykład: <div id="test" __click="1" __mouseover="2">, wartość wskazuje liczbę subskrybowanych odbiorców zdarzeń).

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

W moim rozwiązanie używam oddzielnego pliku eventtarget.js, to musiały być zawarte w sekcji web_accessable_resources oczywistego pliku. Pamiętaj również, że run_at musi mieć wartość document_start, aby subskrybować zdarzenie DOMContentLoaded. Nie są wymagane żadne dodatkowe uprawnienia.

manifest.json:

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

mały przykład jak to działa:

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