Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

document.activeElement not available on firefox addon's content script only for Gmail

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

like image 611
Navaneeth K N Avatar asked Jan 11 '13 05:01

Navaneeth K N


2 Answers

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.

Gmail Search-box text input

Gmail Chat-box textarea

like image 165
Onur Yıldırım Avatar answered Oct 12 '22 08:10

Onur Yıldırım


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.

like image 3
Jonathan Protzenko Avatar answered Oct 12 '22 10:10

Jonathan Protzenko