Safari Extension Get Tab Position or Identifier - javascript

I am working on a safari extension in which I need to parse a particular array element to each instance of a tab that is created. I, however, need to be able to iterate through the array so that each tab receives a different element to work with in an injected script. I using the receive and send message structure to do this, but I cannot for the life of me figure out how to iterate through the array elements. I tried creating an array that would act as an index, and then incrementing it each time the message responder function was fired, but this didn't work for some reason. I also tried simply shifting the array each time an element was pulled from it, but I believe this didn't work because the function is fired too quickly as tabs are created.
I want to be able to use some sort of enumerator function on each injected script instance to figure out the tab number and then parse that with the message to the global page to return the proper element in the array.
Thanks so much for any and all help.

I think you are trying to iterate through all tabs in all windows -- please clarify which array you are having trouble with. In order to iterate through all of them, you should be able to do it like this, first through the windows in the application, then through the tabs:
var bWindows = safari.application.browserWindows;
for(i=0;i<bWindows.length;i++){
var tabs = bWindows[i].tabs;
for(j=0;j<tabs.length;j++){
var tab = tabs[j];
//Do something in each tab.
tab.page.dispatchMessage('message', data);
}
}

Related

JavaScript items disappearing from Array

I have an array called objs that holds all of my application objects. Objects get added and removed from this list depending upon what happens in the application.
I am having this problem where some objects disappear (or are overwritten) only sometimes. If I step through the add and remove functions, the app always runs as it should, however many times when it is run without the debugger, one or two objects that were added to the end of the list disappear from the list.
objects are added to the array like this:
this.objs[this.objs.length]=obj;
and are removed from the array like this:
for(var i=0;i<this.objs.length;i++)
if(this.objs[i]==obj)
return this.objs.splice(i,1);
I put this code at the end of my add and remove functions:
console.log("add! ");
console.log(this.objs);
Linked is an image of a console log during a session where an object dissapeared: http://ilujin.com/error.png
The first 4 objects in the list shown at the top should remain in the list throughout the session, but the object at index 3 (highlighted in red), gets overwritten by the next object that gets added (highlighted in blue).
The other weird thing is that the second list shown already has all of the changes (4 objects removed and 1 added), even though the remove function has only been called once and the add function not at all.
This makes me conclude that the problem is timing - if one add hasn't finished before the next add is called, the first one will be overwritten. And all of the console prints are the same because they all happen before the console can read and print.
Does this makes sense? For some reason I thought JS never ran parallel code and only moved on to a new function when the last function finished. Is the problem that I'm using the length of the objs list as the new index when I add to the list?
How can I fix this issue? I can't figure it out, and the debugger and console have proven useless.
Here is the app: http://iioengine.com/neuro/study2.htm
you only need to enter an id and see if the instructions pop up. If they do, than its working and refresh. If they don't, that means that the Text Object got overwritten.
You would really be better served by using Javascript's array methods.
Add to array:
this.objs.push(obj);
Remove from array:
this.objs.splice(this.objs.indexOf(obj), 1);
Also, note that splice edits the original array and returns the elements that have been removed. It's hard to tell from your limited code sample, but that might also be causing issues.

IndexedDB Fails when adding objects that contain element references

I'm writing an application for Google Chrome (targeted audience is an internal team) that allows a user to manipulate elements from within an iframe. The user is able to use her mouse to select DOM elements and to perform various actions to them, such as changing colors, fonts, etc.
I'm using a nodeIterator method to select only elements that have IDs or class names. Then for each of those elements, I add some element-specific properties to an object, and push that object to an array. Then, I open an IndexedDB database and add each object in the array to the database.
My problem is this: Everything works fine so long as I don't include a reference to the element in the object.
// Works fine
array.push({
width : currentNode.offsetWidth,
height : currentNode.offsetHeight,
top : currentNode.style.top;
left : currentNode.style.left;
});
// Doesn't work
array.push({
elem : currentNode,
width : currentNode.offsetWidth,
height : currentNode.offsetHeight,
top : currentNode.style.top;
left : currentNode.style.left;
});
Google chrome fails silently (nothing in the console at all) after trying to add the first element to the IndexedDB store.
My question is this: Has anyone else experienced this behavior and is this a browser-specific bug?
I'll distill my code to JSfiddle tomorrow. Thanks in advance.
IndexedDB store structured clone of your object. Basically your data will converted into JSON object, these exclude Element or Node data type.
However fail silently is not an expected behaviour. Accordingly to the structured clone algorithm, it should throw DataCloneError.
Is it necessary to save the DOM element? Can you just save the ID of the DOM element and retrieve the element back by its ID?
The indexeddb is only capable of storing data that doesn't have circular references. There is maybe one thing you can try. Sometime ago I wrote a blog post on how you can serialize and deserialize functions to JSON. Maybe this can help you, but I would advace you not to store complete elements unless there is no other option. This will add a lot of unnecessary data into your database, and it's possible you'll lose information when serializing to JSON.
You should get an exception in chrome (I just tried on Chrome 23) from the put() itself, which means if you have an onerror handler, it won't get called because the exception gets called first:
i.e. if you have
req = db.transaction("foo", "readwrite").objectStore("foo").put({...data with dom nodes })
req.onsuccess = ...
req.onerror = ...
The exception will be thrown by the first line.

