I am muddling through my first extension developing experience for the
Thunderbird 1.5 address book (on Windows XP). I want to build an
extension that allows me to find duplicate entries in the personal
address book and let the user handle them. I have successfully extended
the toolbar and have a toolbarbutton that allows me to open a window via
JavaScript window.open(...).
The window is defined in an XUL file, say mywindow.xul.
In the opened window, I'd like to parse through all address book
entries, find duplicates and let the user decide what to keep and what
to delete. I initite the script from the window.xul's like this:
<window ... onload="myClass.myFunction()">...</window>
When I click the toolbarbutton, the following happens:
1) The parsing is done (without a window!)
2) I repeatedly get confirmation dialogs about a script taking a long
time to run, with options proceed/exit
3) Then my window opens
I'd like it the other way around. First, the window should open. Then
the parsing should start and stop at the first duplicate found. The
window should allow the user to track the parsing progress (progressbar
and statusbar etc.).
Also, the mentioned confirmation dialog about the script taking a long
time is pretty annoying. I guess that would be solved as soon as the
script breaks after each entry, but isn't there a way to deactivate this
behavior?
Having almost no experience in Mozilla/XUL development, I would like to
know about a way to accomplish the following. How can I force the window
to be drawn before the script starts? How can I make sure that the GUI
remains responsive while the script runs in the background? This reminds
me of threads in Java/Swing. Is there anything like that in XUL/JavaScript?
Thank you very much!
Marian
> How can I make sure that the GUI remains responsive while the script
> runs in the background? This reminds me of threads in Java/Swing. Is
> there anything like that in XUL/JavaScript?
Although XPCOM supports threads the GUI does not. This means that you
could write your address book parser as a threaded component, although
even then this is difficult to write in JavaScript. However, there is an
alternative which is to batch up your processing and run it from a
timer. It works something like this:
var gChildCards;
var gInterval;
function onLoad() {
gChildCards = window.arguments[0].childCards;
gInterval = setInterval(scanCards, 10);
}
function scanCards() {
if (!gChildCards.hasMoreElements) {
clearInterval(gInterval);
} else {
scanCard(gChildCards.getNext().QueryInterface(Components.interfaces.nsIAbCard));
}
}
etc.
--
Warning: May contain traces of nuts.
Neil,
thanks for your response! I accidentally found a workaround that solves
the problem of the window not opening. I let the user start the
CPU-expensive parsing process manually by hitting a start button that
resides in the opened window.
But, since I make heavy use of regular expressions in JavaScript in
order to compare normalized name forms, and since each card is compared
with every other card (in e.g. 500 addresses for my address book), the
script takes some time to run and the "Script not responding" or
"Unresponsive script" or however (sorry, I use the localized german
version of Thunderbird) dialog keeps appearing. Is there any way I could
work around this? Because this wouldn't be acceptable for actual users
besides me.
Maybe, do I have to create any form of output from the loop? Status text
updates?
A next thing I tried, in order to give "working" feedback to the user,
is setting the 'wait-cursor' attribute of the window to true. This
works, but when I set it back to 'false' or false (when the heavy loop
is finished), the cursor doesn't change back. Anything wrong here?
this.window = document.getElementById('handleDuplicates-window');
...
this.window.setAttribute('wait-cursor', true);
...
this.window.setAttribute('wait-cursor', false);
Thanks,
Marian
> Neil schrieb:
>
>> Although XPCOM supports threads the GUI does not. This means that you
>> could write your address book parser as a threaded component,
>> although even then this is difficult to write in JavaScript. However,
>> there is an alternative which is to batch up your processing and run
>> it from a timer. It works something like this:
>
> thanks for your response! I accidentally found a workaround that
> solves the problem of the window not opening. I let the user start the
> CPU-expensive parsing process manually by hitting a start button that
> resides in the opened window.
>
> But, since I make heavy use of regular expressions in JavaScript in
> order to compare normalized name forms, and since each card is
> compared with every other card (in e.g. 500 addresses for my address
> book), the script takes some time to run and the "Script not
> responding" or "Unresponsive script" or however (sorry, I use the
> localized german version of Thunderbird) dialog keeps appearing. Is
> there any way I could work around this?
I already gave you two suggestions; how many do you need?
> A next thing I tried, in order to give "working" feedback to the user,
> is setting the 'wait-cursor' attribute of the window to true. This
> works, but when I set it back to 'false' or false (when the heavy loop
> is finished), the cursor doesn't change back. Anything wrong here?
>
> this.window.setAttribute('wait-cursor', false);
The attribute doesn't care what it is set to (it's a bit like HTML's
<input disabled="false">). You need to use removeAttribute instead.
Well, seems like I didn't get it then. Sorry for that.
>> A next thing I tried, in order to give "working" feedback to the user,
>> is setting the 'wait-cursor' attribute of the window to true. This
>> works, but when I set it back to 'false' or false (when the heavy loop
>> is finished), the cursor doesn't change back. Anything wrong here?
>>
>> this.window.setAttribute('wait-cursor', false);
>
> The attribute doesn't care what it is set to (it's a bit like HTML's
> <input disabled="false">). You need to use removeAttribute instead.
Thanks!
Marian
This means that if you get the chunking wrong then
Unresponsive script will not be avoidable.
This is pretty unfortunate, as many web 2.0 applications are going
to be compute intensive.
Alex
"Marian Steinbach" <mar...@nospam.sendung.de> wrote in message
news:B9ednXDnW7x68bPZ...@mozilla.org...
Are you saying other webbrowsers do have such a framework, and/or there
is a standard for it? Because I don't know of one, and so these "web
2.0" applications will just have to do exactly what you mentioned, and
it doesn't help anyone to make a content-accessible way of doing this.
For extensions, you can use a custom component, or maybe the nsIThread
component(s) (though I'm told the latter doesn't help much in this case).
-- Gijs
Given large amounts of data, it is important to be able to process
data in a separate thread and not have user experience 'Long Running Script'
dialog.
I will look into nsThread. What other custom compenents do you mean?
Thank you,
Alex
"Gijs Kruitbosch ("Hannibal")" <gijskru...@gmail.com> wrote in message
news:VsOdnU6kGY4hLLPZ...@mozilla.org...
Maybe, but one application that doesn't even have a majority market
share (yet) coding up a random solution doesn't solve it.
> Given large amounts of data, it is important to be able to process
> data in a separate thread and not have user experience 'Long Running Script'
> dialog.
Ian Hickson wrote on bug 230909:
> The problem is that any script that triggers this alert is going to be
> making the UI completely unusable anyway. *This is bad.* It means the
> user can't open another tab or anything. It even kills his other
> windows since we are single-threaded across the board when it comes to
> UI.
>
> The correct solution here is to make the application not take so long,
> e.g. by breaking up the work into chunks and using setTimeout() to
> chain the chunks together. (By doing this you can also give much
> better feedback about how long it is going to take.)
>
> IMHO this simply isn't a Firefox bug, it's a bug with the script.
> Enterprise or not, if JS takes that long, it's broken.
I agree with Ian on this matter, you'll have to work around the dialog
yourself. It's there for a reason, and pretty much means you should do a
better job yourself (usually speaking that is - there are, as for any
rule, exceptions).
> I will look into nsThread. What other custom compenents do you mean?
I seemed to remember there was a ThreadManager component as well, but
apparently not (or I'm failing to find it or whatever...). I won't
pretend to be an expert on threading, so I can't help you much more than
I did. I do wonder what you're doing that's taking so long... (Assuming
that you're not the same as the threadstarter here...).
-- Gijs
(1)
Marian, it sounds like you're actually comparing each card to every
other card.
With n cards, that involves about n*n operations, which we denote as
"O(n^2)". For example, n = 500 implies 500^2 = 250 000 operations.
Consider what happens with ten times as many cards, then you get one
hundred as many operations, 5000^2 = 25 000 000 operations...
Instead I suggest you make a copy of the things you're actually
comparing, sort that list (that takes roughly O(n log n) operations,
which is much less than O(n^2), and is in the regime of the practical),
and scan the sorted list for duplicates, which now will be placed right
after each other.
(2)
Earlier you wrote about how you invoked your function,
<window ... onload="myClass.myFunction()">...</window>
Assuming the window is an overlay for the address book window, i.e.
actually /is/ the address book window, your 'onload' will completely
override the original 'onload', if any, by replacing it. Instead, in
your JavaScript code, add a free statement like
window.addEventListener( "load", myFunction );
(Note that the event name is "load"; the name "onload" or generally
"onXXX" for an event "XXX" is used to install a single handler).
This adds, instead of replacing, an event handler.
The "load" event generally occurs before the window has become visible.
But that's what you want, I think, when you have tackled the inefficiency.
(3)
As you can see we share a common surname, and I have niece named Marian,
so naturally I had to reply... ;-)
I am writing a bookmarking solution that sync with remote source.
At times, this takes > 3-5 seconds. I certainly can and will do chunking
myself,
this is not an issue, it is just that this pattern will be common, so it
makes sense
to abstract it away.
Alex
"Gijs Kruitbosch ("Hannibal")" <gijskru...@gmail.com> wrote in message
news:9qGdnf5Mrq8GQrPZ...@mozilla.org...
Yes, I'm actually running card by card comparison. I'd have to think
about your suggestions and whether or not I could accomplish what I'm
doing that way. For me, there is nothing wrong about a computer doing
lots of comparisons. It still takes significantly less time than a human
solving that task. But that's me, and I'm not a computer scientist. :)
I am comparing the cards in multiple ways. First, see if email addresses
match (and there are two per person). Then, see if names match in
various combinations (and there are first name, last name and display
name - to name only those which are likely to be used). That works
pretty well for my 400 entries and helps to eliminate about 30
duplicates, most of them with user interaction.
>
> (2)
> Earlier you wrote about how you invoked your function,
>
> <window ... onload="myClass.myFunction()">...</window>
>
> Assuming the window is an overlay for the address book window, i.e.
> actually /is/ the address book window, your 'onload' will completely
> override the original 'onload', if any, by replacing it. ...
>
No, my window is not the address book window itself, but a window opened
from a menu option. Since I now let the user start the parsing by
clicking a button, that problem is solved for me.
> (3)
> As you can see we share a common surname, and I have niece named
> Marian, so naturally I had to reply... ;-)
Funny! So your niece is named "Marian Steinbach", too? Seems like
"Marian" works for men and women alike.
Thanks!
Marian
Well, you identified a performance problem.
I'd think speeding up the performance, for your data set size, by a
factor of 300 or 400 (possibly you can get near to 500 times faster),
would be of interest...
Cheers,
- Alf
Also you could chane the time a script is allowed to run.
For firefox set in about:config
dom.max_script_run_time
to 20 seconds or so.
And finally you could cache things using objects or arrays and compare
on them.
e.g. for email adresses (just an idea)
var emails={}; // a hashtable like object (empty yet)
var reference=/*some key or index*/; // unique reference to a card
for(var e in /*emails-tocheck*/)
{ // e is one email
if(e in emails) // emails[e] exist
{
// ask to merge emails[e] and
// * emails[e].push(reference);
} else
{
emails[e]=reference;
// * emails[e]=new Array(reference);
}
}
// * if you process later and need multiple references
// then loop through emails
for(var e in emails)
{
var E=emails[e];
// note a local variable is much faster than using emails[e] each time
if(E.length>1)
{
// do something with array E
}
}
then you have to:
fill caches and hashtables
use hashtables for comparision /etc.
update originals (by references)
hope it helps
Harry