1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
15 * The Original Code is the Mozilla browser.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2001
20 * the Initial Developer. All Rights Reserved.
23 * Bill Law <law@netscape.com>
24 * Scott MacGregor <mscott@netscape.com>
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 nsIHelperAppLauncherDialog interface.
42 * The implementation consists of a JavaScript "class" named nsHelperAppDialog,
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 * nsHelperAppDialog component.
51 const nsIHelperAppLauncherDialog
= Components
.interfaces
.nsIHelperAppLauncherDialog
;
52 const REASON_CANTHANDLE
= nsIHelperAppLauncherDialog
.REASON_CANTHANDLE
;
53 const REASON_SERVERREQUEST
= nsIHelperAppLauncherDialog
.REASON_SERVERREQUEST
;
54 const REASON_TYPESNIFFED
= nsIHelperAppLauncherDialog
.REASON_TYPESNIFFED
;
59 function nsHelperAppDialog() {
60 // Initialize data properties.
61 this.mLauncher
= null;
63 this.mSourcePath
= null;
64 this.chosenApp
= null;
65 this.givenDefaultApp
= false;
66 this.strings
= new Array
;
67 this.elements
= new Array
;
68 this.updateSelf
= true;
73 nsHelperAppDialog
.prototype = {
74 // Turn this on to get debugging messages.
77 nsIMIMEInfo
: Components
.interfaces
.nsIMIMEInfo
,
79 // Dump text (if debug is on).
80 dump: function( text
) {
86 // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
87 QueryInterface: function (iid
) {
88 if (iid
.equals(Components
.interfaces
.nsIHelperAppLauncherDialog
) ||
89 iid
.equals(Components
.interfaces
.nsISupports
))
92 Components
.returnCode
= Components
.results
.NS_ERROR_NO_INTERFACE
;
96 // ---------- nsIHelperAppLauncherDialog methods ----------
98 // show: Open XUL dialog using window watcher. Since the dialog is not
99 // modal, it needs to be a top level window and the way to open
100 // one of those is via that route).
101 show: function(aLauncher
, aContext
, aReason
) {
102 this.mLauncher
= aLauncher
;
103 this.mContext
= aContext
;
104 this.mReason
= aReason
;
105 // Display the dialog using the Window Watcher interface.
106 var ww
= Components
.classes
["@mozilla.org/embedcomp/window-watcher;1"]
107 .getService( Components
.interfaces
.nsIWindowWatcher
);
108 this.mDialog
= ww
.openWindow( null, // no parent
109 "chrome://global/content/nsHelperAppDlg.xul",
111 "chrome,titlebar,dialog=yes",
113 // Hook this object to the dialog.
114 this.mDialog
.dialog
= this;
115 // Watch for error notifications.
116 this.mIsMac
= (this.mDialog
.navigator
.platform
.indexOf( "Mac" ) != -1);
117 this.progressListener
.helperAppDlg
= this;
118 this.mLauncher
.setWebProgressListener( this.progressListener
);
121 // promptForSaveToFile: Display file picker dialog and return selected file.
122 promptForSaveToFile: function(aLauncher
, aContext
, aDefaultFile
, aSuggestedFileExtension
, aForcePrompt
) {
125 const prefSvcContractID
= "@mozilla.org/preferences-service;1";
126 const prefSvcIID
= Components
.interfaces
.nsIPrefService
;
127 var branch
= Components
.classes
[prefSvcContractID
].getService(prefSvcIID
)
128 .getBranch("browser.download.");
131 const nsILocalFile
= Components
.interfaces
.nsILocalFile
;
132 const kDownloadDirPref
= "dir";
134 // Try and pull in download directory pref
136 dir
= branch
.getComplexValue(kDownloadDirPref
, nsILocalFile
);
139 var bundle
= Components
.classes
["@mozilla.org/intl/stringbundle;1"]
140 .getService(Components
.interfaces
.nsIStringBundleService
)
141 .createBundle("chrome://global/locale/nsHelperAppDlg.properties");
143 var autoDownload
= branch
.getBoolPref("autoDownload");
144 // If the autoDownload pref is set then just download to default download directory
145 if (!aForcePrompt
&& autoDownload
&& dir
&& dir
.exists()) {
146 if (aDefaultFile
== "")
147 aDefaultFile
= bundle
.GetStringFromName("noDefaultFile") + (aSuggestedFileExtension
|| "");
148 dir
.append(aDefaultFile
);
149 return uniqueFile(dir
);
152 // Use file picker to show dialog.
153 var nsIFilePicker
= Components
.interfaces
.nsIFilePicker
;
154 var picker
= Components
.classes
[ "@mozilla.org/filepicker;1" ]
155 .createInstance( nsIFilePicker
);
157 var windowTitle
= bundle
.GetStringFromName( "saveDialogTitle" );
159 var parent
= aContext
160 .QueryInterface( Components
.interfaces
.nsIInterfaceRequestor
)
161 .getInterface( Components
.interfaces
.nsIDOMWindowInternal
);
162 picker
.init( parent
, windowTitle
, nsIFilePicker
.modeSave
);
163 picker
.defaultString
= aDefaultFile
;
164 if (aSuggestedFileExtension
) {
165 // aSuggestedFileExtension includes the period, so strip it
166 picker
.defaultExtension
= aSuggestedFileExtension
.substring(1);
169 picker
.defaultExtension
= this.mLauncher
.MIMEInfo
.primaryExtension
;
174 var wildCardExtension
= "*";
175 if ( aSuggestedFileExtension
) {
176 wildCardExtension
+= aSuggestedFileExtension
;
177 picker
.appendFilter( wildCardExtension
, wildCardExtension
);
180 picker
.appendFilters( nsIFilePicker
.filterAll
);
184 picker
.displayDirectory
= dir
;
187 if (picker
.show() == nsIFilePicker
.returnCancel
|| !picker
.file
) {
188 // Null result means user cancelled.
192 // If not using specified location save the user's choice of directory
193 if (branch
.getBoolPref("lastLocation") || autoDownload
) {
194 var directory
= picker
.file
.parent
.QueryInterface(nsILocalFile
);
195 branch
.setComplexValue(kDownloadDirPref
, nsILocalFile
, directory
);
201 // ---------- implementation methods ----------
203 // Web progress listener so we can detect errors while mLauncher is
204 // streaming the data to a temporary file.
206 // Implementation properties.
209 // nsIWebProgressListener methods.
210 // Look for error notifications and display alert to user.
211 onStatusChange: function( aWebProgress
, aRequest
, aStatus
, aMessage
) {
212 if ( aStatus
!= Components
.results
.NS_OK
) {
213 // Get prompt service.
214 var prompter
= Components
.classes
[ "@mozilla.org/embedcomp/prompt-service;1" ]
215 .getService( Components
.interfaces
.nsIPromptService
);
216 // Display error alert (using text supplied by back-end).
217 prompter
.alert( this.dialog
, this.helperAppDlg
.mTitle
, aMessage
);
220 this.helperAppDlg
.onCancel();
221 if ( this.helperAppDlg
.mDialog
) {
222 this.helperAppDlg
.mDialog
.close();
227 // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
228 onProgressChange: function( aWebProgress
,
233 aMaxTotalProgress
) {
236 onProgressChange64: function( aWebProgress
,
241 aMaxTotalProgress
) {
244 onStateChange: function( aWebProgress
, aRequest
, aStateFlags
, aStatus
) {
247 onLocationChange: function( aWebProgress
, aRequest
, aLocation
) {
250 onSecurityChange: function( aWebProgress
, aRequest
, state
) {
253 onRefreshAttempted: function( aWebProgress
, aURI
, aDelay
, aSameURI
) {
258 // initDialog: Fill various dialog fields with initial content.
259 initDialog : function() {
260 // Put product brand short name in prompt.
261 var prompt
= this.dialogElement( "prompt" );
262 var modified
= this.replaceInsert( prompt
.firstChild
.nodeValue
, 1, this.getString( "brandShortName" ) );
263 prompt
.firstChild
.nodeValue
= modified
;
265 // Put file name in window title.
266 var suggestedFileName
= this.mLauncher
.suggestedFileName
;
268 // Some URIs do not implement nsIURL, so we can't just QI.
269 var url
= this.mLauncher
.source
.clone();
274 this.mSourcePath
= url
.prePath
;
276 url
= url
.QueryInterface( Components
.interfaces
.nsIURL
);
277 // A url, use file name from it.
278 fname
= url
.fileName
;
279 this.mSourcePath
+= url
.directory
;
281 // A generic uri, use path.
283 this.mSourcePath
+= url
.path
;
286 if (suggestedFileName
)
287 fname
= suggestedFileName
;
289 this.mTitle
= this.replaceInsert( this.mDialog
.document
.title
, 1, fname
);
290 this.mDialog
.document
.title
= this.mTitle
;
292 // Put content type, filename and location into intro.
293 this.initIntro(url
, fname
);
295 var iconString
= "moz-icon://" + fname
+ "?size=32&contentType=" + this.mLauncher
.MIMEInfo
.MIMEType
;
297 this.dialogElement("contentTypeImage").setAttribute("src", iconString
);
299 this.initAppAndSaveToDiskValues();
301 // Initialize "always ask me" box. This should always be disabled
302 // and set to true for the ambiguous type application/octet-stream.
303 // Same if this dialog was forced due to a server request or type
305 var alwaysHandleCheckbox
= this.dialogElement( "alwaysHandle" );
306 if (this.mReason
!= REASON_CANTHANDLE
||
307 this.mLauncher
.MIMEInfo
.MIMEType
== "application/octet-stream"){
308 alwaysHandleCheckbox
.checked
= false;
309 alwaysHandleCheckbox
.disabled
= true;
312 alwaysHandleCheckbox
.checked
= !this.mLauncher
.MIMEInfo
.alwaysAskBeforeHandling
;
316 if ( this.mDialog
.opener
) {
317 this.mDialog
.moveToAlertPosition();
319 this.mDialog
.sizeToContent();
320 this.mDialog
.centerWindowOnScreen();
324 this.dialogElement( "mode" ).focus();
326 this.mDialog
.document
.documentElement
.getButton("accept").disabled
= true;
327 const nsITimer
= Components
.interfaces
.nsITimer
;
328 this._timer
= Components
.classes
["@mozilla.org/timer;1"]
329 .createInstance(nsITimer
);
330 this._timer
.initWithCallback(this, 250, nsITimer
.TYPE_ONE_SHOT
);
334 initIntro: function(url
, filename
) {
335 var intro
= this.dialogElement( "intro" );
336 var desc
= this.mLauncher
.MIMEInfo
.description
;
339 if ( this.mReason
== REASON_CANTHANDLE
)
341 else if (this.mReason
== REASON_SERVERREQUEST
)
342 text
= "intro.attachment.";
343 else if (this.mReason
== REASON_TYPESNIFFED
)
344 text
= "intro.sniffed.";
348 modified
= this.replaceInsert( this.getString( text
+ "label" ), 1, desc
);
350 modified
= this.getString( text
+ "noDesc.label" );
352 modified
= this.replaceInsert( modified
, 2, this.mLauncher
.MIMEInfo
.MIMEType
);
353 modified
= this.replaceInsert( modified
, 3, filename
);
354 modified
= this.replaceInsert( modified
, 4, this.getString( "brandShortName" ));
356 // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
358 var pathString
= url
.prePath
;
361 var fileURL
= url
.QueryInterface(Components
.interfaces
.nsIFileURL
);
364 var fileObject
= fileURL
.file
;
367 var parentObject
= fileObject
.parent
;
370 pathString
= parentObject
.path
;
377 intro
.firstChild
.nodeValue
= "";
378 intro
.firstChild
.nodeValue
= modified
;
380 // Set the location text, which is separate from the intro text so it can be cropped
381 var location
= this.dialogElement( "location" );
382 location
.value
= pathString
;
383 location
.setAttribute( "tooltiptext", this.mSourcePath
);
387 notify: function (aTimer
) {
388 try { // The user may have already canceled the dialog.
390 this.mDialog
.document
.documentElement
.getButton('accept').disabled
= false;
396 onBlur: function(aEvent
) {
397 if (aEvent
.target
!= this.mDialog
.document
)
400 this._blurred
= true;
401 this.mDialog
.document
.documentElement
.getButton("accept").disabled
= true;
404 onFocus: function(aEvent
) {
405 if (aEvent
.target
!= this.mDialog
.document
)
408 this._blurred
= false;
410 // Don't enable the button if the initial timer is running
411 var script
= "document.documentElement.getButton('accept').disabled = false";
412 this.mDialog
.setTimeout(script
, 250);
416 // Returns true iff opening the default application makes sense.
417 openWithDefaultOK: function() {
420 // The checking is different on Windows...
421 if ( this.mDialog
.navigator
.platform
.indexOf( "Win" ) != -1 ) {
422 // Windows presents some special cases.
423 // We need to prevent use of "system default" when the file is
424 // executable (so the user doesn't launch nasty programs downloaded
425 // from the web), and, enable use of "system default" if it isn't
426 // executable (because we will prompt the user for the default app
429 // Need to get temporary file and check for executable-ness.
430 var tmpFile
= this.mLauncher
.targetFile
;
432 // Default is Ok if the file isn't executable (and vice-versa).
433 result
= !tmpFile
.isExecutable();
435 // On other platforms, default is Ok if there is a default app.
436 // Note that nsIMIMEInfo providers need to ensure that this holds true
438 result
= this.mLauncher
.MIMEInfo
.hasDefaultHandler
;
443 // Set "default" application description field.
444 initDefaultApp: function() {
445 // Use description, if we can get one.
446 var desc
= this.mLauncher
.MIMEInfo
.defaultDescription
;
448 this.dialogElement( "useSystemDefault" ).label
= this.replaceInsert( this.getString( "defaultApp" ), 1, desc
);
453 getPath: function(file
) {
455 return file
.leafName
|| file
.path
;
461 // initAppAndSaveToDiskValues:
462 initAppAndSaveToDiskValues: function() {
463 // Fill in helper app info, if there is any.
466 this.mLauncher
.MIMEInfo
.preferredApplicationHandler
467 .QueryInterface(Components
.interfaces
.nsILocalHandlerApp
);
469 this.chosenApp
= null;
471 // Initialize "default application" field.
472 this.initDefaultApp();
474 // Fill application name textbox.
475 if (this.chosenApp
&& this.chosenApp
.executable
&&
476 this.chosenApp
.executable
.path
) {
477 this.dialogElement( "appPath" ).value
=
478 this.getPath(this.chosenApp
.executable
);
481 var useDefault
= this.dialogElement( "useSystemDefault" );;
482 if (this.mLauncher
.MIMEInfo
.preferredAction
== this.nsIMIMEInfo
.useSystemDefault
&&
483 this.mReason
!= REASON_SERVERREQUEST
) {
484 // Open (using system default).
485 useDefault
.radioGroup
.selectedItem
= useDefault
;
486 } else if (this.mLauncher
.MIMEInfo
.preferredAction
== this.nsIMIMEInfo
.useHelperApp
&&
487 this.mReason
!= REASON_SERVERREQUEST
) {
488 // Open with given helper app.
489 var openUsing
= this.dialogElement( "openUsing" );
490 openUsing
.radioGroup
.selectedItem
= openUsing
;
493 var saveToDisk
= this.dialogElement( "saveToDisk" );
494 saveToDisk
.radioGroup
.selectedItem
= saveToDisk
;
496 // If we don't have a "default app" then disable that choice.
497 if ( !this.openWithDefaultOK() ) {
498 // Disable that choice.
499 useDefault
.hidden
= true;
500 // If that's the default, then switch to "save to disk."
501 if ( useDefault
.selected
) {
502 useDefault
.radioGroup
.selectedItem
= this.dialogElement( "saveToDisk" );
506 // Enable/Disable choose application button and textfield
510 // Enable pick app button if the user chooses that option.
511 toggleChoice : function () {
512 // See what option is selected.
513 var openUsing
= this.dialogElement( "openUsing" ).selected
;
514 this.dialogElement( "chooseApp" ).disabled
= !openUsing
;
515 // appPath is always disabled on Mac
516 this.dialogElement( "appPath" ).disabled
= !openUsing
|| this.mIsMac
;
517 this.updateOKButton();
520 // Returns the user-selected application
521 helperAppChoice: function() {
522 var result
= this.chosenApp
;
524 var typed
= this.dialogElement( "appPath" ).value
;
525 // First, see if one was chosen via the Choose... button.
527 // Verify that the user didn't type in something different later.
528 if ( typed
!= result
.executable
.path
) {
529 // Use what was typed in.
531 result
.executable
.QueryInterface( Components
.interfaces
.nsILocalFile
).initWithPath( typed
);
533 // Invalid path was typed.
538 // The user didn't use the Choose... button, try using what they typed in.
539 var localFile
= Components
.classes
[ "@mozilla.org/file/local;1" ]
540 .createInstance( Components
.interfaces
.nsILocalFile
);
542 localFile
.initWithPath( typed
);
543 result
= Components
.classes
[
544 "@mozilla.org/uriloader/local-handler-app;1"].
545 createInstance(Components
.interfaces
.nsILocalHandlerApp
);
546 result
.executable
= localFile
;
552 // Remember what was chosen.
553 this.chosenApp
= result
;
558 updateOKButton: function() {
560 if ( this.dialogElement( "saveToDisk" ).selected
)
562 // This is always OK.
565 else if ( this.dialogElement( "useSystemDefault" ).selected
)
567 // No app need be specified in this case.
572 // only enable the OK button if we have a default app to use or if
573 // the user chose an app....
574 ok
= this.chosenApp
|| /\S/.test( this.dialogElement( "appPath" ).value
);
577 // Enable Ok button if ok to press.
578 this.mDialog
.document
.documentElement
.getButton( "accept" ).disabled
= !ok
;
581 // Returns true iff the user-specified helper app has been modified.
582 appChanged: function() {
583 return this.helperAppChoice() != this.mLauncher
.MIMEInfo
.preferredApplicationHandler
;
586 updateMIMEInfo: function() {
587 var needUpdate
= false;
588 // If current selection differs from what's in the mime info object,
589 // then we need to update.
590 // However, we don't want to change the action all nsIMIMEInfo objects to
591 // saveToDisk if mReason is REASON_SERVERREQUEST.
592 if ( this.dialogElement( "saveToDisk" ).selected
&&
593 this.mReason
!= REASON_SERVERREQUEST
) {
594 needUpdate
= this.mLauncher
.MIMEInfo
.preferredAction
!= this.nsIMIMEInfo
.saveToDisk
;
596 this.mLauncher
.MIMEInfo
.preferredAction
= this.nsIMIMEInfo
.saveToDisk
;
597 } else if ( this.dialogElement( "useSystemDefault" ).selected
) {
598 needUpdate
= this.mLauncher
.MIMEInfo
.preferredAction
!= this.nsIMIMEInfo
.useSystemDefault
;
600 this.mLauncher
.MIMEInfo
.preferredAction
= this.nsIMIMEInfo
.useSystemDefault
;
601 } else if ( this.dialogElement( "openUsing" ).selected
) {
602 // For "open with", we need to check both preferred action and whether the user chose
604 needUpdate
= this.mLauncher
.MIMEInfo
.preferredAction
!= this.nsIMIMEInfo
.useHelperApp
|| this.appChanged();
606 this.mLauncher
.MIMEInfo
.preferredAction
= this.nsIMIMEInfo
.useHelperApp
;
607 // App may have changed - Update application
608 var app
= this.helperAppChoice();
609 this.mLauncher
.MIMEInfo
.preferredApplicationHandler
= app
;
612 // Only care about the state of "always ask" if this dialog wasn't forced
613 if ( this.mReason
== REASON_CANTHANDLE
)
615 // We will also need to update if the "always ask" flag has changed.
616 needUpdate
= needUpdate
|| this.mLauncher
.MIMEInfo
.alwaysAskBeforeHandling
== this.dialogElement( "alwaysHandle" ).checked
;
618 // One last special case: If the input "always ask" flag was false, then we always
619 // update. In that case we are displaying the helper app dialog for the first
620 // time for this mime type and we need to store the user's action in the mimeTypes.rdf
621 // data source (whether that action has changed or not; if it didn't change, then we need
622 // to store the "always ask" flag so the helper app dialog will or won't display
623 // next time, per the user's selection).
624 needUpdate
= needUpdate
|| !this.mLauncher
.MIMEInfo
.alwaysAskBeforeHandling
;
626 // Make sure mime info has updated setting for the "always ask" flag.
627 this.mLauncher
.MIMEInfo
.alwaysAskBeforeHandling
= !this.dialogElement( "alwaysHandle" ).checked
;
633 // See if the user changed things, and if so, update the
634 // mimeTypes.rdf entry for this mime type.
635 updateHelperAppPref: function() {
636 // We update by passing this mime info into the "Edit Type" helper app
637 // pref dialog. It will update the data source and close the dialog
639 this.mDialog
.openDialog( "chrome://communicator/content/pref/pref-applications-edit.xul",
641 "chrome,modal=yes,resizable=no",
647 // Verify typed app path, if necessary.
648 if ( this.dialogElement( "openUsing" ).selected
) {
649 var helperApp
= this.helperAppChoice();
650 if ( !helperApp
|| !helperApp
.executable
||
651 !helperApp
.executable
.exists() ) {
652 // Show alert and try again.
653 var msg
= this.replaceInsert( this.getString( "badApp" ), 1, this.dialogElement( "appPath" ).value
);
654 var svc
= Components
.classes
[ "@mozilla.org/embedcomp/prompt-service;1" ]
655 .getService( Components
.interfaces
.nsIPromptService
);
656 svc
.alert( this.mDialog
, this.getString( "badApp.title" ), msg
);
657 // Disable the OK button.
658 this.mDialog
.document
.documentElement
.getButton( "accept" ).disabled
= true;
659 // Select and focus the input field if input field is not disabled
660 var path
= this.dialogElement( "appPath" );
661 if ( !path
.disabled
) {
665 // Clear chosen application.
666 this.chosenApp
= null;
672 // Remove our web progress listener (a progress dialog will be
674 this.mLauncher
.setWebProgressListener( null );
676 // saveToDisk and launchWithApplication can return errors in
677 // certain circumstances (e.g. The user clicks cancel in the
678 // "Save to Disk" dialog. In those cases, we don't want to
679 // update the helper application preferences in the RDF file.
681 var needUpdate
= this.updateMIMEInfo();
682 if ( this.dialogElement( "saveToDisk" ).selected
)
683 this.mLauncher
.saveToDisk( null, false );
685 this.mLauncher
.launchWithApplication( null, false );
687 // Update user pref for this mime type (if necessary). We do not
688 // store anything in the mime type preferences for the ambiguous
689 // type application/octet-stream.
691 this.mLauncher
.MIMEInfo
.MIMEType
!= "application/octet-stream" )
693 this.updateHelperAppPref();
698 // Unhook dialog from this object.
699 this.mDialog
.dialog
= null;
701 // Close up dialog by returning true.
706 onCancel: function() {
707 // Remove our web progress listener.
708 this.mLauncher
.setWebProgressListener( null );
710 // Cancel app launcher.
712 const NS_BINDING_ABORTED
= 0x804b0002;
713 this.mLauncher
.cancel(NS_BINDING_ABORTED
);
714 } catch( exception
) {
717 // Unhook dialog from this object.
718 this.mDialog
.dialog
= null;
720 // Close up dialog by returning true.
724 // dialogElement: Try cache; obtain from document if not there.
725 dialogElement: function( id
) {
726 // Check if we've already fetched it.
727 if ( !( id
in this.elements
) ) {
728 // No, then get it from dialog.
729 this.elements
[ id
] = this.mDialog
.document
.getElementById( id
);
731 return this.elements
[ id
];
734 // chooseApp: Open file picker and prompt user for application.
735 chooseApp: function() {
736 var nsIFilePicker
= Components
.interfaces
.nsIFilePicker
;
737 var fp
= Components
.classes
["@mozilla.org/filepicker;1"].createInstance( nsIFilePicker
);
738 fp
.init( this.mDialog
,
739 this.getString( "chooseAppFilePickerTitle" ),
740 nsIFilePicker
.modeOpen
);
742 // XXX - We want to say nsIFilePicker.filterExecutable or something
743 fp
.appendFilters( nsIFilePicker
.filterAll
);
745 if ( fp
.show() == nsIFilePicker
.returnOK
&& fp
.file
) {
746 // Remember the file they chose to run.
748 var localHandler
= Components
.classes
[
749 "@mozilla.org/uriloader/local-handler-app;1"].
750 createInstance(Components
.interfaces
.nsILocalHandlerApp
);
751 localHandler
.executable
= fp
.file
;
752 this.chosenApp
= localHandler
;
754 this.dialogElement( "appPath" ).value
=
755 this.getPath(this.chosenApp
.executable
);
760 doDebug: function() {
761 const nsIProgressDialog
= Components
.interfaces
.nsIProgressDialog
;
762 // Open new progress dialog.
763 var progress
= Components
.classes
[ "@mozilla.org/progressdialog;1" ]
764 .createInstance( nsIProgressDialog
);
766 progress
.open( this.mDialog
);
770 dumpObj: function( spec
) {
771 var val
= "<undefined>";
773 val
= eval( "this."+spec
).toString();
774 } catch( exception
) {
776 this.dump( spec
+ "=" + val
+ "\n" );
779 // dumpObjectProperties
780 dumpObjectProperties: function( desc
, obj
) {
782 this.dump( desc
+ "." + prop
+ "=" );
783 var val
= "<undefined>";
786 } catch ( exception
) {
788 this.dump( val
+ "\n" );
792 // getString: Fetch data string from dialog content (and cache it).
793 getString: function( id
) {
794 // Check if we've fetched this string already.
795 if ( !( id
in this.strings
) ) {
797 var elem
= this.mDialog
.document
.getElementById( id
);
802 elem
.firstChild
.nodeValue
) {
803 this.strings
[ id
] = elem
.firstChild
.nodeValue
;
805 // If unable to fetch string, use an empty string.
806 this.strings
[ id
] = "";
809 return this.strings
[ id
];
812 // replaceInsert: Replace given insert with replacement text and return the result.
813 replaceInsert: function( text
, insertNo
, replacementText
) {
815 var regExp
= new RegExp("#"+insertNo
);
816 result
= result
.replace( regExp
, replacementText
);
821 // This Component's module implementation. All the code below is used to get this
822 // component registered and accessible via XPCOM.
826 // registerSelf: Register this component.
827 registerSelf: function (compMgr
, fileSpec
, location
, type
) {
828 if (this.firstTime
) {
829 this.firstTime
= false;
830 throw Components
.results
.NS_ERROR_FACTORY_REGISTER_AGAIN
;
832 compMgr
= compMgr
.QueryInterface(Components
.interfaces
.nsIComponentRegistrar
);
834 compMgr
.registerFactoryLocation( this.cid
,
835 "Mozilla Helper App Launcher Dialog",
842 // getClassObject: Return this component's factory object.
843 getClassObject: function (compMgr
, cid
, iid
) {
844 if (!cid
.equals(this.cid
)) {
845 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
848 if (!iid
.equals(Components
.interfaces
.nsIFactory
)) {
849 throw Components
.results
.NS_ERROR_NOT_IMPLEMENTED
;
855 /* CID for this class */
856 cid
: Components
.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
858 /* Contract ID for this class */
859 contractId
: "@mozilla.org/helperapplauncherdialog;1",
863 // createInstance: Return a new nsProgressDialog object.
864 createInstance: function (outer
, iid
) {
866 throw Components
.results
.NS_ERROR_NO_AGGREGATION
;
868 return (new nsHelperAppDialog()).QueryInterface(iid
);
872 // canUnload: n/a (returns true)
873 canUnload: function(compMgr
) {
878 // NSGetModule: Return the nsIModule object.
879 function NSGetModule(compMgr
, fileSpec
) {
883 // Since we're automatically downloading, we don't get the file picker's
884 // logic to check for existing files, so we need to do that here.
886 // Note - this code is identical to that in contentAreaUtils.js.
887 // If you are updating this code, update that code too! We can't share code
888 // here since this is called in a js component.
889 function uniqueFile(aLocalFile
) {
890 while (aLocalFile
.exists()) {
891 parts
= /(-\d+)?(\.[^.]+)?$/.test(aLocalFile
.leafName
);
892 aLocalFile
.leafName
= RegExp
.leftContext
+ (RegExp
.$1 - 1) + RegExp
.$2;