Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / embedding / components / ui / progressDlg / nsProgressDialog.js
blob660f9b3eef9960581fe5ca4746eac732129c01f0
1 /* vim:set ts=4 sts=4 sw=4 et cin: */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Progress Dialog.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corp.
19 * Portions created by the Initial Developer are Copyright (C) 2002
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Bill Law <law@netscape.com>
24 * Aaron Kaluszka <ask@swva.net>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 /* This file implements the nsIProgressDialog interface. See nsIProgressDialog.idl
42 * The implementation consists of a JavaScript "class" named nsProgressDialog,
43 * comprised of:
44 * - a JS constructor function
45 * - a prototype providing all the interface methods and implementation stuff
47 * In addition, this file implements an nsIModule object that registers the
48 * nsProgressDialog component.
51 /* ctor
53 function nsProgressDialog() {
54 // Initialize data properties.
55 this.mParent = null;
56 this.mOperation = null;
57 this.mStartTime = ( new Date() ).getTime();
58 this.observer = null;
59 this.mLastUpdate = Number.MIN_VALUE; // To ensure first onProgress causes update.
60 this.mInterval = 750; // Default to .75 seconds.
61 this.mElapsed = 0;
62 this.mLoaded = false;
63 this.fields = new Array;
64 this.strings = new Array;
65 this.mSource = null;
66 this.mTarget = null;
67 this.mTargetFile = null;
68 this.mMIMEInfo = null;
69 this.mDialog = null;
70 this.mDisplayName = null;
71 this.mPaused = false;
72 this.mRequest = null;
73 this.mCompleted = false;
74 this.mMode = "normal";
75 this.mPercent = -1;
76 this.mRate = 0;
77 this.mBundle = null;
78 this.mCancelDownloadOnClose = true;
81 const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
82 const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
83 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
84 const nsITextToSubURI = Components.interfaces.nsITextToSubURI;
85 const nsIChannel = Components.interfaces.nsIChannel;
86 const nsIFileURL = Components.interfaces.nsIFileURL;
87 const nsIURL = Components.interfaces.nsIURL;
88 const nsILocalFile = Components.interfaces.nsILocalFile;
90 nsProgressDialog.prototype = {
91 // Turn this on to get debugging messages.
92 debug: false,
94 // Chrome-related constants.
95 dialogChrome: "chrome://global/content/nsProgressDialog.xul",
96 dialogFeatures: "chrome,titlebar,minimizable=yes,dialog=no",
98 // getters/setters
99 get saving() { return this.MIMEInfo == null ||
100 this.MIMEInfo.preferredAction == Components.interfaces.nsIMIMEInfo.saveToDisk; },
101 get parent() { return this.mParent; },
102 set parent(newval) { return this.mParent = newval; },
103 get operation() { return this.mOperation; },
104 set operation(newval) { return this.mOperation = newval; },
105 get observer() { return this.mObserver; },
106 set observer(newval) { return this.mObserver = newval; },
107 get startTime() { return this.mStartTime; },
108 set startTime(newval) { return this.mStartTime = newval/1000; }, // PR_Now() is in microseconds, so we convert.
109 get lastUpdate() { return this.mLastUpdate; },
110 set lastUpdate(newval) { return this.mLastUpdate = newval; },
111 get interval() { return this.mInterval; },
112 set interval(newval) { return this.mInterval = newval; },
113 get elapsed() { return this.mElapsed; },
114 set elapsed(newval) { return this.mElapsed = newval; },
115 get loaded() { return this.mLoaded; },
116 set loaded(newval) { return this.mLoaded = newval; },
117 get source() { return this.mSource; },
118 set source(newval) { return this.mSource = newval; },
119 get target() { return this.mTarget; },
120 get targetFile() { return this.mTargetFile; },
121 get MIMEInfo() { return this.mMIMEInfo; },
122 set MIMEInfo(newval) { return this.mMIMEInfo = newval; },
123 get dialog() { return this.mDialog; },
124 set dialog(newval) { return this.mDialog = newval; },
125 get displayName() { return this.mDisplayName; },
126 set displayName(newval) { return this.mDisplayName = newval; },
127 get paused() { return this.mPaused; },
128 get completed() { return this.mCompleted; },
129 get mode() { return this.mMode; },
130 get percent() { return this.mPercent; },
131 get rate() { return this.mRate; },
132 get kRate() { return this.mRate / 1024; },
133 get cancelDownloadOnClose() { return this.mCancelDownloadOnClose; },
134 set cancelDownloadOnClose(newval) { return this.mCancelDownloadOnClose = newval; },
136 set target(newval) {
137 // If newval references a file on the local filesystem, then grab a
138 // reference to its corresponding nsIFile.
139 if (newval instanceof nsIFileURL && newval.file instanceof nsILocalFile) {
140 this.mTargetFile = newval.file.QueryInterface(nsILocalFile);
141 } else {
142 this.mTargetFile = null;
145 return this.mTarget = newval;
148 // These setters use functions that update the dialog.
149 set paused(newval) { return this.setPaused(newval); },
150 set completed(newval) { return this.setCompleted(newval); },
151 set mode(newval) { return this.setMode(newval); },
152 set percent(newval) { return this.setPercent(newval); },
153 set rate(newval) { return this.setRate(newval); },
155 // ---------- nsIProgressDialog methods ----------
157 // open: Store aParentWindow and open the dialog.
158 open: function( aParentWindow ) {
159 // Save parent and "persist" operation.
160 this.parent = aParentWindow;
162 // Open dialog using the WindowWatcher service.
163 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
164 .getService( nsIWindowWatcher );
165 this.dialog = ww.openWindow( this.parent,
166 this.dialogChrome,
167 null,
168 this.dialogFeatures,
169 this );
172 init: function( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
173 aTempFile, aOperation ) {
174 this.source = aSource;
175 this.target = aTarget;
176 this.displayName = aDisplayName;
177 this.MIMEInfo = aMIMEInfo;
178 if ( aStartTime ) {
179 this.startTime = aStartTime;
181 this.operation = aOperation;
184 // ----- nsIDownloadProgressListener/nsIWebProgressListener methods -----
185 // Take advantage of javascript's function overloading feature to combine
186 // similiar nsIDownloadProgressListener and nsIWebProgressListener methods
187 // in one. For nsIWebProgressListener calls, the aDownload paramater will
188 // always be undefined.
190 // Look for STATE_STOP and update dialog to indicate completion when it happens.
191 onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus, aDownload ) {
192 if ( aStateFlags & nsIWebProgressListener.STATE_STOP ) {
193 // if we are downloading, then just wait for the first STATE_STOP
194 if ( this.targetFile != null ) {
195 // we are done transferring...
196 this.completed = true;
197 return;
200 // otherwise, wait for STATE_STOP with aRequest corresponding to
201 // our target. XXX redirects might screw up this logic.
202 try {
203 var chan = aRequest.QueryInterface(nsIChannel);
204 if (chan.URI.equals(this.target)) {
205 // we are done transferring...
206 this.completed = true;
209 catch (e) {
214 // Handle progress notifications.
215 onProgressChange: function( aWebProgress,
216 aRequest,
217 aCurSelfProgress,
218 aMaxSelfProgress,
219 aCurTotalProgress,
220 aMaxTotalProgress,
221 aDownload ) {
222 return this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
223 aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload);
226 onProgressChange64: function( aWebProgress,
227 aRequest,
228 aCurSelfProgress,
229 aMaxSelfProgress,
230 aCurTotalProgress,
231 aMaxTotalProgress,
232 aDownload ) {
233 // Get current time.
234 var now = ( new Date() ).getTime();
236 // If interval hasn't elapsed, ignore it.
237 if ( now - this.lastUpdate < this.interval ) {
238 return;
241 // Update this time.
242 this.lastUpdate = now;
244 // Update elapsed time.
245 this.elapsed = now - this.startTime;
247 // Calculate percentage.
248 if ( aMaxTotalProgress > 0) {
249 this.percent = Math.floor( ( aCurTotalProgress * 100.0 ) / aMaxTotalProgress );
250 } else {
251 this.percent = -1;
254 // If dialog not loaded, then don't bother trying to update display.
255 if ( !this.loaded ) {
256 return;
259 // Update dialog's display of elapsed time.
260 this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
262 // Now that we've set the progress and the time, update # bytes downloaded...
263 // Update status (nn KB of mm KB at xx.x KB/sec)
264 var status = this.getString( "progressMsg" );
266 // Insert 1 is the number of kilobytes downloaded so far.
267 status = this.replaceInsert( status, 1, parseInt( aCurTotalProgress/1024 + .5 ) );
269 // Insert 2 is the total number of kilobytes to be downloaded (if known).
270 if ( aMaxTotalProgress != "-1" ) {
271 status = this.replaceInsert( status, 2, parseInt( aMaxTotalProgress/1024 + .5 ) );
272 } else {
273 status = this.replaceInsert( status, 2, "??" );
276 // Insert 3 is the download rate.
277 if ( this.elapsed ) {
278 // Use the download speed where available, otherwise calculate
279 // rate using current progress and elapsed time.
280 if ( aDownload ) {
281 this.rate = aDownload.speed;
282 } else {
283 this.rate = ( aCurTotalProgress * 1000 ) / this.elapsed;
285 status = this.replaceInsert( status, 3, this.kRate.toFixed(1) );
286 } else {
287 // Rate not established, yet.
288 status = this.replaceInsert( status, 3, "??.?" );
291 // All 3 inserts are taken care of, now update status msg.
292 this.setValue( "status", status );
294 // Update time remaining.
295 if ( this.rate && ( aMaxTotalProgress > 0 ) ) {
296 // Calculate how much time to download remaining at this rate.
297 var rem = Math.round( ( aMaxTotalProgress - aCurTotalProgress ) / this.rate );
298 this.setValue( "timeLeft", this.formatSeconds( rem ) );
299 } else {
300 // We don't know how much time remains.
301 this.setValue( "timeLeft", this.getString( "unknownTime" ) );
305 // Look for error notifications and display alert to user.
306 onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage, aDownload ) {
307 // Check for error condition (only if dialog is still open).
308 if ( aStatus != Components.results.NS_OK ) {
309 if ( this.loaded ) {
310 // Get prompt service.
311 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
312 .getService( Components.interfaces.nsIPromptService );
313 // Display error alert (using text supplied by back-end).
314 var title = this.getProperty( this.saving ? "savingAlertTitle" : "openingAlertTitle",
315 [ this.fileName() ],
316 1 );
317 prompter.alert( this.dialog, title, aMessage );
319 // Close the dialog.
320 if ( !this.completed ) {
321 this.onCancel();
323 } else {
324 // Error occurred prior to onload even firing.
325 // We can't handle this error until we're done loading, so
326 // defer the handling of this call.
327 this.dialog.setTimeout( function(obj,wp,req,stat,msg){obj.onStatusChange(wp,req,stat,msg)},
328 100, this, aWebProgress, aRequest, aStatus, aMessage );
333 // Ignore onLocationChange and onSecurityChange notifications.
334 onLocationChange: function( aWebProgress, aRequest, aLocation, aDownload ) {
337 onSecurityChange: function( aWebProgress, aRequest, aState, aDownload ) {
340 // ---------- nsIObserver methods ----------
341 observe: function( anObject, aTopic, aData ) {
342 // Something of interest occured on the dialog.
343 // Dispatch to corresponding implementation method.
344 switch ( aTopic ) {
345 case "onload":
346 this.onLoad();
347 break;
348 case "oncancel":
349 this.onCancel();
350 break;
351 case "onpause":
352 this.onPause();
353 break;
354 case "onlaunch":
355 this.onLaunch();
356 break;
357 case "onreveal":
358 this.onReveal();
359 break;
360 case "onunload":
361 this.onUnload();
362 break;
363 case "oncompleted":
364 // This event comes in when setCompleted needs to be deferred because
365 // the dialog isn't loaded yet.
366 this.completed = true;
367 break;
368 default:
369 break;
373 // ---------- nsISupports methods ----------
375 // This "class" supports nsIProgressDialog, nsIWebProgressListener (by virtue
376 // of interface inheritance), nsIObserver, and nsISupports.
377 QueryInterface: function (iid) {
378 if (iid.equals(Components.interfaces.nsIProgressDialog) ||
379 iid.equals(Components.interfaces.nsIDownload) ||
380 iid.equals(Components.interfaces.nsITransfer) ||
381 iid.equals(Components.interfaces.nsIWebProgressListener) ||
382 iid.equals(Components.interfaces.nsIWebProgressListener2) ||
383 iid.equals(Components.interfaces.nsIDownloadProgressListener) ||
384 iid.equals(Components.interfaces.nsIObserver) ||
385 iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
386 iid.equals(Components.interfaces.nsISupports))
387 return this;
389 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
390 return null;
393 // ---------- nsIInterfaceRequestor methods ----------
395 getInterface: function(iid) {
396 if (iid.equals(Components.interfaces.nsIPrompt) ||
397 iid.equals(Components.interfaces.nsIAuthPrompt)) {
398 // use the window watcher service to get a nsIPrompt/nsIAuthPrompt impl
399 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
400 .getService(Components.interfaces.nsIWindowWatcher);
401 var prompt;
402 if (iid.equals(Components.interfaces.nsIPrompt))
403 prompt = ww.getNewPrompter(this.parent);
404 else
405 prompt = ww.getNewAuthPrompter(this.parent);
406 return prompt;
408 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
409 return null;
412 // ---------- implementation methods ----------
414 // Initialize the dialog.
415 onLoad: function() {
416 // Note that onLoad has finished.
417 this.loaded = true;
419 // Fill dialog.
420 this.loadDialog();
422 // Position dialog.
423 if ( this.dialog.opener ) {
424 this.dialog.moveToAlertPosition();
425 } else {
426 this.dialog.centerWindowOnScreen();
429 // Set initial focus on "keep open" box. If that box is hidden, or, if
430 // the download is already complete, then focus is on the cancel/close
431 // button. The download may be complete if it was really short and the
432 // dialog took longer to open than to download the data.
433 if ( !this.completed && !this.saving ) {
434 this.dialogElement( "keep" ).focus();
435 } else {
436 this.dialogElement( "cancel" ).focus();
440 // load dialog with initial contents
441 loadDialog: function() {
442 // Check whether we're saving versus opening with a helper app.
443 if ( !this.saving ) {
444 // Put proper label on source field.
445 this.setValue( "sourceLabel", this.getString( "openingSource" ) );
447 // Target is the "preferred" application. Hide if empty.
448 if ( this.MIMEInfo &&
449 this.MIMEInfo.preferredApplicationHandler &&
450 this.MIMEInfo.preferredApplicationHandler.executable ) {
451 var appName =
452 this.MIMEInfo.preferredApplicationHandler.executable.leafName;
453 if ( appName == null || appName.length == 0 ) {
454 this.hide( "targetRow" );
455 } else {
456 // Use the "with:" label.
457 this.setValue( "targetLabel", this.getString( "openingTarget" ) );
458 // Name of application.
459 this.setValue( "target", appName );
461 } else {
462 this.hide( "targetRow" );
464 } else {
465 // If target is not a local file, then hide extra dialog controls.
466 if (this.targetFile != null) {
467 this.setValue( "target", this.targetFile.path );
468 } else {
469 this.setValue( "target", this.target.spec );
470 this.hide( "pauseResume" );
471 this.hide( "launch" );
472 this.hide( "reveal" );
476 // Set source field.
477 this.setValue( "source", this.source.spec );
479 var now = ( new Date() ).getTime();
481 // Initialize the elapsed time.
482 if ( !this.elapsed ) {
483 this.elapsed = now - this.startTime;
486 // Update elapsed time display.
487 this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
488 this.setValue( "timeLeft", this.getString( "unknownTime" ) );
490 // Initialize the "keep open" box. Hide this if we're opening a helper app
491 // or if we are uploading.
492 if ( !this.saving || !this.targetFile ) {
493 // Hide this in this case.
494 this.hide( "keep" );
495 } else {
496 // Initialize using last-set value from prefs.
497 var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
498 .getService( Components.interfaces.nsIPrefBranch );
499 if ( prefs ) {
500 this.dialogElement( "keep" ).checked = prefs.getBoolPref( "browser.download.progressDnldDialog.keepAlive" );
504 // Initialize title.
505 this.setTitle();
508 // Cancel button stops the download (if not completed),
509 // and closes the dialog.
510 onCancel: function() {
511 // Cancel the download, if not completed.
512 if ( !this.completed ) {
513 if ( this.operation ) {
514 const NS_BINDING_ABORTED = 0x804b0002;
515 this.operation.cancel(NS_BINDING_ABORTED);
516 // XXX We're supposed to clean up files/directories.
518 if ( this.observer ) {
519 this.observer.observe( this, "oncancel", "" );
521 this.paused = false;
523 // Test whether the dialog is already closed.
524 // This will be the case if we've come through onUnload.
525 if ( this.dialog ) {
526 // Close the dialog.
527 this.dialog.close();
531 // onunload event means the dialog has closed.
532 // We go through our onCancel logic to stop the download if still in progress.
533 onUnload: function() {
534 // Remember "keep dialog open" setting, if visible.
535 if ( this.saving ) {
536 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
537 .getService( Components.interfaces.nsIPrefBranch );
538 if ( prefs ) {
539 prefs.setBoolPref( "browser.download.progressDnldDialog.keepAlive", this.dialogElement( "keep" ).checked );
542 this.dialog = null; // The dialog is history.
543 if ( this.mCancelDownloadOnClose ) {
544 this.onCancel();
548 // onpause event means the user pressed the pause/resume button
549 // Toggle the pause/resume state (see the function setPause(), below).i
550 onPause: function() {
551 this.paused = !this.paused;
554 // onlaunch event means the user pressed the launch button
555 // Invoke the launch method of the target file.
556 onLaunch: function() {
557 try {
558 const kDontAskAgainPref = "browser.download.progressDnlgDialog.dontAskForLaunch";
559 try {
560 var pref = Components.classes["@mozilla.org/preferences-service;1"]
561 .getService(Components.interfaces.nsIPrefBranch);
562 var dontAskAgain = pref.getBoolPref(kDontAskAgainPref);
563 } catch (e) {
564 // we need to ask if we're unsure
565 dontAskAgain = false;
567 if ( !dontAskAgain && this.targetFile.isExecutable() ) {
568 try {
569 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
570 .getService( Components.interfaces.nsIPromptService );
571 } catch (ex) {
572 // getService doesn't return null, it throws
573 return;
575 var title = this.getProperty( "openingAlertTitle",
576 [ this.fileName() ],
577 1 );
578 var msg = this.getProperty( "securityAlertMsg",
579 [ this.fileName() ],
580 1 );
581 var dontaskmsg = this.getProperty( "dontAskAgain",
582 [ ], 0 );
583 var checkbox = {value:0};
584 var okToProceed = promptService.confirmCheck(this.dialog, title, msg, dontaskmsg, checkbox);
585 try {
586 if (checkbox.value != dontAskAgain)
587 pref.setBoolPref(kDontAskAgainPref, checkbox.value);
588 } catch (ex) {}
590 if ( !okToProceed )
591 return;
593 this.targetFile.launch();
594 this.dialog.close();
595 } catch ( exception ) {
596 // XXX Need code here to tell user the launch failed!
597 dump( "nsProgressDialog::onLaunch failed: " + exception + "\n" );
601 // onreveal event means the user pressed the "reveal location" button
602 // Invoke the reveal method of the target file.
603 onReveal: function() {
604 try {
605 this.targetFile.reveal();
606 this.dialog.close();
607 } catch ( exception ) {
611 // Get filename from the target.
612 fileName: function() {
613 if ( this.targetFile != null )
614 return this.targetFile.leafName;
615 try {
616 var escapedFileName = this.target.QueryInterface(nsIURL).fileName;
617 var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
618 .getService(nsITextToSubURI);
619 return textToSubURI.unEscapeURIForUI(this.target.originCharset, escapedFileName);
620 } catch (e) {}
621 return "";
624 // Set the dialog title.
625 setTitle: function() {
626 // Start with saving/opening template.
627 // If percentage is not known (-1), use alternate template
628 var title = this.saving
629 ? ( this.percent != -1 ? this.getString( "savingTitle" ) : this.getString( "unknownSavingTitle" ) )
630 : ( this.percent != -1 ? this.getString( "openingTitle" ) : this.getString( "unknownOpeningTitle" ) );
633 // Use file name as insert 1.
634 title = this.replaceInsert( title, 1, this.fileName() );
636 // Use percentage as insert 2 (if known).
637 if ( this.percent != -1 ) {
638 title = this.replaceInsert( title, 2, this.percent );
641 // Set dialog's title property.
642 if ( this.dialog ) {
643 this.dialog.document.title = title;
647 // Update the dialog to indicate specified percent complete.
648 setPercent: function( percent ) {
649 // Maximum percentage is 100.
650 if ( percent > 100 ) {
651 percent = 100;
653 // Test if percentage is changing.
654 if ( this.percent != percent ) {
655 this.mPercent = percent;
657 // If dialog not opened yet, bail early.
658 if ( !this.loaded ) {
659 return this.mPercent;
662 if ( percent == -1 ) {
663 // Progress meter needs to be in "undetermined" mode.
664 this.mode = "undetermined";
666 // Update progress meter percentage text.
667 this.setValue( "progressText", "" );
668 } else {
669 // Progress meter needs to be in normal mode.
670 this.mode = "normal";
672 // Set progress meter thermometer.
673 this.setValue( "progress", percent );
675 // Update progress meter percentage text.
676 this.setValue( "progressText", this.replaceInsert( this.getString( "percentMsg" ), 1, percent ) );
679 // Update title.
680 this.setTitle();
682 return this.mPercent;
685 // Update download rate and dialog display.
686 setRate: function( rate ) {
687 this.mRate = rate;
688 return this.mRate;
691 // Handle download completion.
692 setCompleted: function() {
693 // If dialog hasn't loaded yet, defer this.
694 if ( !this.loaded ) {
695 this.dialog.setTimeout( function(obj){obj.setCompleted()}, 100, this );
696 return false;
698 if ( !this.mCompleted ) {
699 this.mCompleted = true;
701 // If the "keep dialog open" box is checked, then update dialog.
702 if ( this.dialog && this.dialogElement( "keep" ).checked ) {
703 // Indicate completion in status area.
704 var string = this.getString( "completeMsg" );
705 string = this.replaceInsert( string,
707 this.formatSeconds( this.elapsed/1000 ) );
708 string = this.replaceInsert( string,
710 this.targetFile.fileSize >> 10 );
712 this.setValue( "status", string);
713 // Put progress meter at 100%.
714 this.percent = 100;
716 // Set time remaining to 00:00.
717 this.setValue( "timeLeft", this.formatSeconds( 0 ) );
719 // Change Cancel button to Close, and give it focus.
720 var cancelButton = this.dialogElement( "cancel" );
721 cancelButton.label = this.getString( "close" );
722 cancelButton.focus();
724 // Activate reveal/launch buttons if we enable them.
725 var enableButtons = true;
726 try {
727 var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
728 .getService( Components.interfaces.nsIPrefBranch );
729 enableButtons = prefs.getBoolPref( "browser.download.progressDnldDialog.enable_launch_reveal_buttons" );
730 } catch ( e ) {
733 if ( enableButtons ) {
734 this.enable( "reveal" );
735 try {
736 if ( this.targetFile != null ) {
737 this.enable( "launch" );
739 } catch(e) {
743 // Disable the Pause/Resume buttons.
744 this.dialogElement( "pauseResume" ).disabled = true;
746 // Fix up dialog layout (which gets messed up sometimes).
747 this.dialog.sizeToContent();
749 // GetAttention to show the user that we're done
750 this.dialog.getAttention();
751 } else if ( this.dialog ) {
752 this.dialog.close();
755 return this.mCompleted;
758 // Set progress meter to given mode ("normal" or "undetermined").
759 setMode: function( newMode ) {
760 if ( this.mode != newMode ) {
761 // Need to update progress meter.
762 this.dialogElement( "progress" ).setAttribute( "mode", newMode );
764 return this.mMode = newMode;
767 // Set pause/resume state.
768 setPaused: function( pausing ) {
769 // If state changing, then update stuff.
770 if ( this.paused != pausing ) {
771 var string = pausing ? "resume" : "pause";
772 this.dialogElement( "pauseResume" ).label = this.getString(string);
774 // If we have an observer, tell it to suspend/resume
775 if ( this.observer ) {
776 this.observer.observe( this, pausing ? "onpause" : "onresume" , "" );
779 return this.mPaused = pausing;
782 // Format number of seconds in hh:mm:ss form.
783 formatSeconds: function( secs ) {
784 // Round the number of seconds to remove fractions.
785 secs = parseInt( secs + .5 );
786 var hours = parseInt( secs/3600 );
787 secs -= hours*3600;
788 var mins = parseInt( secs/60 );
789 secs -= mins*60;
790 var result;
791 if ( hours )
792 result = this.getString( "longTimeFormat" );
793 else
794 result = this.getString( "shortTimeFormat" );
796 if ( hours < 10 )
797 hours = "0" + hours;
798 if ( mins < 10 )
799 mins = "0" + mins;
800 if ( secs < 10 )
801 secs = "0" + secs;
803 // Insert hours, minutes, and seconds into result string.
804 result = this.replaceInsert( result, 1, hours );
805 result = this.replaceInsert( result, 2, mins );
806 result = this.replaceInsert( result, 3, secs );
808 return result;
811 // Get dialog element using argument as id.
812 dialogElement: function( id ) {
813 // Check if we've already fetched it.
814 if ( !( id in this.fields ) ) {
815 // No, then get it from dialog.
816 try {
817 this.fields[ id ] = this.dialog.document.getElementById( id );
818 } catch(e) {
819 this.fields[ id ] = {
820 value: "",
821 setAttribute: function(id,val) {},
822 removeAttribute: function(id) {}
826 return this.fields[ id ];
829 // Set dialog element value for given dialog element.
830 setValue: function( id, val ) {
831 this.dialogElement( id ).value = val;
834 // Enable dialog element.
835 enable: function( field ) {
836 this.dialogElement( field ).removeAttribute( "disabled" );
839 // Get localizable string from properties file.
840 getProperty: function( propertyId, strings, len ) {
841 if ( !this.mBundle ) {
842 this.mBundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
843 .getService( Components.interfaces.nsIStringBundleService )
844 .createBundle( "chrome://global/locale/nsProgressDialog.properties");
846 return len ? this.mBundle.formatStringFromName( propertyId, strings, len )
847 : this.mBundle.getStringFromName( propertyId );
850 // Get localizable string (from dialog <data> elements).
851 getString: function ( stringId ) {
852 // Check if we've fetched this string already.
853 if ( !( this.strings && stringId in this.strings ) ) {
854 // Presume the string is empty if we can't get it.
855 this.strings[ stringId ] = "";
856 // Try to get it.
857 try {
858 this.strings[ stringId ] = this.dialog.document.getElementById( "string."+stringId ).childNodes[0].nodeValue;
859 } catch (e) {}
861 return this.strings[ stringId ];
864 // Replaces insert ("#n") with input text.
865 replaceInsert: function( text, index, value ) {
866 var result = text;
867 var regExp = new RegExp( "#"+index );
868 result = result.replace( regExp, value );
869 return result;
872 // Hide a given dialog field.
873 hide: function( field ) {
874 this.dialogElement( field ).hidden = true;
876 // Also hide any related separator element...
877 var sep = this.dialogElement( field+"Separator" );
878 if (sep)
879 sep.hidden = true;
882 // Return input in hex, prepended with "0x" and leading zeros (to 8 digits).
883 hex: function( x ) {
884 return "0x" + ("0000000" + Number(x).toString(16)).slice(-8);
887 // Dump text (if debug is on).
888 dump: function( text ) {
889 if ( this.debug ) {
890 dump( text );
895 // This Component's module implementation. All the code below is used to get this
896 // component registered and accessible via XPCOM.
897 var module = {
898 // registerSelf: Register this component.
899 registerSelf: function (compMgr, fileSpec, location, type) {
900 var compReg = compMgr.QueryInterface( Components.interfaces.nsIComponentRegistrar );
901 compReg.registerFactoryLocation( this.cid,
902 "Mozilla Download Progress Dialog",
903 this.contractId,
904 fileSpec,
905 location,
906 type );
909 // getClassObject: Return this component's factory object.
910 getClassObject: function (compMgr, cid, iid) {
911 if (!cid.equals(this.cid))
912 throw Components.results.NS_ERROR_NO_INTERFACE;
914 if (!iid.equals(Components.interfaces.nsIFactory))
915 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
917 return this.factory;
920 /* CID for this class */
921 cid: Components.ID("{F5D248FD-024C-4f30-B208-F3003B85BC92}"),
923 /* Contract ID for this class */
924 contractId: "@mozilla.org/progressdialog;1",
926 /* factory object */
927 factory: {
928 // createInstance: Return a new nsProgressDialog object.
929 createInstance: function (outer, iid) {
930 if (outer != null)
931 throw Components.results.NS_ERROR_NO_AGGREGATION;
933 return (new nsProgressDialog()).QueryInterface(iid);
937 // canUnload: n/a (returns true)
938 canUnload: function(compMgr) {
939 return true;
943 // NSGetModule: Return the nsIModule object.
944 function NSGetModule(compMgr, fileSpec) {
945 return module;