JavaScript Pub/Sub property access issue

I am having a strange issue here I hope you all can help with.
Project Details
I am working on a simple pub/sub implementation for a larger application that includes a pubsub.subscribe_once() method. This method enables the creation of one-off subscriptions, meaning that a generic subscription is created, and then once the correct "publish" event fires and the subscription callback is run, the subscription deletes itself.
subscribe_once: function(topic, func) {
var sub = pubsub.subscribe(topic, func),
old_func = sub.func;
// rewrite our subscription's method to remove itself after invocation
sub.func = function() {
// call the original function
old_func.apply(this);
// remove subscription from topic
pubsub.unsubscribe(sub);
};
return sub;
}
Problem
I seem to be having some kind of issue with the memory flow of this process. (In order to best understand the following explanation I suggest you walk through the jsfiddle demo below as I go.) I create a subscribe_once('someevent') subscription, and then fire publish('someevent'). What you would expect to see when the publish method is invoked is that the topics hashtable contains a "someevent" key, which references an array of Subscription objects. And in fact, if you reference topics["someevent"], you see an array with a single Subscription. If, however, you reference topics you see the "someevent" key, but the array is empty!
By commenting out pubsub.unsubscribe(sub); the problem is eliminated, even though this function does not appear to be fired until after we run console.log(topics).
Further, this does not seem to be an issue with the way a given browser "threads" console.log; try console.log(topics, topics[topic], topics, topics[topic]) and you get the same result.
Demo: http://jsfiddle.net/4Ab6c/
Any help would be greatly appreciated! Thanks.
I'm still looking for some documentation to back me up, but I suspect the object display in the console is performing lazy evaluation on your topics object. I added console.log(topics) to the subscribe method after sub is pushed onto the array and I get the same result as your 'but not here' log. When I wrap the final line of your fiddle pubsub.publish('someevent') in setTimeout and I get the Object tree open before the publish callback runs, then it shows the subscription in the array and it stays that way even after the callback runs. If I don't open the object tree in the console before the callback runs then I see the empty array.
I will keep searching for a blog post or something that confirms lazy evaluation is occurring.
Just in case I haven't made it obvious enough, by lazy I mean, the console isn't gathering the details of the object until the tree view is clicked open in the console.
I am working in Chrome.
UPDATE
I have found similar behavior on Firefox as well. Firefox recognizes that there is one object in the array but if you don't drill down into the array before the publish even fires then the drill-down on the array will be empty.
I updated the fiddle from your comment:
http://jsfiddle.net/4Ab6c/2/
Please try this:
Run the fiddle and expand the object tree for the first console.log before the publish event fires, I set it to a five second timeout but you could make it longer or shorter depending on how quickly you can get down to the console and click the inspector open.
You should see the subscribe object in the array as expected.
Clear the console and run the fiddle again. This time do not open the object inspector until after the publish event has fired and all the code is done running.
Now when you open the object inspector of the first console.log you should not see the subscription event in the array.
UPDATE 2
Here is a much simpler fiddle that exhibits the same behavior:
http://jsfiddle.net/4Ab6c/3/
If you expand first before second shows up then you will get foo: bar. If you expand first after second shows up you will get foo: baz.
UPDATE 3
And, voila, another SO question seconds the motion on lazy evaluation.

