I am developing a Firefox addon using the addon-sdk. This addon adds a menu item to the context menu and user can right click on any edit controls to activate this menu item. Once activated, it shows a small popup with suggestions when user types something.
Everything works well except on Gmail.
In Gmail, the below code fails.
self.port.on('showPopup', function(data) {
var active = document.activeElement;
console.log(active.type);
if (active && getWordUnderCaret(active).word == data.input) {
populateSuggestions(data);
positionPopup(active);
stylePopup();
}
});
The reason for the failure is document.activeElement
points to document.body
and the getWordUnderCaret
fails as it expects an input/textarea control. This works well in all other places. I am not sure why it points to document.body
as I can see the focus is on the input control. Typing document.activeElement
in Firebug console gives me proper object.
Alternatively, I tried to track the active element myself rather than using document.activeElement
. But I ran into issues like persisting this somewhere. I can't use window
to persist this as window
is a proxy. I tried with unsafeWindow
but couldn't get it to work.
I am wondering why this fails in Gmail? Any help to fix this would be great!
My code is available at Github
Edit
It looks like this is an issue with addon-sdk. I have created a Gist which can be used to reproduce the problem. It is available here
I guess, you are referring to the big box on Gmail, where you enter the message-body when you compose an email.
» If that's the case;
That edit control on Gmail is not a text control (input or textarea). That's a WYSIWYG editor implemented with an <iframe>
which is made editable by setting document.designMode = "on"
(or document.body.contentEditable = true
depending on the browser support).
So, when clicked; you don't always get the right element with the way you query document.activeElement
, in this case. You get the wrapping (main) document's body
, (not even the iframe
's body
).
For example; I believe, at this line of your code; you add a click
event listener to the main document. But you should also add it to the editable iframes in the page; because the page and the iframe(s) have different document
objects (and iframe will not set itself as active). So, you will get the wrong document.activeElement
.
Add the necessary event listeners to the iframes too; since you want to get notified about them.
// Add mouseup event listener to the main document
document.addEventListener('mouseup', logActiveElement, false);
// Get all the iframes on the main document.
var iframeElems = document.getElementsByTagName('iframe');
// Add mouseup event listener to the document of the iframe elements
for (var i = 0; i < iframeElems.length; ++i) {
addIframeEvent(iframeElems[i], true, 'mouseup', logActiveElement);
}
Here are the helper functions:
(Notice how we add event listeners to an iframe (document) from the main document and how we check whether or not the iframe is currently editable.)
// Adds the specified event listener to the iframe element.
function addIframeEvent(iframeElem, editableOnly, eventName, callback) {
// Get the document of the iframe element.
var iframeDocument = iframeElem.contentDocument ||
iframeElem.contentWindow.document;
// Watch for editableOnly argument.
if ( (editableOnly && isDocEditable(iframeDocument)) || !editableOnly) {
// Add the event listener to the document of the iframe element.
iframeDocument.addEventListener(eventName, callback, false);
}
}
// Checks whether the specified document is content-editable or in design mode.
function isDocEditable(doc) {
return ( ('contentEditable' in doc.body) && (doc.body.contentEditable === true) ) ||
('designMode' in doc) && (doc.designMode == "on") );
}
// Handler function
function logActiveElement() {
console.log("Active Element:", document.activeElement);
}
Now, the event handler will log the correct activeElement
to the console.
As a result; things like text selections and inspections in your existing code, will not work the same way they do with regular edit controls (input, textarea, etc).
For example; your getWordUnderCaret(editor)
function gets the insertionPoint
by editor.
selectionStart
which does not exist in document.body
(of the target iframe
). For this kind of selection/inspection; you should switch between DOM Selections, DOM Ranges; instead of text selection and ranges in text edit controls.
Note: The text selection/range jQuery library (Rangy) you use, promises to handle this kind of editable content within the browser (iframes, divs, etc). Have you tried that for iframes (on Gmail for example)?
activeElement
is the page's <body>
."
To check the text controls on Gmail; I also played with your sample code;
attachTo: ["existing", "top", "frame"]
to PageMod options.contentScriptWhen
to 'end'
(instead of
'ready'
) in PageMod options; to make sure everything including DOM, CSS, JS has been loaded. Some content may be altered via JS on page ready/load so 'end' will execute after it.Tested on Gmail (search box, chat box, etc) and other sites and; this seems to be working.
See/test the working example here on addon builder.
Have you tried using the latest git version of the addon sdk? The context menu there has been rewritten from scratch and I heard it fixes quite a few bugs.
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