Follow-on fix for bug 457825. Use sheet principal for agent and user sheets. r=dbaron...
[wine-gecko.git] / embedding / components / ui / helperAppDlg / nsHelperAppDlg.js
blob8315894c7c7c70b40bb08612a52dcd560062aae6
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
13 * License.
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.
22 * Contributor(s):
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,
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 * 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;
57 /* ctor
59 function nsHelperAppDialog() {
60 // Initialize data properties.
61 this.mLauncher = null;
62 this.mContext = 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;
69 this.mTitle = "";
70 this.mIsMac = false;
73 nsHelperAppDialog.prototype = {
74 // Turn this on to get debugging messages.
75 debug: false,
77 nsIMIMEInfo : Components.interfaces.nsIMIMEInfo,
79 // Dump text (if debug is on).
80 dump: function( text ) {
81 if ( this.debug ) {
82 dump( 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))
90 return this;
92 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
93 return null;
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",
110 null,
111 "chrome,titlebar,dialog=yes",
112 null );
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) {
123 var result = "";
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.");
129 var dir = null;
131 const nsILocalFile = Components.interfaces.nsILocalFile;
132 const kDownloadDirPref = "dir";
134 // Try and pull in download directory pref
135 try {
136 dir = branch.getComplexValue(kDownloadDirPref, nsILocalFile);
137 } catch (e) { }
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);
167 } else {
168 try {
169 picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
170 } catch (ex) {
174 var wildCardExtension = "*";
175 if ( aSuggestedFileExtension ) {
176 wildCardExtension += aSuggestedFileExtension;
177 picker.appendFilter( wildCardExtension, wildCardExtension );
180 picker.appendFilters( nsIFilePicker.filterAll );
182 try {
183 if (dir.exists())
184 picker.displayDirectory = dir;
185 } catch (e) { }
187 if (picker.show() == nsIFilePicker.returnCancel || !picker.file) {
188 // Null result means user cancelled.
189 return null;
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);
198 return picker.file;
201 // ---------- implementation methods ----------
203 // Web progress listener so we can detect errors while mLauncher is
204 // streaming the data to a temporary file.
205 progressListener: {
206 // Implementation properties.
207 helperAppDlg: null,
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 );
219 // Close the dialog.
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,
229 aRequest,
230 aCurSelfProgress,
231 aMaxSelfProgress,
232 aCurTotalProgress,
233 aMaxTotalProgress ) {
236 onProgressChange64: function( aWebProgress,
237 aRequest,
238 aCurSelfProgress,
239 aMaxSelfProgress,
240 aCurTotalProgress,
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 ) {
254 return true;
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();
270 try {
271 url.userPass = "";
272 } catch (ex) {}
273 var fname = "";
274 this.mSourcePath = url.prePath;
275 try {
276 url = url.QueryInterface( Components.interfaces.nsIURL );
277 // A url, use file name from it.
278 fname = url.fileName;
279 this.mSourcePath += url.directory;
280 } catch (ex) {
281 // A generic uri, use path.
282 fname = url.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
304 // sniffing
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;
311 else {
312 alwaysHandleCheckbox.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
315 // Position it.
316 if ( this.mDialog.opener ) {
317 this.mDialog.moveToAlertPosition();
318 } else {
319 this.mDialog.sizeToContent();
320 this.mDialog.centerWindowOnScreen();
323 // Set initial focus
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);
333 // initIntro:
334 initIntro: function(url, filename) {
335 var intro = this.dialogElement( "intro" );
336 var desc = this.mLauncher.MIMEInfo.description;
338 var text;
339 if ( this.mReason == REASON_CANTHANDLE )
340 text = "intro.";
341 else if (this.mReason == REASON_SERVERREQUEST )
342 text = "intro.attachment.";
343 else if (this.mReason == REASON_TYPESNIFFED )
344 text = "intro.sniffed.";
346 var modified;
347 if (desc)
348 modified = this.replaceInsert( this.getString( text + "label" ), 1, desc );
349 else
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
357 // url...
358 var pathString = url.prePath;
361 var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
362 if (fileURL)
364 var fileObject = fileURL.file;
365 if (fileObject)
367 var parentObject = fileObject.parent;
368 if (parentObject)
370 pathString = parentObject.path;
374 } catch(ex) {}
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 );
386 _timer: null,
387 notify: function (aTimer) {
388 try { // The user may have already canceled the dialog.
389 if (!this._blurred)
390 this.mDialog.document.documentElement.getButton('accept').disabled = false;
391 } catch (ex) {}
392 this._timer = null;
395 _blurred: false,
396 onBlur: function(aEvent) {
397 if (aEvent.target != this.mDialog.document)
398 return;
400 this._blurred = true;
401 this.mDialog.document.documentElement.getButton("accept").disabled = true;
404 onFocus: function(aEvent) {
405 if (aEvent.target != this.mDialog.document)
406 return;
408 this._blurred = false;
409 if (!this._timer) {
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() {
418 var result;
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
427 // in that case).
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();
434 } else {
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
437 // on each platform.
438 result = this.mLauncher.MIMEInfo.hasDefaultHandler;
440 return result;
443 // Set "default" application description field.
444 initDefaultApp: function() {
445 // Use description, if we can get one.
446 var desc = this.mLauncher.MIMEInfo.defaultDescription;
447 if ( desc ) {
448 this.dialogElement( "useSystemDefault" ).label = this.replaceInsert( this.getString( "defaultApp" ), 1, desc );
452 // getPath:
453 getPath: function(file) {
454 if (this.mIsMac) {
455 return file.leafName || file.path;
458 return file.path;
461 // initAppAndSaveToDiskValues:
462 initAppAndSaveToDiskValues: function() {
463 // Fill in helper app info, if there is any.
464 try {
465 this.chosenApp =
466 this.mLauncher.MIMEInfo.preferredApplicationHandler
467 .QueryInterface(Components.interfaces.nsILocalHandlerApp);
468 } catch (e) {
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;
491 } else {
492 // Save to disk.
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
507 this.toggleChoice();
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;
523 if (!this.mIsMac) {
524 var typed = this.dialogElement( "appPath" ).value;
525 // First, see if one was chosen via the Choose... button.
526 if ( result ) {
527 // Verify that the user didn't type in something different later.
528 if ( typed != result.executable.path ) {
529 // Use what was typed in.
530 try {
531 result.executable.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
532 } catch( e ) {
533 // Invalid path was typed.
534 result = null;
537 } else {
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 );
541 try {
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;
548 } catch( e ) {
549 result = null;
552 // Remember what was chosen.
553 this.chosenApp = result;
555 return result;
558 updateOKButton: function() {
559 var ok = false;
560 if ( this.dialogElement( "saveToDisk" ).selected )
562 // This is always OK.
563 ok = true;
565 else if ( this.dialogElement( "useSystemDefault" ).selected )
567 // No app need be specified in this case.
568 ok = true;
570 else
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;
595 if ( needUpdate )
596 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
597 } else if ( this.dialogElement( "useSystemDefault" ).selected ) {
598 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
599 if ( needUpdate )
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
603 // a new app.
604 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
605 if ( needUpdate ) {
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;
630 return needUpdate;
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
638 // automatically.
639 this.mDialog.openDialog( "chrome://communicator/content/pref/pref-applications-edit.xul",
640 "_blank",
641 "chrome,modal=yes,resizable=no",
642 this );
645 // onOK:
646 onOK: function() {
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 ) {
662 path.select();
663 path.focus();
665 // Clear chosen application.
666 this.chosenApp = null;
667 // Leave dialog up.
668 return false;
672 // Remove our web progress listener (a progress dialog will be
673 // taking over).
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.
680 try {
681 var needUpdate = this.updateMIMEInfo();
682 if ( this.dialogElement( "saveToDisk" ).selected )
683 this.mLauncher.saveToDisk( null, false );
684 else
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.
690 if ( needUpdate &&
691 this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream" )
693 this.updateHelperAppPref();
696 } catch(e) { }
698 // Unhook dialog from this object.
699 this.mDialog.dialog = null;
701 // Close up dialog by returning true.
702 return true;
705 // onCancel:
706 onCancel: function() {
707 // Remove our web progress listener.
708 this.mLauncher.setWebProgressListener( null );
710 // Cancel app launcher.
711 try {
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.
721 return 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;
753 // Update dialog.
754 this.dialogElement( "appPath" ).value =
755 this.getPath(this.chosenApp.executable);
759 // dumpInfo:
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 );
765 // Show it.
766 progress.open( this.mDialog );
769 // dumpObj:
770 dumpObj: function( spec ) {
771 var val = "<undefined>";
772 try {
773 val = eval( "this."+spec ).toString();
774 } catch( exception ) {
776 this.dump( spec + "=" + val + "\n" );
779 // dumpObjectProperties
780 dumpObjectProperties: function( desc, obj ) {
781 for( prop in obj ) {
782 this.dump( desc + "." + prop + "=" );
783 var val = "<undefined>";
784 try {
785 val = obj[ prop ];
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 ) ) {
796 // Try to get it.
797 var elem = this.mDialog.document.getElementById( id );
798 if ( elem
800 elem.firstChild
802 elem.firstChild.nodeValue ) {
803 this.strings[ id ] = elem.firstChild.nodeValue;
804 } else {
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 ) {
814 var result = text;
815 var regExp = new RegExp("#"+insertNo);
816 result = result.replace( regExp, replacementText );
817 return result;
821 // This Component's module implementation. All the code below is used to get this
822 // component registered and accessible via XPCOM.
823 var module = {
824 firstTime: true,
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",
836 this.contractId,
837 fileSpec,
838 location,
839 type );
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;
852 return this.factory;
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",
861 /* factory object */
862 factory: {
863 // createInstance: Return a new nsProgressDialog object.
864 createInstance: function (outer, iid) {
865 if (outer != null)
866 throw Components.results.NS_ERROR_NO_AGGREGATION;
868 return (new nsHelperAppDialog()).QueryInterface(iid);
872 // canUnload: n/a (returns true)
873 canUnload: function(compMgr) {
874 return true;
878 // NSGetModule: Return the nsIModule object.
879 function NSGetModule(compMgr, fileSpec) {
880 return module;
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;
894 return aLocalFile;