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.
Related
Is there a way I could identify the source file of a specific event?
My events are being removed, because the usage of document.open usage in the code. This is something can't change. I am trying to re-use my events, but because of another limitation I have, I need to know which events are coming from different JS files.
EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type,listener,params)
{
var isFromSourceX = "nameoffile.js"
var worker_events = ['DOMContentLoaded',
'beforeunload', "blue","devicemotion","deviceorientation",
"error","focus","load","message","orientationchange",
"resize","scroll","storage","click"];
var _this=this;
var _isEventExists = window._stackedListeners.filter(function(item){ return item.type==type && item.target==_this; })[0]===undefined?false:true;
var isHtmlElement = this instanceof HTMLElement;
if (worker_events.indexOf(type)>-1 && !_isEventExists && isFromSourceX) {
window._stackedListeners.push({
target: _this,
type: type,
listener: listener,
params: params
});
this.addEventListenerBase(type, listener, params);
}
};
})(self);
Eventually, I'll be pushing all needed events to an array to later attach them on the web page. But the problem as mentioned, is that I need to identify the source (to exclude external events) in the webpage.
p.s: I did not chose to work with document.open :)
Any ideas?
Thanks.
If you tend to use Chrome Dev Tools you will be able to see all the events associated with the specific type if you open the Source tab. On the right pane in Event listener breakpoints you could check the event category that you are interested in. By executing specific ones you will be able to see the source file.
It does not give you all the event listeners out of the box but it could be helpful if you are trying to diagnose your issue. You could also find event listeners on a dom node by inspecting it. On the right pane there should be a list of event listeners for it.
Hope I gave you some glimpse.
This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.
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 :).
I have a page that loads another window on button click. The loaded page has silverlight control on it, so it takes some time to load and get prepared before it can receive javascript calls.
What I need to do is to call a particular method of silverlight object right after the silverlight plugin gets loaded and is ready to interact with me.
Now, if the pop-up page was already opened then the code would be like that:
var slWin = window.open('PopupPage.html', 'WindowName');
var elem = slWin.document.getElementById('slControl');
elem.Content.SlObject.MethodA();
This works when the window is already opened because the control is already loaded and ready. I need to modify this code to handle the situation when the elem need some time to be prepared.
I tried to use jQuery's ready and load methods to add handlers to corresponding events, but with no particular lack. Here's the full snippet:
var slWin = window.open('', 'WindowName');
var elem = slWin.document.getElementById('slControl');
if (elem == null) {
slWin.location.href = 'PopupPage.aspx';
// this branch doesn't work
$(slWin).load(function () {
elem = slWin.document.getElementById('slControl');
elem.Content.SlObject.MethodA();
});
}
else {
// this branch works fine
elem.Content.SlObject.MethodA();
}
How do I solve this issue? I don't mind jQuery solutions.
This error is happening because the Silverlight object is not fully loaded when you are trying to access it.
Try to use the "onload" event of the silverlight object to dectect when it's ready to use. Here you have a link to the MSDN documentation:
http://msdn.microsoft.com/en-us/library/cc838107(v=vs.95).aspx
Hope it helps. :)
I have a Javascript plugin that searches the DOM for any elements starting with the class name "tracking" and adds a click event listener (or another type of listener, if specified) to that element. The idea is that every time that event occurs on that element, that it runs a Javascript function that sends data to our traffic servers. Here's what the code looks like:
// Once the page is completed loaded
window.mmload(function() {
// Get the container object
obj = document.getElementById(name);
if ( obj.length < 0 )
throw ("The Id passed into the tracker does not exist ("+name+")");
// Find all the elements belonging to the tracking class
var trackingClass = new RegExp( /tracking\[[a-zA-Z0-9\.\-_]+\]/g );
var myElements = getElementsByRegex( trackingClass, obj );
//For each of those elements...
for( var i in myElements ) {
var elm = myElements[i];
var method = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.')[2];
method = typeof( method ) == 'undefined' ? 'click' : method;
// Add a click event listener
myElements[i].addEventListener( method, function(e){
// Get the element, the link (if any), and the args of the event
var link = elm.getAttribute('href') == null ? "" : elm.getAttribute('href');
var args = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.');
// If a link existed, pause it, for now
if ( link != '' )
e.preventDefault();
// Track the event
eventTracker( args[0], args[1], ( method == 'click' ? 'redirect' : 'default' ), link );
return false;
}, true);
}
});
Right now I've got this chuck of code running once the window has completely loaded (window.mmload() is a function I made for appending window.onload events). However, there maybe times when I need to run this function again because I added new elements to the DOM via Javascript with this class name and I want to track them too.
My initial solution was to run this function using setInterval to check the DOM every few milliseconds or second or whatever makes the most sense. However, I was worried if I took this approach that it might slow down the website, especially since this is running on a mobile website for smartphones. I'm not sure what kind of a performance hit I might take if I'm searching to DOM every so often.
The other approach I had in mind was to simply call the function after adding traceable elements to the DOM. This is probably the most efficient way of handling it. However, the people that I'm working with, granted very smart individuals, are Web Designers who don't often think about nor understand very well code. So the simpler I can make this, the better. That's why I liked the setInterval approach because nothing additional would be required of them. But if it noticeably slows down the site, I might have to take the other approach.
You should consider even delegation.
You just add one event listener to the document root and check the class of the element the event originated from (event.target). If you want to include also clicks from descendants, you'd have to traverse the DOM up form the target and check whether any of the ancestors contains the class.
I see two main advantages:
It works for newly generated elements without any extra steps (so the other developers don't have to do anything special).
It adds only one event handler instead of potentially many, which saves memory.
Disadvantages:
If other event handlers are registered along the path and they prevent the event from bubbling up, you cannot register this event.
A bit more information:
An event handler gets an event object as first argument. This object has several properties, among others, which element the event originated form.
E.g. to get the target element:
var element = event.target || event.srcElement;
This will be a DOM element and you can access the classes via element.className.
So your event listener could look like this (note that IE uses another method to attach event listeners and the event object is not passed but available via window.event):
function handler(event) {
event = event || window.event;
var target = event.target || event.srcElement;
if(target.className.match(/tracking\[[a-zA-Z0-9\.\-_]+\]/g) {
// do your stuff
}
}
if(document.addEventListener) {
document.addEventListener('click', handler, false);
}
else {
document.attachEvent('onclick', handler);
}
But as I said, this would miss events that are prevented from bubbling up. At least in the browsers following the W3C model (so not IE), you can handle the events in the capture phase by setting the last parameter to true:
document.addEventListener('click', handler, true);
If you can live without IE, then there is a change event which you can hook into for the window/document/dom element. Simply hook into the event at the document level, and it'd fire anytime something's changed in the page (stuff inserted, deleted, changed). I believe the event's context contains what got changed, so it should be fairly trivial to find any new trackable elements and attach your spy code to it.
A third option would be to write a method for manipulating the innerHTML of an element. At the end of that method simply call your function that refreshes everything.
example:
var setHtml = function(element, newHtml){
element.innerhtml = newHtml;
yourRefreshFunction();
}
So obviously this requires that you have your web developers user this method to update the dom. And you'll have to do it for anything that is more complicated than simple html edits. But that gives you the idea.
Hope that helps!