I am writing a browser extension that needs to attach handlers to the keyup
and keydown
events on all pages. I can get it working pretty well with the following content script code.
document.addEventListener("keydown",keyDown, true);
document.addEventListener("keyup", keyUp, true);
I can't get this to work in Gmail though. Specifically I can't get it to work when composing the body of an new email. It will work everywhere else I have tested. I think the problem is because Gmail is calling stopPropagation
on all keyboard events but it is difficult to debug their minimized code. I thought that setting the 3rd parameter to true
would cause the event to be captured during the CAPTURE_PHASE
but this isn't working.
How can I capture keyup
and keydown
events while composing a new body in Gmail with a Google Chrome content script?
Edit:
I've ensured that my content scripts are being injected into all iframes of the DOM by adding "all_frames": true,
to my manifest. I have even tried using the following code:
document.addEventListener("DOMNodeInserted", function (event) {
if(event.type === "DOMNodeInserted") {
if(event.srcElement.nodeName === "IFRAME") {
console.log(event.srcElement.nodeName + " iframe detected");
event.srcElement.addEventListener("keydown", function(kevent) {
document.dispatchEvent(kevent);
}, true);
event.srcElement.addEventListener("keyup", function(kevent) {
document.dispatchEvent(kevent);
}, true);
}
}
},true);
This still doesn't fix the issue with Gmail.
Your code doesn't work because event.srcElement
refers to the <iframe>
element, not its content. To access its content document, you have to wait for the frame to be loaded (onload
or polling), then use frame.contentDocument
to access the frame.
Starting from Chrome 37.0.1995.0, you can also use the match_about_blank
(with all_frames) to insert a content script in the about:blank
frame that captures the event and sends it to the parent content script.
Here is an example of an implementation for the original idea (using polling):
The relevant parts of manifest.json
:
"content_scripts": [{
"matches": ["*://mail.google.com/*"],
"js": ["contentscript.js"],
"run_at": "document_end"
}],
contentscript.js
function keyDown(e) {console.log(e.which);}; // Test
function keyUp(e) {console.log(e.keyCode);}; // Test
(function checkForNewIframe(doc) {
if (!doc) return; // document does not exist. Cya
// Note: It is important to use "true", to bind events to the capturing
// phase. If omitted or set to false, the event listener will be bound
// to the bubbling phase, where the event is not visible any more when
// Gmail calls event.stopPropagation().
// Calling addEventListener with the same arguments multiple times bind
// the listener only once, so we don't have to set a guard for that.
doc.addEventListener('keydown', keyDown, true);
doc.addEventListener('keyup', keyUp, true);
doc.hasSeenDocument = true;
for (var i = 0, contentDocument; i<frames.length; i++) {
try {
contentDocument = iframes[i].document;
} catch (e) {
continue; // Same-origin policy violation?
}
if (contentDocument && !contentDocument.hasSeenDocument) {
// Add poller to the new iframe
checkForNewIframe(iframes[i].contentDocument);
}
}
setTimeout(checkForNewIframe, 250, doc; // <-- delay of 1/4 second
})(document); // Initiate recursive function for the document.
Note that I used polling instead of DOM mutation events, because the latter heavily reduces performance.