Javascript - DOMSubtreeModified event is not fired for my content in Firefox! - javascript

I am facing a weird behavior and I need some help..
I encounter a situation when I try to recognize whether the content of the page was modified. I do it using
gBrowser.tabContainer.addEventListener("DOMSubtreeModified", function (e) { this.foo(e); }, false);
I also tried listening to document.DOMSubtreeModified and window.DOMSubtreeModified.
However, I sometimes get a situation in which the default\selected document is something that is irrelevant to me - perhaps some IFrame or a commercial built in or whatever, and bottom line my content is modified but when staring at the browser DOMSubtreeModified doesn't fire since it listens to a document\whatever that was indeed not modified...
Can you please help my understnad where's my problem? I need to create some event that recognizes any content modification (something like DOMSubtreeModified) that fires for every document, so that I could identify my relevant content and process it?
Thanks a lot,
Nili

You could explicitly listen for all DOM modification by adding a listener on the document object for each <iframe> element within the element you're interested in:
function listenForDomModified(node, listener) {
node.addEventListener("DOMSubtreeModified", listener, false);
var iframes = node.getElementsByTagName("iframe");
for (var i = 0, len = iframes.length, doc; i < len; ++i) {
// Catch and ignore errors caused by iframes from other domains
try {
doc = iframes[i].contentDocument || iframes[i].contentWindow.document;
doc.addEventListener("DOMSubtreeModified", listener, false);
} catch (ex) {}
}
}
listenForDomModified(gBrowser.tabContainer);
Note that the DOMSubtreeModified event doesn't fire at all in Opera, so your code won't work in that browser.

for FF 2, Safari, Opera 9.6+
doc.addEventListener('DOMNodeInserted', callback, false);
doc.addEventListener('DOMNodeRemoved', callback, false);

Related

object.onload fails with IE and Edge

I'm trying to dynamically preload list of files which may be anything between images and JavaScript files. Everything is going supersmooth with Chrome and Firefox, but failing when I'm trying to preload JavaScript files with Edge. Edge still can handle images for example but no js files. And yes I've tried with addEventListener, it's not working either.
Edge doesn't give me any errors.
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = path/to/javascriptfile.js
body.appendChild(object);
object.onload = function(){
console.log('hello world')
//not firing with edge
}
Anything relevant I'm missing?
UPDATE: Didn't get any success after the day. Will probably leave it for now and just skip preloading script files with edge until i find a solution.
Perhaps worth a check - from msdn:
The client loads applications, embedded objects, and images as soon as
it encounters the applet, embed, and img objects during parsing.
Consequently, the onload event for these objects occurs before the
client parses any subsequent objects. To ensure that an event handler
receives the onload event for these objects, place the script object
that defines the event handler before the object and use the onload
attribute in the object to set the handler.
https://msdn.microsoft.com/en-us/library/windows/apps/hh465984.aspx
Edit, a clarification:
You should attach the event listener before the element is added to the page.
Even doing that I'm not sure if it'll work or not though. But to make sure you've exhausted all options try the example below:
function doLoad() {
console.log('The load event is executing');
}
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = 'path/to/javascriptfile.js';
object.onreadystatechange = function () {
if (object.readyState === 'loaded' || object.readyState === 'complete') doLoad();
console.log('onreadystatechange');
}
if (object.addEventListener) {
object.addEventListener( "load", doLoad, false );
console.log('addEventListener');
}
else
{
if (object.attachEvent) {
object.attachEvent( "onload", doLoad );
console.log('attachEvent');
} else if (object.onLoad) {
object.onload = doLoad;
console.log('onload');
}
}
var body = document.getElementsByTagName("body")[0];
body.appendChild(object);
If this doesn't work, you could perhaps preload using "image" instead of "object" in IE: https://stackoverflow.com/a/11103087/1996783
Working temporary solution is to put onload event directly to script element instead of object. It's sad since it works like a charm in Chrome & FF.
It turns out, object.data with css source did not load either. I don't know if it's a bug since it still can load image from to object.data.
But show must go on.
Cheers, eljuko

Firefox - Javascript - window.event not surviving pass to context.apply()

-This is the original question for posterity-
I'm having a bit of trouble with a new piece of code I wrote. I'm trying to find the iframe the window that sent a message lives in, assuming it's from an iframe at all. The relevant code:
var getContainingFrame = function(source){
var frames = document.getElementsByTagName('iframe');
for (var i = 0; i < frames.length; ++i) {
if (frames[i].contentWindow === source) {
return frames[i];
}
}
return false;
}
var postMessageReceivedCallback = function(event){
getContainingFrame(event.source);
}
window.addEventListener("message", postMessageReceivedCallback, false);
Works fine in Chrome/Safari, however Firefox matches the first frame every time. (iframe.contentWindow === window regardless of which window). I actually originally found how to do this from another post here, though they don't mention the problem with Firefox.
Each iframe has a different src.
Is there a different method for matching these? Have I done something obviously wrong?
No jQuery, please.
Thanks
Better Question:
My event was being passed through a function.apply(window, params) through the window object, expecting window.event available in the function it was applied to - this works in Chrome/Safari, thought not the case in FF. How do I get the event to be passed in FF?
var someFunction = function(params){
this.event.source //expect this to be the window the event originated in
//... do something, like the iframe check
}
var postMessageReceivedCallback = function(event){
//FF not keeping the event reference on the window that other browsers are,
//so we add it ourselves from the event object passed by the message event.
window.event = event;
someFunction.apply(window, event.message);
}
window.addEventListener("message", postMessageReceivedCallback, false);

