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;
The Reason:
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 settingdocument.designMode = "on"(ordocument.body.contentEditable = truedepending 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’sbody, (not even theiframe‘sbody).For example; I believe, at this line of your code; you add a
clickevent 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 differentdocumentobjects (and iframe will not set itself as active). So, you will get the wrongdocument.activeElement.Workaround:
Add the necessary event listeners to the iframes too; since you want to get notified about them.
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.)
Now, the event handler will log the correct
activeElementto the console.Complications:
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 theinsertionPointbyeditor.selectionStartwhich does not exist indocument.body(of the targetiframe). 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)?
Further Information:
Network. Aditinonally, it indicates: "Do not confuse focus with a
selection over the document. When there is no selection, the
activeElementis the page’s<body>."UPDATE:
To check the text controls on Gmail; I also played with your sample code;
attachTo: ["existing", "top", "frame"]to PageMod options.contentScriptWhento'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.ensure the item only appears during the selector context.
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.