Knockout Mapping Plugin - Capture Array Value

I have a JSON array coming from a REST API. I am using the Knockout mapping plugin to process the array and load the JSON into preset form values (if a user has added values to the form previously - I have data there to test the Knockout arrays). The form essentially adds or deletes div blocks with inputs so users can add/delete "work" experiences.
My trouble is with trying to decipher how the plugin maps the arrays. I am trying to locate a specific value (the id) of a row in the array so I can add it as a variable to tell the API to delete that specific row. I can get Knockout to explicitly output the row value in the html, but I can't figure out how to capture it otherwise. In the template "foreach" I have a button that references a "remove:" and that's where I'm stuck in trying to capture the value from the array.
For Example in the HTML:
This outputs the two rows of the "work" object no problem:
<span data-bind="text: ko.mapping.toJSON(workModel.work())"></span>
[{"id":"1","schoolID":"2","place":"","position":"Science Teacher","description":"I worked at ASD for 1 year as a Science teacher.","start":"2011","end":"2012","profileID":"91"},{"id":"2","schoolID":"1","place":"American School of Taiwan","position":"Science Guy","description":"I was just another science guy","start":"2008","end":"2011","profileID":"91"}]
This outputs the id of the first row and item in the array:
<span data-bind="text: ko.mapping.toJSON(workModel.work()[0].id)"></span>
"1"
But in the javascript, if you click on the remove button generated by the foreach template...
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work(this).id));
}
Gives me this error in Firebug, and then the UI reloads and drops out the template block I just clicked on.
Unable to parse bindings. Message: TypeError: workModel.work()[0] is undefined; Bindings value: text: ko.mapping.toJSON(workModel.work()[0].id)
Even though, if I replace the above alert with the explicit statement:
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work()[0].id));
}
I get the correct value of "1" again. I know it has to do with the "this" aspect of the code, but I'm not sure what the mapping plugin is doing so that I can capture the specific value from the array...make sense? Any help would be greatly appreciated.
I'm going out on a limb here, but I do think it's the this-problem yes. Scoping in Javascript can be a hassle sometimes. Try doing something like this in the scope containing the gone-function:
var self = this;
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work(self).id));
}
Disclaimer: I'm not able to test this myself right now, but give it a try :)
I finally got it. It came from combining different post on Stack Overflow and also from the Knockout forums. I'm sure other folks have more elegant solutions than this, but it works for me.
In the foreach loop on the "Delete" (or whatever button you want to use to capture the value) button I included the following on the data-bind:
Remove
Then in the javascript I have:
var self = this;
var row_id;
self.remove = function(index){
var row_id = index;
alert(row_id);
}
The alert returns the row ID of the loaded JSON as I wanted. The $data.id() could be changed/used to return any mapped element from the loaded JSON. The row_id is then a global that can be accessed elsewhere as well.

How do I compare Application.windows[x] to Application.activeWindow?

I'm creating a Firefox extension, in which I want to iterate through the Application.windows array and check if one of its elements is the same as Application.activeWindow.
The mentioned excerpt from my code looks like this:
for (var i in Application.windows) {
if (Application.windows[i]==Application.activeWindow) alert('debug');
// there was some more complex code than alert('debug'),
// but since it didn't work, I decided to try with an alert
}
Unfortunately, the 'debug' alert is never viewed. Thus I decided to try this code (with only one window opened):
// the following code runs in an event listener for window.onload
alert(Application.windows[0]);
alert(Application.activeWindow);
alert(Application.windows[0]==Application.activeWindow);
Firefox displayed 3 alerts: the first one was [object Object], the second one - [xpconnect wrapped fuelIWindow], and the last one (which didn't surprise me) said false. So it seems the objects I'm trying to compare have different types. How can I deal with this? Thanks in advance.
You have two problems.
The first is that XPConnect doesn't support array-valued properties, so when FUEL (or STEEL or SMILE) return an array, they're actually returning an nsIVariant of internal objects! On the other hand, single-valued objects return an XPConnect wrapper which hides the internal object.
The second is that each time you access windows or activeWindow, new internal objects are created, so even two calls to activeWindow return different objects.
The way around this is to avoid FUEL and enumerate the windows directly using the window mediator.

Categories

Resources