How can I detect keyboard events in Gmail

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.

Accesing DOM with pages using AJAX (Firefox extension)

I'm trying to learn to create extensions with Firefox but I'm running into some issues and I hope someone more experienced could give me some tips.
The idea was doing a dummy extension that would access the DOM once the page is loaded, so I hook onto DOMContentLoaded to later iterate over certain element class getElementsByClassName. The problem I noticed is that I get a zero length array as response. I assume this is due to the fact that the page uses asynchronous scripts to load some parts of the content, do when the event is triggered the content is not yet complete.
I found this interesting thread of someone running into a very similar problem: Interacting with DOM for AJAX webpages? and tried to test the answer proposed by someone. Unfortunately, when I run my script I get the following error: "getattribute is not a function"
Just for clarity, I'm using the same snippet from that post (which uses twitter.com for test)
window.addEventListener("DOMNodeInserted",
function(event){
var streamItem = event.target;
if (streamItem == null)
return;
if (streamItem.getAttribute('class') != 'stream-item')
return;
var tweet = streamItem.getElementsByClassName("tweet",streamItem)[0];
var name = tweet.getAttribute("data-screen-name");
if (name.toLowerCase() == 'some-username'.toLowerCase())
{
var icon = tweet.getElementsByTagName("img")[0];
icon.style.display='none';
}
},
false);
except that I'm using gBrowser.addEventListener() instead, which is called whenever I get a callback from window.addEventListener("load") in my extension.
Any idea how to solve this issue? I'm currently using exactly the above script just for testing purposes, as it's an identical case for what I'm trying to achieve.
Felix Kling is correct, you should register your event handler on the content document - right now you are listening for nodes that are being added to the browser (most likely new tabs). Of course that's only possible when the content document is available, e.g. in the DOMContentLoaded event. This also has the advantage that you only slow down the documents that you really want to look at (having a DOMNodeInserted event listener in a document slows down DOM modifications quite a lot). Something like this (untested but should work):
gBrowser.addEventListener("DOMContentLoaded", function(event)
{
var contentDoc = event.target; // That's the document that just loaded
// Check document URL, only add a listener to the document we want
if (contentDoc.URL.indexOf("http://example.com/") != 0)
return;
contentDoc.addEventListener("DOMNodeInserted", function(event)
{
var streamItem = event.target;
if (streamItem == null || streamItem.nodeType != Node.ELEMENT_NODE)
return;
...
});
}, false);
Note the additional check of the node type - there are also text nodes being inserted for example and those don't have the getAttribute method (which is probably causing your error). You only want to look at the element nodes.

What Event should I register on DOM Update with Internet Explorer

Background
I'm developing a bookmarklet which has dependencies on 3rd party libraries to be loaded into the DOM before my bookmarklet script can run. The heart of the problem I'm having is this dependency chain that I need to wait for before I can react, and I'm having issues getting it to fly in IE.
I cannot be sure JQuery is even on the page at this stage. It's something I check, and if it isn't there, then I have to create and inject this into the DOM.
The flow of control is something like this:
Load main library (jquery) into the DOM, I've attached an event listener to pick up on when it's actually in the DOM.
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = http:// ... jquerylibrary.js
e.addEventListener('load', callback);
Setup the callback function
var callback = function() {
//my stuff that wants to use jquery
}
This works, and it works well. I can chain jquery, some jquery plugins, then my code, and then I can be sure when my code runs (in real browser land) that it'll run.
The Crux of the problem
When IE > 9 (Which we must support) has a similar function ... attachEvent('onload',callback);
Thing that's similar, but doesn't appear to fire correctly when DOM elements are updated. Looking through MSDN's long list of events, it's not clear if any of those events are what I'm looking for.
What event does IE 7+ fire when the DOM is updated?
I've figured it out after reading some of the source of head.js. This calls for a bit of creative javascript magic.
The events we're interested in are onreadystatechange, and onload. You can attach a custom function to these to trigger update notifications when they insert into the page.
I've abstracted this into a function, where, if you pass it an element and a callback statement, it'll dump the element at the end of the page body and run the callback.
function loadElement(e, callback) {
if ( typeof(callback) === 'function' ) {
e.onreadystatechange = e.onload = function() {
var state = e.readyState;
if ( !callback.done && (!state || /loaded|complete/.test(state)) ) {
callback.done = true;
callback();
}
}
}
document.body.appendChild(e);
}
Though I've posted this answer to my problem, I'm interested in alternative approaches to this :).

Categories