!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
4 *
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/
9 *
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.
14 *
15 * The Original Code is Mozilla Progress Dialog.
16 *
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.
21 *
22 * Contributor(s):
23 * Bill Law <law@netscape.com>
24 * Aaron Kaluszka <ask@swva.net>
25 *
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.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 /* This file implements the nsIProgressDialog interface. See nsIProgressDialog.idl
41 *
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
46 *
47 * In addition, this file implements an nsIModule object that registers the
48 * nsProgressDialog component.
49 */
50
51 /* ctor
52 */
nsProgressDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
79 }
80
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;
89
90 nsProgressDialog.prototype = {
91 // Turn this on to get debugging messages.
92 debug: false,
93
94 // Chrome-related constants.
95 dialogChrome: "chrome://global/content/nsProgressDialog.xul",
96 dialogFeatures: "chrome,titlebar,minimizable=yes,dialog=no",
97
98 // getters/setters
get_saving
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
99 get saving() { return this.MIMEInfo == null ||
100 this.MIMEInfo.preferredAction == Components.interfaces.nsIMIMEInfo.saveToDisk; },
get_parent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
101 get parent() { return this.mParent; },
set_parent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
102 set parent(newval) { return this.mParent = newval; },
get_operation
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
103 get operation() { return this.mOperation; },
set_operation
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
104 set operation(newval) { return this.mOperation = newval; },
get_observer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
105 get observer() { return this.mObserver; },
set_observer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
106 set observer(newval) { return this.mObserver = newval; },
get_startTime
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
107 get startTime() { return this.mStartTime; },
set_startTime
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
108 set startTime(newval) { return this.mStartTime = newval/1000; }, // PR_Now() is in microseconds, so we convert.
get_lastUpdate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
109 get lastUpdate() { return this.mLastUpdate; },
set_lastUpdate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
110 set lastUpdate(newval) { return this.mLastUpdate = newval; },
get_interval
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
111 get interval() { return this.mInterval; },
set_interval
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
112 set interval(newval) { return this.mInterval = newval; },
get_elapsed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
113 get elapsed() { return this.mElapsed; },
set_elapsed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
114 set elapsed(newval) { return this.mElapsed = newval; },
get_loaded
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
115 get loaded() { return this.mLoaded; },
set_loaded
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
116 set loaded(newval) { return this.mLoaded = newval; },
get_source
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
117 get source() { return this.mSource; },
set_source
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
118 set source(newval) { return this.mSource = newval; },
get_target
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
119 get target() { return this.mTarget; },
get_targetFile
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
120 get targetFile() { return this.mTargetFile; },
get_MIMEInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
121 get MIMEInfo() { return this.mMIMEInfo; },
set_MIMEInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
122 set MIMEInfo(newval) { return this.mMIMEInfo = newval; },
get_dialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
123 get dialog() { return this.mDialog; },
set_dialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
124 set dialog(newval) { return this.mDialog = newval; },
get_displayName
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
125 get displayName() { return this.mDisplayName; },
set_displayName
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
126 set displayName(newval) { return this.mDisplayName = newval; },
get_paused
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
127 get paused() { return this.mPaused; },
get_completed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
128 get completed() { return this.mCompleted; },
get_mode
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
129 get mode() { return this.mMode; },
get_percent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
130 get percent() { return this.mPercent; },
get_rate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
131 get rate() { return this.mRate; },
get_kRate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
132 get kRate() { return this.mRate / 1024; },
get_cancelDownloadOnClose
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
133 get cancelDownloadOnClose() { return this.mCancelDownloadOnClose; },
set_cancelDownloadOnClose
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
134 set cancelDownloadOnClose(newval) { return this.mCancelDownloadOnClose = newval; },
135
set_target
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
143 }
144
145 return this.mTarget = newval;
146 },
147
148 // These setters use functions that update the dialog.
set_paused
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
149 set paused(newval) { return this.setPaused(newval); },
set_completed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
150 set completed(newval) { return this.setCompleted(newval); },
set_mode
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
151 set mode(newval) { return this.setMode(newval); },
set_percent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
152 set percent(newval) { return this.setPercent(newval); },
set_rate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
153 set rate(newval) { return this.setRate(newval); },
154
155 // ---------- nsIProgressDialog methods ----------
156
157 // open: Store aParentWindow and open the dialog.
open
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
158 open: function( aParentWindow ) {
159 // Save parent and "persist" operation.
160 this.parent = aParentWindow;
161
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 );
170 },
171
init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
180 }
181 this.operation = aOperation;
182 },
183
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.
189
190 // Look for STATE_STOP and update dialog to indicate completion when it happens.
onStateChange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
198 }
199
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;
207 }
208 }
209 catch (e) {
210 }
211 }
212 },
213
214 // Handle progress notifications.
onProgressChange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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);
224 },
225
onProgressChange64
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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();
235
236 // If interval hasn't elapsed, ignore it.
237 if ( now - this.lastUpdate < this.interval ) {
238 return;
239 }
240
241 // Update this time.
242 this.lastUpdate = now;
243
244 // Update elapsed time.
245 this.elapsed = now - this.startTime;
246
247 // Calculate percentage.
248 if ( aMaxTotalProgress > 0) {
249 this.percent = Math.floor( ( aCurTotalProgress * 100.0 ) / aMaxTotalProgress );
250 } else {
251 this.percent = -1;
252 }
253
254 // If dialog not loaded, then don't bother trying to update display.
255 if ( !this.loaded ) {
256 return;
257 }
258
259 // Update dialog's display of elapsed time.
260 this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
261
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" );
265
266 // Insert 1 is the number of kilobytes downloaded so far.
267 status = this.replaceInsert( status, 1, parseInt( aCurTotalProgress/1024 + .5 ) );
268
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, "??" );
274 }
275
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;
284 }
285 status = this.replaceInsert( status, 3, this.kRate.toFixed(1) );
286 } else {
287 // Rate not established, yet.
288 status = this.replaceInsert( status, 3, "??.?" );
289 }
290
291 // All 3 inserts are taken care of, now update status msg.
292 this.setValue( "status", status );
293
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" ) );
302 }
303 },
304
305 // Look for error notifications and display alert to user.
onStatusChange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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 );
318
319 // Close the dialog.
320 if ( !this.completed ) {
321 this.onCancel();
322 }
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.
anon:327:40
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
327 this.dialog.setTimeout( function(obj,wp,req,stat,msg){obj.onStatusChange(wp,req,stat,msg)},
328 100, this, aWebProgress, aRequest, aStatus, aMessage );
329 }
330 }
331 },
332
333 // Ignore onLocationChange and onSecurityChange notifications.
onLocationChange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
334 onLocationChange: function( aWebProgress, aRequest, aLocation, aDownload ) {
335 },
336
onSecurityChange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
337 onSecurityChange: function( aWebProgress, aRequest, aState, aDownload ) {
338 },
339
340 // ---------- nsIObserver methods ----------
observe
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
370 }
371 },
372
373 // ---------- nsISupports methods ----------
374
375 // This "class" supports nsIProgressDialog, nsIWebProgressListener (by virtue
376 // of interface inheritance), nsIObserver, and nsISupports.
QueryInterface
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
388
389 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
390 return null;
391 },
392
393 // ---------- nsIInterfaceRequestor methods ----------
394
getInterface
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
407 }
408 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
409 return null;
410 },
411
412 // ---------- implementation methods ----------
413
414 // Initialize the dialog.
onLoad
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
415 onLoad: function() {
416 // Note that onLoad has finished.
417 this.loaded = true;
418
419 // Fill dialog.
420 this.loadDialog();
421
422 // Position dialog.
423 if ( this.dialog.opener ) {
424 this.dialog.moveToAlertPosition();
425 } else {
426 this.dialog.centerWindowOnScreen();
427 }
428
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();
437 }
438 },
439
440 // load dialog with initial contents
loadDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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" ) );
446
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 );
460 }
461 } else {
462 this.hide( "targetRow" );
463 }
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" );
473 }
474 }
475
476 // Set source field.
477 this.setValue( "source", this.source.spec );
478
479 var now = ( new Date() ).getTime();
480
481 // Initialize the elapsed time.
482 if ( !this.elapsed ) {
483 this.elapsed = now - this.startTime;
484 }
485
486 // Update elapsed time display.
487 this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
488 this.setValue( "timeLeft", this.getString( "unknownTime" ) );
489
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" );
501 }
502 }
503
504 // Initialize title.
505 this.setTitle();
506 },
507
508 // Cancel button stops the download (if not completed),
509 // and closes the dialog.
onCancel
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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.
517 }
518 if ( this.observer ) {
519 this.observer.observe( this, "oncancel", "" );
520 }
521 this.paused = false;
522 }
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();
528 }
529 },
530
531 // onunload event means the dialog has closed.
532 // We go through our onCancel logic to stop the download if still in progress.
onUnload
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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 );
540 }
541 }
542 this.dialog = null; // The dialog is history.
543 if ( this.mCancelDownloadOnClose ) {
544 this.onCancel();
545 }
546 },
547
548 // onpause event means the user pressed the pause/resume button
549 // Toggle the pause/resume state (see the function setPause(), below).i
onPause
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
550 onPause: function() {
551 this.paused = !this.paused;
552 },
553
554 // onlaunch event means the user pressed the launch button
555 // Invoke the launch method of the target file.
onLaunch
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
566 }
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;
574 }
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) {}
589
590 if ( !okToProceed )
591 return;
592 }
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" );
598 }
599 },
600
601 // onreveal event means the user pressed the "reveal location" button
602 // Invoke the reveal method of the target file.
onReveal
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
603 onReveal: function() {
604 try {
605 this.targetFile.reveal();
606 this.dialog.close();
607 } catch ( exception ) {
608 }
609 },
610
611 // Get filename from the target.
fileName
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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 "";
622 },
623
624 // Set the dialog title.
setTitle
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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" ) );
631
632
633 // Use file name as insert 1.
634 title = this.replaceInsert( title, 1, this.fileName() );
635
636 // Use percentage as insert 2 (if known).
637 if ( this.percent != -1 ) {
638 title = this.replaceInsert( title, 2, this.percent );
639 }
640
641 // Set dialog's title property.
642 if ( this.dialog ) {
643 this.dialog.document.title = title;
644 }
645 },
646
647 // Update the dialog to indicate specified percent complete.
setPercent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
648 setPercent: function( percent ) {
649 // Maximum percentage is 100.
650 if ( percent > 100 ) {
651 percent = 100;
652 }
653 // Test if percentage is changing.
654 if ( this.percent != percent ) {
655 this.mPercent = percent;
656
657 // If dialog not opened yet, bail early.
658 if ( !this.loaded ) {
659 return this.mPercent;
660 }
661
662 if ( percent == -1 ) {
663 // Progress meter needs to be in "undetermined" mode.
664 this.mode = "undetermined";
665
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";
671
672 // Set progress meter thermometer.
673 this.setValue( "progress", percent );
674
675 // Update progress meter percentage text.
676 this.setValue( "progressText", this.replaceInsert( this.getString( "percentMsg" ), 1, percent ) );
677 }
678
679 // Update title.
680 this.setTitle();
681 }
682 return this.mPercent;
683 },
684
685 // Update download rate and dialog display.
setRate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
686 setRate: function( rate ) {
687 this.mRate = rate;
688 return this.mRate;
689 },
690
691 // Handle download completion.
setCompleted
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
692 setCompleted: function() {
693 // If dialog hasn't loaded yet, defer this.
694 if ( !this.loaded ) {
anon:695:36
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
695 this.dialog.setTimeout( function(obj){obj.setCompleted()}, 100, this );
696 return false;
697 }
698 if ( !this.mCompleted ) {
699 this.mCompleted = true;
700
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,
706 1,
707 this.formatSeconds( this.elapsed/1000 ) );
708 string = this.replaceInsert( string,
709 2,
710 this.targetFile.fileSize >> 10 );
711
712 this.setValue( "status", string);
713 // Put progress meter at 100%.
714 this.percent = 100;
715
716 // Set time remaining to 00:00.
717 this.setValue( "timeLeft", this.formatSeconds( 0 ) );
718
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();
723
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 ) {
731 }
732
733 if ( enableButtons ) {
734 this.enable( "reveal" );
735 try {
736 if ( this.targetFile != null ) {
737 this.enable( "launch" );
738 }
739 } catch(e) {
740 }
741 }
742
743 // Disable the Pause/Resume buttons.
744 this.dialogElement( "pauseResume" ).disabled = true;
745
746 // Fix up dialog layout (which gets messed up sometimes).
747 this.dialog.sizeToContent();
748
749 // GetAttention to show the user that we're done
750 this.dialog.getAttention();
751 } else if ( this.dialog ) {
752 this.dialog.close();
753 }
754 }
755 return this.mCompleted;
756 },
757
758 // Set progress meter to given mode ("normal" or "undetermined").
setMode
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
759 setMode: function( newMode ) {
760 if ( this.mode != newMode ) {
761 // Need to update progress meter.
762 this.dialogElement( "progress" ).setAttribute( "mode", newMode );
763 }
764 return this.mMode = newMode;
765 },
766
767 // Set pause/resume state.
setPaused
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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);
773
774 // If we have an observer, tell it to suspend/resume
775 if ( this.observer ) {
776 this.observer.observe( this, pausing ? "onpause" : "onresume" , "" );
777 }
778 }
779 return this.mPaused = pausing;
780 },
781
782 // Format number of seconds in hh:mm:ss form.
formatSeconds
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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" );
795
796 if ( hours < 10 )
797 hours = "0" + hours;
798 if ( mins < 10 )
799 mins = "0" + mins;
800 if ( secs < 10 )
801 secs = "0" + secs;
802
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 );
807
808 return result;
809 },
810
811 // Get dialog element using argument as id.
dialogElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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: "",
setAttribute
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
821 setAttribute: function(id,val) {},
removeAttribute
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
822 removeAttribute: function(id) {}
823 }
824 }
825 }
826 return this.fields[ id ];
827 },
828
829 // Set dialog element value for given dialog element.
setValue
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
830 setValue: function( id, val ) {
831 this.dialogElement( id ).value = val;
832 },
833
834 // Enable dialog element.
enable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
835 enable: function( field ) {
836 this.dialogElement( field ).removeAttribute( "disabled" );
837 },
838
839 // Get localizable string from properties file.
getProperty
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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");
845 }
846 return len ? this.mBundle.formatStringFromName( propertyId, strings, len )
847 : this.mBundle.getStringFromName( propertyId );
848 },
849
850 // Get localizable string (from dialog <data> elements).
getString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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) {}
860 }
861 return this.strings[ stringId ];
862 },
863
864 // Replaces insert ("#n") with input text.
replaceInsert
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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;
870 },
871
872 // Hide a given dialog field.
hide
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
873 hide: function( field ) {
874 this.dialogElement( field ).hidden = true;
875
876 // Also hide any related separator element...
877 var sep = this.dialogElement( field+"Separator" );
878 if (sep)
879 sep.hidden = true;
880 },
881
882 // Return input in hex, prepended with "0x" and leading zeros (to 8 digits).
hex
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
883 hex: function( x ) {
884 return "0x" + ("0000000" + Number(x).toString(16)).slice(-8);
885 },
886
887 // Dump text (if debug is on).
dump
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
888 dump: function( text ) {
889 if ( this.debug ) {
890 dump( text );
891 }
892 }
893 }
894
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.
registerSelf
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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 );
907 },
908
909 // getClassObject: Return this component's factory object.
getClassObject
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
910 getClassObject: function (compMgr, cid, iid) {
911 if (!cid.equals(this.cid))
912 throw Components.results.NS_ERROR_NO_INTERFACE;
913
914 if (!iid.equals(Components.interfaces.nsIFactory))
915 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
916
917 return this.factory;
918 },
919
920 /* CID for this class */
921 cid: Components.ID("{F5D248FD-024C-4f30-B208-F3003B85BC92}"),
922
923 /* Contract ID for this class */
924 contractId: "@mozilla.org/progressdialog;1",
925
926 /* factory object */
927 factory: {
928 // createInstance: Return a new nsProgressDialog object.
createInstance
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
929 createInstance: function (outer, iid) {
930 if (outer != null)
931 throw Components.results.NS_ERROR_NO_AGGREGATION;
932
933 return (new nsProgressDialog()).QueryInterface(iid);
934 }
935 },
936
937 // canUnload: n/a (returns true)
canUnload
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
938 canUnload: function(compMgr) {
939 return true;
940 }
941 };
942
943 // NSGetModule: Return the nsIModule object.
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
944 function NSGetModule(compMgr, fileSpec) {
945 return module;
946 }