1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1110 @@ 1.4 +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* 1.7 +# This Source Code Form is subject to the terms of the Mozilla Public 1.8 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.10 +*/ 1.11 + 1.12 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.13 + 1.14 +/////////////////////////////////////////////////////////////////////////////// 1.15 +//// Helper Functions 1.16 + 1.17 +/** 1.18 + * Determines if a given directory is able to be used to download to. 1.19 + * 1.20 + * @param aDirectory 1.21 + * The directory to check. 1.22 + * @return true if we can use the directory, false otherwise. 1.23 + */ 1.24 +function isUsableDirectory(aDirectory) 1.25 +{ 1.26 + return aDirectory.exists() && aDirectory.isDirectory() && 1.27 + aDirectory.isWritable(); 1.28 +} 1.29 + 1.30 +// Web progress listener so we can detect errors while mLauncher is 1.31 +// streaming the data to a temporary file. 1.32 +function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) { 1.33 + this.helperAppDlg = aHelperAppDialog; 1.34 +} 1.35 + 1.36 +nsUnknownContentTypeDialogProgressListener.prototype = { 1.37 + // nsIWebProgressListener methods. 1.38 + // Look for error notifications and display alert to user. 1.39 + onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) { 1.40 + if ( aStatus != Components.results.NS_OK ) { 1.41 + // Display error alert (using text supplied by back-end). 1.42 + // FIXME this.dialog is undefined? 1.43 + Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage ); 1.44 + // Close the dialog. 1.45 + this.helperAppDlg.onCancel(); 1.46 + if ( this.helperAppDlg.mDialog ) { 1.47 + this.helperAppDlg.mDialog.close(); 1.48 + } 1.49 + } 1.50 + }, 1.51 + 1.52 + // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications. 1.53 + onProgressChange: function( aWebProgress, 1.54 + aRequest, 1.55 + aCurSelfProgress, 1.56 + aMaxSelfProgress, 1.57 + aCurTotalProgress, 1.58 + aMaxTotalProgress ) { 1.59 + }, 1.60 + 1.61 + onProgressChange64: function( aWebProgress, 1.62 + aRequest, 1.63 + aCurSelfProgress, 1.64 + aMaxSelfProgress, 1.65 + aCurTotalProgress, 1.66 + aMaxTotalProgress ) { 1.67 + }, 1.68 + 1.69 + 1.70 + 1.71 + onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) { 1.72 + }, 1.73 + 1.74 + onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) { 1.75 + }, 1.76 + 1.77 + onSecurityChange: function( aWebProgress, aRequest, state ) { 1.78 + }, 1.79 + 1.80 + onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) { 1.81 + return true; 1.82 + } 1.83 +}; 1.84 + 1.85 +/////////////////////////////////////////////////////////////////////////////// 1.86 +//// nsUnknownContentTypeDialog 1.87 + 1.88 +/* This file implements the nsIHelperAppLauncherDialog interface. 1.89 + * 1.90 + * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog, 1.91 + * comprised of: 1.92 + * - a JS constructor function 1.93 + * - a prototype providing all the interface methods and implementation stuff 1.94 + * 1.95 + * In addition, this file implements an nsIModule object that registers the 1.96 + * nsUnknownContentTypeDialog component. 1.97 + */ 1.98 + 1.99 +const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; 1.100 +const nsITimer = Components.interfaces.nsITimer; 1.101 + 1.102 +let downloadModule = {}; 1.103 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.104 +Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule); 1.105 +Components.utils.import("resource://gre/modules/DownloadPaths.jsm"); 1.106 +Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); 1.107 +Components.utils.import("resource://gre/modules/Downloads.jsm"); 1.108 +Components.utils.import("resource://gre/modules/FileUtils.jsm"); 1.109 +Components.utils.import("resource://gre/modules/Task.jsm"); 1.110 + 1.111 +/* ctor 1.112 + */ 1.113 +function nsUnknownContentTypeDialog() { 1.114 + // Initialize data properties. 1.115 + this.mLauncher = null; 1.116 + this.mContext = null; 1.117 + this.chosenApp = null; 1.118 + this.givenDefaultApp = false; 1.119 + this.updateSelf = true; 1.120 + this.mTitle = ""; 1.121 +} 1.122 + 1.123 +nsUnknownContentTypeDialog.prototype = { 1.124 + classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"), 1.125 + 1.126 + nsIMIMEInfo : Components.interfaces.nsIMIMEInfo, 1.127 + 1.128 + QueryInterface: function (iid) { 1.129 + if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) && 1.130 + !iid.equals(Components.interfaces.nsITimerCallback) && 1.131 + !iid.equals(Components.interfaces.nsISupports)) { 1.132 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.133 + } 1.134 + return this; 1.135 + }, 1.136 + 1.137 + // ---------- nsIHelperAppLauncherDialog methods ---------- 1.138 + 1.139 + // show: Open XUL dialog using window watcher. Since the dialog is not 1.140 + // modal, it needs to be a top level window and the way to open 1.141 + // one of those is via that route). 1.142 + show: function(aLauncher, aContext, aReason) { 1.143 + this.mLauncher = aLauncher; 1.144 + this.mContext = aContext; 1.145 + 1.146 + const nsITimer = Components.interfaces.nsITimer; 1.147 + this._showTimer = Components.classes["@mozilla.org/timer;1"] 1.148 + .createInstance(nsITimer); 1.149 + this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT); 1.150 + }, 1.151 + 1.152 + // When opening from new tab, if tab closes while dialog is opening, 1.153 + // (which is a race condition on the XUL file being cached and the timer 1.154 + // in nsExternalHelperAppService), the dialog gets a blur and doesn't 1.155 + // activate the OK button. So we wait a bit before doing opening it. 1.156 + reallyShow: function() { 1.157 + try { 1.158 + var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor); 1.159 + var dwi = ir.getInterface(Components.interfaces.nsIDOMWindow); 1.160 + var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] 1.161 + .getService(Components.interfaces.nsIWindowWatcher); 1.162 + this.mDialog = ww.openWindow(dwi, 1.163 + "chrome://mozapps/content/downloads/unknownContentType.xul", 1.164 + null, 1.165 + "chrome,centerscreen,titlebar,dialog=yes,dependent", 1.166 + null); 1.167 + } catch (ex) { 1.168 + // The containing window may have gone away. Break reference 1.169 + // cycles and stop doing the download. 1.170 + this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED); 1.171 + return; 1.172 + } 1.173 + 1.174 + // Hook this object to the dialog. 1.175 + this.mDialog.dialog = this; 1.176 + 1.177 + // Hook up utility functions. 1.178 + this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey; 1.179 + 1.180 + // Watch for error notifications. 1.181 + var progressListener = new nsUnknownContentTypeDialogProgressListener(this); 1.182 + this.mLauncher.setWebProgressListener(progressListener); 1.183 + }, 1.184 + 1.185 + // 1.186 + // displayBadPermissionAlert() 1.187 + // 1.188 + // Diplay an alert panel about the bad permission of folder/directory. 1.189 + // 1.190 + displayBadPermissionAlert: function () { 1.191 + let bundle = 1.192 + Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); 1.193 + 1.194 + Services.prompt.alert(this.dialog, 1.195 + bundle.GetStringFromName("badPermissions.title"), 1.196 + bundle.GetStringFromName("badPermissions")); 1.197 + }, 1.198 + 1.199 + // promptForSaveToFile: Display file picker dialog and return selected file. 1.200 + // This is called by the External Helper App Service 1.201 + // after the ucth dialog calls |saveToDisk| with a null 1.202 + // target filename (no target, therefore user must pick). 1.203 + // 1.204 + // Alternatively, if the user has selected to have all 1.205 + // files download to a specific location, return that 1.206 + // location and don't ask via the dialog. 1.207 + // 1.208 + // Note - this function is called without a dialog, so it cannot access any part 1.209 + // of the dialog XUL as other functions on this object do. 1.210 + 1.211 + promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) { 1.212 + throw new Components.Exception("Async version must be used", Components.results.NS_ERROR_NOT_AVAILABLE); 1.213 + }, 1.214 + 1.215 + promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) { 1.216 + var result = null; 1.217 + 1.218 + this.mLauncher = aLauncher; 1.219 + 1.220 + let prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.221 + .getService(Components.interfaces.nsIPrefBranch); 1.222 + let bundle = 1.223 + Services.strings 1.224 + .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); 1.225 + 1.226 + Task.spawn(function() { 1.227 + if (!aForcePrompt) { 1.228 + // Check to see if the user wishes to auto save to the default download 1.229 + // folder without prompting. Note that preference might not be set. 1.230 + let autodownload = false; 1.231 + try { 1.232 + autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); 1.233 + } catch (e) { } 1.234 + 1.235 + if (autodownload) { 1.236 + // Retrieve the user's default download directory 1.237 + let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); 1.238 + let defaultFolder = new FileUtils.File(preferredDir); 1.239 + 1.240 + try { 1.241 + result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); 1.242 + } 1.243 + catch (ex) { 1.244 + // When the default download directory is write-protected, 1.245 + // prompt the user for a different target file. 1.246 + } 1.247 + 1.248 + // Check to make sure we have a valid directory, otherwise, prompt 1.249 + if (result) { 1.250 + // This path is taken when we have a writable default download directory. 1.251 + aLauncher.saveDestinationAvailable(result); 1.252 + return; 1.253 + } 1.254 + } 1.255 + } 1.256 + 1.257 + // Use file picker to show dialog. 1.258 + var nsIFilePicker = Components.interfaces.nsIFilePicker; 1.259 + var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); 1.260 + var windowTitle = bundle.GetStringFromName("saveDialogTitle"); 1.261 + var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow); 1.262 + picker.init(parent, windowTitle, nsIFilePicker.modeSave); 1.263 + picker.defaultString = aDefaultFile; 1.264 + 1.265 + let gDownloadLastDir = new downloadModule.DownloadLastDir(parent); 1.266 + 1.267 + if (aSuggestedFileExtension) { 1.268 + // aSuggestedFileExtension includes the period, so strip it 1.269 + picker.defaultExtension = aSuggestedFileExtension.substring(1); 1.270 + } 1.271 + else { 1.272 + try { 1.273 + picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; 1.274 + } 1.275 + catch (ex) { } 1.276 + } 1.277 + 1.278 + var wildCardExtension = "*"; 1.279 + if (aSuggestedFileExtension) { 1.280 + wildCardExtension += aSuggestedFileExtension; 1.281 + picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); 1.282 + } 1.283 + 1.284 + picker.appendFilters( nsIFilePicker.filterAll ); 1.285 + 1.286 + // Default to lastDir if it is valid, otherwise use the user's default 1.287 + // downloads directory. getPreferredDownloadsDirectory should always 1.288 + // return a valid directory path, so we can safely default to it. 1.289 + let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); 1.290 + picker.displayDirectory = new FileUtils.File(preferredDir); 1.291 + 1.292 + gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) { 1.293 + if (lastDir && isUsableDirectory(lastDir)) 1.294 + picker.displayDirectory = lastDir; 1.295 + 1.296 + if (picker.show() == nsIFilePicker.returnCancel) { 1.297 + // null result means user cancelled. 1.298 + aLauncher.saveDestinationAvailable(null); 1.299 + return; 1.300 + } 1.301 + 1.302 + // Be sure to save the directory the user chose through the Save As... 1.303 + // dialog as the new browser.download.dir since the old one 1.304 + // didn't exist. 1.305 + result = picker.file; 1.306 + 1.307 + if (result) { 1.308 + try { 1.309 + // Remove the file so that it's not there when we ensure non-existence later; 1.310 + // this is safe because for the file to exist, the user would have had to 1.311 + // confirm that he wanted the file overwritten. 1.312 + if (result.exists()) 1.313 + result.remove(false); 1.314 + } 1.315 + catch (ex) { 1.316 + // As it turns out, the failure to remove the file, for example due to 1.317 + // permission error, will be handled below eventually somehow. 1.318 + } 1.319 + 1.320 + var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile); 1.321 + 1.322 + // Do not store the last save directory as a pref inside the private browsing mode 1.323 + gDownloadLastDir.setFile(aLauncher.source, newDir); 1.324 + 1.325 + try { 1.326 + result = this.validateLeafName(newDir, result.leafName, null); 1.327 + } 1.328 + catch (ex) { 1.329 + // When the chosen download directory is write-protected, 1.330 + // display an informative error message. 1.331 + // In all cases, download will be stopped. 1.332 + 1.333 + if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) { 1.334 + this.displayBadPermissionAlert(); 1.335 + aLauncher.saveDestinationAvailable(null); 1.336 + return; 1.337 + } 1.338 + 1.339 + } 1.340 + } 1.341 + aLauncher.saveDestinationAvailable(result); 1.342 + }.bind(this)); 1.343 + }.bind(this)).then(null, Components.utils.reportError); 1.344 + }, 1.345 + 1.346 + /** 1.347 + * Ensures that a local folder/file combination does not already exist in 1.348 + * the file system (or finds such a combination with a reasonably similar 1.349 + * leaf name), creates the corresponding file, and returns it. 1.350 + * 1.351 + * @param aLocalFolder 1.352 + * the folder where the file resides 1.353 + * @param aLeafName 1.354 + * the string name of the file (may be empty if no name is known, 1.355 + * in which case a name will be chosen) 1.356 + * @param aFileExt 1.357 + * the extension of the file, if one is known; this will be ignored 1.358 + * if aLeafName is non-empty 1.359 + * @return nsILocalFile 1.360 + * the created file 1.361 + * @throw an error such as permission doesn't allow creation of 1.362 + * file, etc. 1.363 + */ 1.364 + validateLeafName: function (aLocalFolder, aLeafName, aFileExt) 1.365 + { 1.366 + if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) { 1.367 + throw new Components.Exception("Destination directory non-existing or permission error", 1.368 + Components.results.NS_ERROR_FILE_ACCESS_DENIED); 1.369 + } 1.370 + // Remove any leading periods, since we don't want to save hidden files 1.371 + // automatically. 1.372 + aLeafName = aLeafName.replace(/^\.+/, ""); 1.373 + 1.374 + if (aLeafName == "") 1.375 + aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : ""); 1.376 + aLocalFolder.append(aLeafName); 1.377 + 1.378 + // The following assignment can throw an exception, but 1.379 + // is now caught properly in the caller of validateLeafName. 1.380 + var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder); 1.381 + 1.382 +#ifdef XP_WIN 1.383 + let ext; 1.384 + try { 1.385 + // We can fail here if there's no primary extension set 1.386 + ext = "." + this.mLauncher.MIMEInfo.primaryExtension; 1.387 + } catch (e) { } 1.388 + 1.389 + // Append a file extension if it's an executable that doesn't have one 1.390 + // but make sure we actually have an extension to add 1.391 + let leaf = createdFile.leafName; 1.392 + if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) { 1.393 + createdFile.remove(false); 1.394 + aLocalFolder.leafName = leaf + ext; 1.395 + createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder); 1.396 + } 1.397 +#endif 1.398 + 1.399 + return createdFile; 1.400 + }, 1.401 + 1.402 + // ---------- implementation methods ---------- 1.403 + 1.404 + // initDialog: Fill various dialog fields with initial content. 1.405 + initDialog : function() { 1.406 + // Put file name in window title. 1.407 + var suggestedFileName = this.mLauncher.suggestedFileName; 1.408 + 1.409 + // Some URIs do not implement nsIURL, so we can't just QI. 1.410 + var url = this.mLauncher.source; 1.411 + if (url instanceof Components.interfaces.nsINestedURI) 1.412 + url = url.innermostURI; 1.413 + 1.414 + var fname = ""; 1.415 + var iconPath = "goat"; 1.416 + this.mSourcePath = url.prePath; 1.417 + if (url instanceof Components.interfaces.nsIURL) { 1.418 + // A url, use file name from it. 1.419 + fname = iconPath = url.fileName; 1.420 + this.mSourcePath += url.directory; 1.421 + } else { 1.422 + // A generic uri, use path. 1.423 + fname = url.path; 1.424 + this.mSourcePath += url.path; 1.425 + } 1.426 + 1.427 + if (suggestedFileName) 1.428 + fname = iconPath = suggestedFileName; 1.429 + 1.430 + var displayName = fname.replace(/ +/g, " "); 1.431 + 1.432 + this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]); 1.433 + this.mDialog.document.title = this.mTitle; 1.434 + 1.435 + // Put content type, filename and location into intro. 1.436 + this.initIntro(url, fname, displayName); 1.437 + 1.438 + var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType; 1.439 + this.dialogElement("contentTypeImage").setAttribute("src", iconString); 1.440 + 1.441 + // if always-save and is-executable and no-handler 1.442 + // then set up simple ui 1.443 + var mimeType = this.mLauncher.MIMEInfo.MIMEType; 1.444 + var shouldntRememberChoice = (mimeType == "application/octet-stream" || 1.445 + mimeType == "application/x-msdownload" || 1.446 + this.mLauncher.targetFileIsExecutable); 1.447 + if (shouldntRememberChoice && !this.openWithDefaultOK()) { 1.448 + // hide featured choice 1.449 + this.dialogElement("normalBox").collapsed = true; 1.450 + // show basic choice 1.451 + this.dialogElement("basicBox").collapsed = false; 1.452 + // change button labels and icons; use "save" icon for the accept 1.453 + // button since it's the only action possible 1.454 + let acceptButton = this.mDialog.document.documentElement 1.455 + .getButton("accept"); 1.456 + acceptButton.label = this.dialogElement("strings") 1.457 + .getString("unknownAccept.label"); 1.458 + acceptButton.setAttribute("icon", "save"); 1.459 + this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label"); 1.460 + // hide other handler 1.461 + this.dialogElement("openHandler").collapsed = true; 1.462 + // set save as the selected option 1.463 + this.dialogElement("mode").selectedItem = this.dialogElement("save"); 1.464 + } 1.465 + else { 1.466 + this.initAppAndSaveToDiskValues(); 1.467 + 1.468 + // Initialize "always ask me" box. This should always be disabled 1.469 + // and set to true for the ambiguous type application/octet-stream. 1.470 + // We don't also check for application/x-msdownload here since we 1.471 + // want users to be able to autodownload .exe files. 1.472 + var rememberChoice = this.dialogElement("rememberChoice"); 1.473 + 1.474 +#if 0 1.475 + // Just because we have a content-type of application/octet-stream 1.476 + // here doesn't actually mean that the content is of that type. Many 1.477 + // servers default to sending text/plain for file types they don't know 1.478 + // about. To account for this, the uriloader does some checking to see 1.479 + // if a file sent as text/plain contains binary characters, and if so (*) 1.480 + // it morphs the content-type into application/octet-stream so that 1.481 + // the file can be properly handled. Since this is not generic binary 1.482 + // data, rather, a data format that the system probably knows about, 1.483 + // we don't want to use the content-type provided by this dialog's 1.484 + // opener, as that's the generic application/octet-stream that the 1.485 + // uriloader has passed, rather we want to ask the MIME Service. 1.486 + // This is so we don't needlessly disable the "autohandle" checkbox. 1.487 + 1.488 + // commented out to close the opening brace in the if statement. 1.489 + // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService); 1.490 + // var type = mimeService.getTypeFromURI(this.mLauncher.source); 1.491 + // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, ""); 1.492 + 1.493 + // if (type == "application/octet-stream") { 1.494 +#endif 1.495 + if (shouldntRememberChoice) { 1.496 + rememberChoice.checked = false; 1.497 + rememberChoice.disabled = true; 1.498 + } 1.499 + else { 1.500 + rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling && 1.501 + this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally; 1.502 + } 1.503 + this.toggleRememberChoice(rememberChoice); 1.504 + 1.505 + // XXXben - menulist won't init properly, hack. 1.506 + var openHandler = this.dialogElement("openHandler"); 1.507 + openHandler.parentNode.removeChild(openHandler); 1.508 + var openHandlerBox = this.dialogElement("openHandlerBox"); 1.509 + openHandlerBox.appendChild(openHandler); 1.510 + } 1.511 + 1.512 + this.mDialog.setTimeout("dialog.postShowCallback()", 0); 1.513 + 1.514 + let acceptDelay = Services.prefs.getIntPref("security.dialog_enable_delay"); 1.515 + this.mDialog.document.documentElement.getButton("accept").disabled = true; 1.516 + this._showTimer = Components.classes["@mozilla.org/timer;1"] 1.517 + .createInstance(nsITimer); 1.518 + this._showTimer.initWithCallback(this, acceptDelay, nsITimer.TYPE_ONE_SHOT); 1.519 + }, 1.520 + 1.521 + notify: function (aTimer) { 1.522 + if (aTimer == this._showTimer) { 1.523 + if (!this.mDialog) { 1.524 + this.reallyShow(); 1.525 + } else { 1.526 + // The user may have already canceled the dialog. 1.527 + try { 1.528 + if (!this._blurred) { 1.529 + this.mDialog.document.documentElement.getButton("accept").disabled = false; 1.530 + } 1.531 + } catch (ex) {} 1.532 + this._delayExpired = true; 1.533 + } 1.534 + // The timer won't release us, so we have to release it. 1.535 + this._showTimer = null; 1.536 + } 1.537 + else if (aTimer == this._saveToDiskTimer) { 1.538 + // Since saveToDisk may open a file picker and therefore block this routine, 1.539 + // we should only call it once the dialog is closed. 1.540 + this.mLauncher.saveToDisk(null, false); 1.541 + this._saveToDiskTimer = null; 1.542 + } 1.543 + }, 1.544 + 1.545 + postShowCallback: function () { 1.546 + this.mDialog.sizeToContent(); 1.547 + 1.548 + // Set initial focus 1.549 + this.dialogElement("mode").focus(); 1.550 + }, 1.551 + 1.552 + // initIntro: 1.553 + initIntro: function(url, filename, displayname) { 1.554 + this.dialogElement( "location" ).value = displayname; 1.555 + this.dialogElement( "location" ).setAttribute("realname", filename); 1.556 + this.dialogElement( "location" ).setAttribute("tooltiptext", displayname); 1.557 + 1.558 + // if mSourcePath is a local file, then let's use the pretty path name 1.559 + // instead of an ugly url... 1.560 + var pathString; 1.561 + if (url instanceof Components.interfaces.nsIFileURL) { 1.562 + try { 1.563 + // Getting .file might throw, or .parent could be null 1.564 + pathString = url.file.parent.path; 1.565 + } catch (ex) {} 1.566 + } 1.567 + 1.568 + if (!pathString) { 1.569 + // wasn't a fileURL 1.570 + var tmpurl = url.clone(); // don't want to change the real url 1.571 + try { 1.572 + tmpurl.userPass = ""; 1.573 + } catch (ex) {} 1.574 + pathString = tmpurl.prePath; 1.575 + } 1.576 + 1.577 + // Set the location text, which is separate from the intro text so it can be cropped 1.578 + var location = this.dialogElement( "source" ); 1.579 + location.value = pathString; 1.580 + location.setAttribute("tooltiptext", this.mSourcePath); 1.581 + 1.582 + // Show the type of file. 1.583 + var type = this.dialogElement("type"); 1.584 + var mimeInfo = this.mLauncher.MIMEInfo; 1.585 + 1.586 + // 1. Try to use the pretty description of the type, if one is available. 1.587 + var typeString = mimeInfo.description; 1.588 + 1.589 + if (typeString == "") { 1.590 + // 2. If there is none, use the extension to identify the file, e.g. "ZIP file" 1.591 + var primaryExtension = ""; 1.592 + try { 1.593 + primaryExtension = mimeInfo.primaryExtension; 1.594 + } 1.595 + catch (ex) { 1.596 + } 1.597 + if (primaryExtension != "") 1.598 + typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]); 1.599 + // 3. If we can't even do that, just give up and show the MIME type. 1.600 + else 1.601 + typeString = mimeInfo.MIMEType; 1.602 + } 1.603 + // When the length is unknown, contentLength would be -1 1.604 + if (this.mLauncher.contentLength >= 0) { 1.605 + let [size, unit] = DownloadUtils. 1.606 + convertByteUnits(this.mLauncher.contentLength); 1.607 + type.value = this.dialogElement("strings") 1.608 + .getFormattedString("orderedFileSizeWithType", 1.609 + [typeString, size, unit]); 1.610 + } 1.611 + else { 1.612 + type.value = typeString; 1.613 + } 1.614 + }, 1.615 + 1.616 + _blurred: false, 1.617 + _delayExpired: false, 1.618 + onBlur: function(aEvent) { 1.619 + this._blurred = true; 1.620 + this.mDialog.document.documentElement.getButton("accept").disabled = true; 1.621 + }, 1.622 + 1.623 + onFocus: function(aEvent) { 1.624 + this._blurred = false; 1.625 + if (this._delayExpired) { 1.626 + var script = "document.documentElement.getButton('accept').disabled = false"; 1.627 + this.mDialog.setTimeout(script, 250); 1.628 + } 1.629 + }, 1.630 + 1.631 + // Returns true if opening the default application makes sense. 1.632 + openWithDefaultOK: function() { 1.633 + // The checking is different on Windows... 1.634 +#ifdef XP_WIN 1.635 + // Windows presents some special cases. 1.636 + // We need to prevent use of "system default" when the file is 1.637 + // executable (so the user doesn't launch nasty programs downloaded 1.638 + // from the web), and, enable use of "system default" if it isn't 1.639 + // executable (because we will prompt the user for the default app 1.640 + // in that case). 1.641 + 1.642 + // Default is Ok if the file isn't executable (and vice-versa). 1.643 + return !this.mLauncher.targetFileIsExecutable; 1.644 +#else 1.645 + // On other platforms, default is Ok if there is a default app. 1.646 + // Note that nsIMIMEInfo providers need to ensure that this holds true 1.647 + // on each platform. 1.648 + return this.mLauncher.MIMEInfo.hasDefaultHandler; 1.649 +#endif 1.650 + }, 1.651 + 1.652 + // Set "default" application description field. 1.653 + initDefaultApp: function() { 1.654 + // Use description, if we can get one. 1.655 + var desc = this.mLauncher.MIMEInfo.defaultDescription; 1.656 + if (desc) { 1.657 + var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]); 1.658 + this.dialogElement("defaultHandler").label = defaultApp; 1.659 + } 1.660 + else { 1.661 + this.dialogElement("modeDeck").setAttribute("selectedIndex", "1"); 1.662 + // Hide the default handler item too, in case the user picks a 1.663 + // custom handler at a later date which triggers the menulist to show. 1.664 + this.dialogElement("defaultHandler").hidden = true; 1.665 + } 1.666 + }, 1.667 + 1.668 + // getPath: 1.669 + getPath: function (aFile) { 1.670 +#ifdef XP_MACOSX 1.671 + return aFile.leafName || aFile.path; 1.672 +#else 1.673 + return aFile.path; 1.674 +#endif 1.675 + }, 1.676 + 1.677 + // initAppAndSaveToDiskValues: 1.678 + initAppAndSaveToDiskValues: function() { 1.679 + var modeGroup = this.dialogElement("mode"); 1.680 + 1.681 + // We don't let users open .exe files or random binary data directly 1.682 + // from the browser at the moment because of security concerns. 1.683 + var openWithDefaultOK = this.openWithDefaultOK(); 1.684 + var mimeType = this.mLauncher.MIMEInfo.MIMEType; 1.685 + if (this.mLauncher.targetFileIsExecutable || ( 1.686 + (mimeType == "application/octet-stream" || 1.687 + mimeType == "application/x-msdownload") && 1.688 + !openWithDefaultOK)) { 1.689 + this.dialogElement("open").disabled = true; 1.690 + var openHandler = this.dialogElement("openHandler"); 1.691 + openHandler.disabled = true; 1.692 + openHandler.selectedItem = null; 1.693 + modeGroup.selectedItem = this.dialogElement("save"); 1.694 + return; 1.695 + } 1.696 + 1.697 + // Fill in helper app info, if there is any. 1.698 + try { 1.699 + this.chosenApp = 1.700 + this.mLauncher.MIMEInfo.preferredApplicationHandler 1.701 + .QueryInterface(Components.interfaces.nsILocalHandlerApp); 1.702 + } catch (e) { 1.703 + this.chosenApp = null; 1.704 + } 1.705 + // Initialize "default application" field. 1.706 + this.initDefaultApp(); 1.707 + 1.708 + var otherHandler = this.dialogElement("otherHandler"); 1.709 + 1.710 + // Fill application name textbox. 1.711 + if (this.chosenApp && this.chosenApp.executable && 1.712 + this.chosenApp.executable.path) { 1.713 + otherHandler.setAttribute("path", 1.714 + this.getPath(this.chosenApp.executable)); 1.715 + 1.716 + otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); 1.717 + otherHandler.hidden = false; 1.718 + } 1.719 + 1.720 + var useDefault = this.dialogElement("useSystemDefault"); 1.721 + var openHandler = this.dialogElement("openHandler"); 1.722 + openHandler.selectedIndex = 0; 1.723 + 1.724 + if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) { 1.725 + // Open (using system default). 1.726 + modeGroup.selectedItem = this.dialogElement("open"); 1.727 + } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) { 1.728 + // Open with given helper app. 1.729 + modeGroup.selectedItem = this.dialogElement("open"); 1.730 + openHandler.selectedIndex = 1; 1.731 + } else { 1.732 + // Save to disk. 1.733 + modeGroup.selectedItem = this.dialogElement("save"); 1.734 + } 1.735 + 1.736 + // If we don't have a "default app" then disable that choice. 1.737 + if (!openWithDefaultOK) { 1.738 + var useDefault = this.dialogElement("defaultHandler"); 1.739 + var isSelected = useDefault.selected; 1.740 + 1.741 + // Disable that choice. 1.742 + useDefault.hidden = true; 1.743 + // If that's the default, then switch to "save to disk." 1.744 + if (isSelected) { 1.745 + openHandler.selectedIndex = 1; 1.746 + modeGroup.selectedItem = this.dialogElement("save"); 1.747 + } 1.748 + } 1.749 + 1.750 + otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false; 1.751 + this.updateOKButton(); 1.752 + }, 1.753 + 1.754 + // Returns the user-selected application 1.755 + helperAppChoice: function() { 1.756 + return this.chosenApp; 1.757 + }, 1.758 + 1.759 + get saveToDisk() { 1.760 + return this.dialogElement("save").selected; 1.761 + }, 1.762 + 1.763 + get useOtherHandler() { 1.764 + return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1; 1.765 + }, 1.766 + 1.767 + get useSystemDefault() { 1.768 + return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0; 1.769 + }, 1.770 + 1.771 + toggleRememberChoice: function (aCheckbox) { 1.772 + this.dialogElement("settingsChange").hidden = !aCheckbox.checked; 1.773 + this.mDialog.sizeToContent(); 1.774 + }, 1.775 + 1.776 + openHandlerCommand: function () { 1.777 + var openHandler = this.dialogElement("openHandler"); 1.778 + if (openHandler.selectedItem.id == "choose") 1.779 + this.chooseApp(); 1.780 + else 1.781 + openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id); 1.782 + }, 1.783 + 1.784 + updateOKButton: function() { 1.785 + var ok = false; 1.786 + if (this.dialogElement("save").selected) { 1.787 + // This is always OK. 1.788 + ok = true; 1.789 + } 1.790 + else if (this.dialogElement("open").selected) { 1.791 + switch (this.dialogElement("openHandler").selectedIndex) { 1.792 + case 0: 1.793 + // No app need be specified in this case. 1.794 + ok = true; 1.795 + break; 1.796 + case 1: 1.797 + // only enable the OK button if we have a default app to use or if 1.798 + // the user chose an app.... 1.799 + ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 1.800 + break; 1.801 + } 1.802 + } 1.803 + 1.804 + // Enable Ok button if ok to press. 1.805 + this.mDialog.document.documentElement.getButton("accept").disabled = !ok; 1.806 + }, 1.807 + 1.808 + // Returns true iff the user-specified helper app has been modified. 1.809 + appChanged: function() { 1.810 + return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler; 1.811 + }, 1.812 + 1.813 + updateMIMEInfo: function() { 1.814 + // Don't update mime type preferences when the preferred action is set to 1.815 + // the internal handler -- this dialog is the result of the handler fallback 1.816 + // (e.g. Content-Disposition was set as attachment) 1.817 + var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally && 1.818 + !this.dialogElement("rememberChoice").checked; 1.819 + 1.820 + var needUpdate = false; 1.821 + // If current selection differs from what's in the mime info object, 1.822 + // then we need to update. 1.823 + if (this.saveToDisk) { 1.824 + needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk; 1.825 + if (needUpdate) 1.826 + this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk; 1.827 + } 1.828 + else if (this.useSystemDefault) { 1.829 + needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault; 1.830 + if (needUpdate) 1.831 + this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault; 1.832 + } 1.833 + else { 1.834 + // For "open with", we need to check both preferred action and whether the user chose 1.835 + // a new app. 1.836 + needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged(); 1.837 + if (needUpdate) { 1.838 + this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp; 1.839 + // App may have changed - Update application 1.840 + var app = this.helperAppChoice(); 1.841 + this.mLauncher.MIMEInfo.preferredApplicationHandler = app; 1.842 + } 1.843 + } 1.844 + // We will also need to update if the "always ask" flag has changed. 1.845 + needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked); 1.846 + 1.847 + // One last special case: If the input "always ask" flag was false, then we always 1.848 + // update. In that case we are displaying the helper app dialog for the first 1.849 + // time for this mime type and we need to store the user's action in the mimeTypes.rdf 1.850 + // data source (whether that action has changed or not; if it didn't change, then we need 1.851 + // to store the "always ask" flag so the helper app dialog will or won't display 1.852 + // next time, per the user's selection). 1.853 + needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling; 1.854 + 1.855 + // Make sure mime info has updated setting for the "always ask" flag. 1.856 + this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked; 1.857 + 1.858 + return needUpdate && !discardUpdate; 1.859 + }, 1.860 + 1.861 + // See if the user changed things, and if so, update the 1.862 + // mimeTypes.rdf entry for this mime type. 1.863 + updateHelperAppPref: function() { 1.864 + var ha = new this.mDialog.HelperApps(); 1.865 + ha.updateTypeInfo(this.mLauncher.MIMEInfo); 1.866 + ha.destroy(); 1.867 + }, 1.868 + 1.869 + // onOK: 1.870 + onOK: function() { 1.871 + // Verify typed app path, if necessary. 1.872 + if (this.useOtherHandler) { 1.873 + var helperApp = this.helperAppChoice(); 1.874 + if (!helperApp || !helperApp.executable || 1.875 + !helperApp.executable.exists()) { 1.876 + // Show alert and try again. 1.877 + var bundle = this.dialogElement("strings"); 1.878 + var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]); 1.879 + Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg); 1.880 + 1.881 + // Disable the OK button. 1.882 + this.mDialog.document.documentElement.getButton("accept").disabled = true; 1.883 + this.dialogElement("mode").focus(); 1.884 + 1.885 + // Clear chosen application. 1.886 + this.chosenApp = null; 1.887 + 1.888 + // Leave dialog up. 1.889 + return false; 1.890 + } 1.891 + } 1.892 + 1.893 + // Remove our web progress listener (a progress dialog will be 1.894 + // taking over). 1.895 + this.mLauncher.setWebProgressListener(null); 1.896 + 1.897 + // saveToDisk and launchWithApplication can return errors in 1.898 + // certain circumstances (e.g. The user clicks cancel in the 1.899 + // "Save to Disk" dialog. In those cases, we don't want to 1.900 + // update the helper application preferences in the RDF file. 1.901 + try { 1.902 + var needUpdate = this.updateMIMEInfo(); 1.903 + 1.904 + if (this.dialogElement("save").selected) { 1.905 + // If we're using a default download location, create a path 1.906 + // for the file to be saved to to pass to |saveToDisk| - otherwise 1.907 + // we must ask the user to pick a save name. 1.908 + 1.909 +#if 0 1.910 + var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); 1.911 + var targetFile = null; 1.912 + try { 1.913 + targetFile = prefs.getComplexValue("browser.download.defaultFolder", 1.914 + Components.interfaces.nsILocalFile); 1.915 + var leafName = this.dialogElement("location").getAttribute("realname"); 1.916 + // Ensure that we don't overwrite any existing files here. 1.917 + targetFile = this.validateLeafName(targetFile, leafName, null); 1.918 + } 1.919 + catch(e) { } 1.920 + 1.921 + this.mLauncher.saveToDisk(targetFile, false); 1.922 +#endif 1.923 + 1.924 + // see @notify 1.925 + // we cannot use opener's setTimeout, see bug 420405 1.926 + this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"] 1.927 + .createInstance(nsITimer); 1.928 + this._saveToDiskTimer.initWithCallback(this, 0, 1.929 + nsITimer.TYPE_ONE_SHOT); 1.930 + } 1.931 + else 1.932 + this.mLauncher.launchWithApplication(null, false); 1.933 + 1.934 + // Update user pref for this mime type (if necessary). We do not 1.935 + // store anything in the mime type preferences for the ambiguous 1.936 + // type application/octet-stream. We do NOT do this for 1.937 + // application/x-msdownload since we want users to be able to 1.938 + // autodownload these to disk. 1.939 + if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream") 1.940 + this.updateHelperAppPref(); 1.941 + } catch(e) { } 1.942 + 1.943 + // Unhook dialog from this object. 1.944 + this.mDialog.dialog = null; 1.945 + 1.946 + // Close up dialog by returning true. 1.947 + return true; 1.948 + }, 1.949 + 1.950 + // onCancel: 1.951 + onCancel: function() { 1.952 + // Remove our web progress listener. 1.953 + this.mLauncher.setWebProgressListener(null); 1.954 + 1.955 + // Cancel app launcher. 1.956 + try { 1.957 + this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED); 1.958 + } catch(exception) { 1.959 + } 1.960 + 1.961 + // Unhook dialog from this object. 1.962 + this.mDialog.dialog = null; 1.963 + 1.964 + // Close up dialog by returning true. 1.965 + return true; 1.966 + }, 1.967 + 1.968 + // dialogElement: Convenience. 1.969 + dialogElement: function(id) { 1.970 + return this.mDialog.document.getElementById(id); 1.971 + }, 1.972 + 1.973 + // Retrieve the pretty description from the file 1.974 + getFileDisplayName: function getFileDisplayName(file) 1.975 + { 1.976 +#ifdef XP_WIN 1.977 + if (file instanceof Components.interfaces.nsILocalFileWin) { 1.978 + try { 1.979 + return file.getVersionInfoField("FileDescription"); 1.980 + } catch (e) {} 1.981 + } 1.982 +#endif 1.983 +#ifdef XP_MACOSX 1.984 + if (file instanceof Components.interfaces.nsILocalFileMac) { 1.985 + try { 1.986 + return file.bundleDisplayName; 1.987 + } catch (e) {} 1.988 + } 1.989 +#endif 1.990 + return file.leafName; 1.991 + }, 1.992 + 1.993 + // chooseApp: Open file picker and prompt user for application. 1.994 + chooseApp: function() { 1.995 +#ifdef XP_WIN 1.996 + // Protect against the lack of an extension 1.997 + var fileExtension = ""; 1.998 + try { 1.999 + fileExtension = this.mLauncher.MIMEInfo.primaryExtension; 1.1000 + } catch(ex) { 1.1001 + } 1.1002 + 1.1003 + // Try to use the pretty description of the type, if one is available. 1.1004 + var typeString = this.mLauncher.MIMEInfo.description; 1.1005 + 1.1006 + if (!typeString) { 1.1007 + // If there is none, use the extension to 1.1008 + // identify the file, e.g. "ZIP file" 1.1009 + if (fileExtension) { 1.1010 + typeString = 1.1011 + this.dialogElement("strings"). 1.1012 + getFormattedString("fileType", [fileExtension.toUpperCase()]); 1.1013 + } else { 1.1014 + // If we can't even do that, just give up and show the MIME type. 1.1015 + typeString = this.mLauncher.MIMEInfo.MIMEType; 1.1016 + } 1.1017 + } 1.1018 + 1.1019 + var params = {}; 1.1020 + params.title = 1.1021 + this.dialogElement("strings").getString("chooseAppFilePickerTitle"); 1.1022 + params.description = typeString; 1.1023 + params.filename = this.mLauncher.suggestedFileName; 1.1024 + params.mimeInfo = this.mLauncher.MIMEInfo; 1.1025 + params.handlerApp = null; 1.1026 + 1.1027 + this.mDialog.openDialog("chrome://global/content/appPicker.xul", null, 1.1028 + "chrome,modal,centerscreen,titlebar,dialog=yes", 1.1029 + params); 1.1030 + 1.1031 + if (params.handlerApp && 1.1032 + params.handlerApp.executable && 1.1033 + params.handlerApp.executable.isFile()) { 1.1034 + // Remember the file they chose to run. 1.1035 + this.chosenApp = params.handlerApp; 1.1036 + 1.1037 +#else 1.1038 + var nsIFilePicker = Components.interfaces.nsIFilePicker; 1.1039 + var fp = Components.classes["@mozilla.org/filepicker;1"] 1.1040 + .createInstance(nsIFilePicker); 1.1041 + fp.init(this.mDialog, 1.1042 + this.dialogElement("strings").getString("chooseAppFilePickerTitle"), 1.1043 + nsIFilePicker.modeOpen); 1.1044 + 1.1045 + fp.appendFilters(nsIFilePicker.filterApps); 1.1046 + 1.1047 + if (fp.show() == nsIFilePicker.returnOK && fp.file) { 1.1048 + // Remember the file they chose to run. 1.1049 + var localHandlerApp = 1.1050 + Components.classes["@mozilla.org/uriloader/local-handler-app;1"]. 1.1051 + createInstance(Components.interfaces.nsILocalHandlerApp); 1.1052 + localHandlerApp.executable = fp.file; 1.1053 + this.chosenApp = localHandlerApp; 1.1054 +#endif 1.1055 + 1.1056 + // Show the "handler" menulist since we have a (user-specified) 1.1057 + // application now. 1.1058 + this.dialogElement("modeDeck").setAttribute("selectedIndex", "0"); 1.1059 + 1.1060 + // Update dialog. 1.1061 + var otherHandler = this.dialogElement("otherHandler"); 1.1062 + otherHandler.removeAttribute("hidden"); 1.1063 + otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable)); 1.1064 + otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); 1.1065 + this.dialogElement("openHandler").selectedIndex = 1; 1.1066 + this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler"); 1.1067 + 1.1068 + this.dialogElement("mode").selectedItem = this.dialogElement("open"); 1.1069 + } 1.1070 + else { 1.1071 + var openHandler = this.dialogElement("openHandler"); 1.1072 + var lastSelectedID = openHandler.getAttribute("lastSelectedItemID"); 1.1073 + if (!lastSelectedID) 1.1074 + lastSelectedID = "defaultHandler"; 1.1075 + openHandler.selectedItem = this.dialogElement(lastSelectedID); 1.1076 + } 1.1077 + }, 1.1078 + 1.1079 + // Turn this on to get debugging messages. 1.1080 + debug: false, 1.1081 + 1.1082 + // Dump text (if debug is on). 1.1083 + dump: function( text ) { 1.1084 + if ( this.debug ) { 1.1085 + dump( text ); 1.1086 + } 1.1087 + }, 1.1088 + 1.1089 + // dumpObj: 1.1090 + dumpObj: function( spec ) { 1.1091 + var val = "<undefined>"; 1.1092 + try { 1.1093 + val = eval( "this."+spec ).toString(); 1.1094 + } catch( exception ) { 1.1095 + } 1.1096 + this.dump( spec + "=" + val + "\n" ); 1.1097 + }, 1.1098 + 1.1099 + // dumpObjectProperties 1.1100 + dumpObjectProperties: function( desc, obj ) { 1.1101 + for( prop in obj ) { 1.1102 + this.dump( desc + "." + prop + "=" ); 1.1103 + var val = "<undefined>"; 1.1104 + try { 1.1105 + val = obj[ prop ]; 1.1106 + } catch ( exception ) { 1.1107 + } 1.1108 + this.dump( val + "\n" ); 1.1109 + } 1.1110 + } 1.1111 +} 1.1112 + 1.1113 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]);