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 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
.
Workaround:
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.
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 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)?
Further Information:
- See activeElement documentation on Mozilla Developer
Network. Aditinonally, it indicates: "Do not confuse focus with a
selection over the document. When there is no selection, the
activeElement
is the page's <body>
."
UPDATE:
To check the text controls on Gmail; I also played with your sample code;
- Added
attachTo: ["existing", "top", "frame"]
to PageMod options.
- Changed the value of
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.
- Applied the selector context to the menu items too; in order to
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.

