|
1 /* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* |
|
4 # This Source Code Form is subject to the terms of the Mozilla Public |
|
5 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
7 */ |
|
8 |
|
9 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
10 |
|
11 /////////////////////////////////////////////////////////////////////////////// |
|
12 //// Helper Functions |
|
13 |
|
14 /** |
|
15 * Determines if a given directory is able to be used to download to. |
|
16 * |
|
17 * @param aDirectory |
|
18 * The directory to check. |
|
19 * @return true if we can use the directory, false otherwise. |
|
20 */ |
|
21 function isUsableDirectory(aDirectory) |
|
22 { |
|
23 return aDirectory.exists() && aDirectory.isDirectory() && |
|
24 aDirectory.isWritable(); |
|
25 } |
|
26 |
|
27 // Web progress listener so we can detect errors while mLauncher is |
|
28 // streaming the data to a temporary file. |
|
29 function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) { |
|
30 this.helperAppDlg = aHelperAppDialog; |
|
31 } |
|
32 |
|
33 nsUnknownContentTypeDialogProgressListener.prototype = { |
|
34 // nsIWebProgressListener methods. |
|
35 // Look for error notifications and display alert to user. |
|
36 onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) { |
|
37 if ( aStatus != Components.results.NS_OK ) { |
|
38 // Display error alert (using text supplied by back-end). |
|
39 // FIXME this.dialog is undefined? |
|
40 Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage ); |
|
41 // Close the dialog. |
|
42 this.helperAppDlg.onCancel(); |
|
43 if ( this.helperAppDlg.mDialog ) { |
|
44 this.helperAppDlg.mDialog.close(); |
|
45 } |
|
46 } |
|
47 }, |
|
48 |
|
49 // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications. |
|
50 onProgressChange: function( aWebProgress, |
|
51 aRequest, |
|
52 aCurSelfProgress, |
|
53 aMaxSelfProgress, |
|
54 aCurTotalProgress, |
|
55 aMaxTotalProgress ) { |
|
56 }, |
|
57 |
|
58 onProgressChange64: function( aWebProgress, |
|
59 aRequest, |
|
60 aCurSelfProgress, |
|
61 aMaxSelfProgress, |
|
62 aCurTotalProgress, |
|
63 aMaxTotalProgress ) { |
|
64 }, |
|
65 |
|
66 |
|
67 |
|
68 onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) { |
|
69 }, |
|
70 |
|
71 onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) { |
|
72 }, |
|
73 |
|
74 onSecurityChange: function( aWebProgress, aRequest, state ) { |
|
75 }, |
|
76 |
|
77 onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) { |
|
78 return true; |
|
79 } |
|
80 }; |
|
81 |
|
82 /////////////////////////////////////////////////////////////////////////////// |
|
83 //// nsUnknownContentTypeDialog |
|
84 |
|
85 /* This file implements the nsIHelperAppLauncherDialog interface. |
|
86 * |
|
87 * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog, |
|
88 * comprised of: |
|
89 * - a JS constructor function |
|
90 * - a prototype providing all the interface methods and implementation stuff |
|
91 * |
|
92 * In addition, this file implements an nsIModule object that registers the |
|
93 * nsUnknownContentTypeDialog component. |
|
94 */ |
|
95 |
|
96 const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; |
|
97 const nsITimer = Components.interfaces.nsITimer; |
|
98 |
|
99 let downloadModule = {}; |
|
100 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
101 Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule); |
|
102 Components.utils.import("resource://gre/modules/DownloadPaths.jsm"); |
|
103 Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); |
|
104 Components.utils.import("resource://gre/modules/Downloads.jsm"); |
|
105 Components.utils.import("resource://gre/modules/FileUtils.jsm"); |
|
106 Components.utils.import("resource://gre/modules/Task.jsm"); |
|
107 |
|
108 /* ctor |
|
109 */ |
|
110 function nsUnknownContentTypeDialog() { |
|
111 // Initialize data properties. |
|
112 this.mLauncher = null; |
|
113 this.mContext = null; |
|
114 this.chosenApp = null; |
|
115 this.givenDefaultApp = false; |
|
116 this.updateSelf = true; |
|
117 this.mTitle = ""; |
|
118 } |
|
119 |
|
120 nsUnknownContentTypeDialog.prototype = { |
|
121 classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"), |
|
122 |
|
123 nsIMIMEInfo : Components.interfaces.nsIMIMEInfo, |
|
124 |
|
125 QueryInterface: function (iid) { |
|
126 if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) && |
|
127 !iid.equals(Components.interfaces.nsITimerCallback) && |
|
128 !iid.equals(Components.interfaces.nsISupports)) { |
|
129 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
130 } |
|
131 return this; |
|
132 }, |
|
133 |
|
134 // ---------- nsIHelperAppLauncherDialog methods ---------- |
|
135 |
|
136 // show: Open XUL dialog using window watcher. Since the dialog is not |
|
137 // modal, it needs to be a top level window and the way to open |
|
138 // one of those is via that route). |
|
139 show: function(aLauncher, aContext, aReason) { |
|
140 this.mLauncher = aLauncher; |
|
141 this.mContext = aContext; |
|
142 |
|
143 const nsITimer = Components.interfaces.nsITimer; |
|
144 this._showTimer = Components.classes["@mozilla.org/timer;1"] |
|
145 .createInstance(nsITimer); |
|
146 this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT); |
|
147 }, |
|
148 |
|
149 // When opening from new tab, if tab closes while dialog is opening, |
|
150 // (which is a race condition on the XUL file being cached and the timer |
|
151 // in nsExternalHelperAppService), the dialog gets a blur and doesn't |
|
152 // activate the OK button. So we wait a bit before doing opening it. |
|
153 reallyShow: function() { |
|
154 try { |
|
155 var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor); |
|
156 var dwi = ir.getInterface(Components.interfaces.nsIDOMWindow); |
|
157 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] |
|
158 .getService(Components.interfaces.nsIWindowWatcher); |
|
159 this.mDialog = ww.openWindow(dwi, |
|
160 "chrome://mozapps/content/downloads/unknownContentType.xul", |
|
161 null, |
|
162 "chrome,centerscreen,titlebar,dialog=yes,dependent", |
|
163 null); |
|
164 } catch (ex) { |
|
165 // The containing window may have gone away. Break reference |
|
166 // cycles and stop doing the download. |
|
167 this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED); |
|
168 return; |
|
169 } |
|
170 |
|
171 // Hook this object to the dialog. |
|
172 this.mDialog.dialog = this; |
|
173 |
|
174 // Hook up utility functions. |
|
175 this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey; |
|
176 |
|
177 // Watch for error notifications. |
|
178 var progressListener = new nsUnknownContentTypeDialogProgressListener(this); |
|
179 this.mLauncher.setWebProgressListener(progressListener); |
|
180 }, |
|
181 |
|
182 // |
|
183 // displayBadPermissionAlert() |
|
184 // |
|
185 // Diplay an alert panel about the bad permission of folder/directory. |
|
186 // |
|
187 displayBadPermissionAlert: function () { |
|
188 let bundle = |
|
189 Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); |
|
190 |
|
191 Services.prompt.alert(this.dialog, |
|
192 bundle.GetStringFromName("badPermissions.title"), |
|
193 bundle.GetStringFromName("badPermissions")); |
|
194 }, |
|
195 |
|
196 // promptForSaveToFile: Display file picker dialog and return selected file. |
|
197 // This is called by the External Helper App Service |
|
198 // after the ucth dialog calls |saveToDisk| with a null |
|
199 // target filename (no target, therefore user must pick). |
|
200 // |
|
201 // Alternatively, if the user has selected to have all |
|
202 // files download to a specific location, return that |
|
203 // location and don't ask via the dialog. |
|
204 // |
|
205 // Note - this function is called without a dialog, so it cannot access any part |
|
206 // of the dialog XUL as other functions on this object do. |
|
207 |
|
208 promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) { |
|
209 throw new Components.Exception("Async version must be used", Components.results.NS_ERROR_NOT_AVAILABLE); |
|
210 }, |
|
211 |
|
212 promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) { |
|
213 var result = null; |
|
214 |
|
215 this.mLauncher = aLauncher; |
|
216 |
|
217 let prefs = Components.classes["@mozilla.org/preferences-service;1"] |
|
218 .getService(Components.interfaces.nsIPrefBranch); |
|
219 let bundle = |
|
220 Services.strings |
|
221 .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); |
|
222 |
|
223 Task.spawn(function() { |
|
224 if (!aForcePrompt) { |
|
225 // Check to see if the user wishes to auto save to the default download |
|
226 // folder without prompting. Note that preference might not be set. |
|
227 let autodownload = false; |
|
228 try { |
|
229 autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); |
|
230 } catch (e) { } |
|
231 |
|
232 if (autodownload) { |
|
233 // Retrieve the user's default download directory |
|
234 let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); |
|
235 let defaultFolder = new FileUtils.File(preferredDir); |
|
236 |
|
237 try { |
|
238 result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); |
|
239 } |
|
240 catch (ex) { |
|
241 // When the default download directory is write-protected, |
|
242 // prompt the user for a different target file. |
|
243 } |
|
244 |
|
245 // Check to make sure we have a valid directory, otherwise, prompt |
|
246 if (result) { |
|
247 // This path is taken when we have a writable default download directory. |
|
248 aLauncher.saveDestinationAvailable(result); |
|
249 return; |
|
250 } |
|
251 } |
|
252 } |
|
253 |
|
254 // Use file picker to show dialog. |
|
255 var nsIFilePicker = Components.interfaces.nsIFilePicker; |
|
256 var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); |
|
257 var windowTitle = bundle.GetStringFromName("saveDialogTitle"); |
|
258 var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow); |
|
259 picker.init(parent, windowTitle, nsIFilePicker.modeSave); |
|
260 picker.defaultString = aDefaultFile; |
|
261 |
|
262 let gDownloadLastDir = new downloadModule.DownloadLastDir(parent); |
|
263 |
|
264 if (aSuggestedFileExtension) { |
|
265 // aSuggestedFileExtension includes the period, so strip it |
|
266 picker.defaultExtension = aSuggestedFileExtension.substring(1); |
|
267 } |
|
268 else { |
|
269 try { |
|
270 picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; |
|
271 } |
|
272 catch (ex) { } |
|
273 } |
|
274 |
|
275 var wildCardExtension = "*"; |
|
276 if (aSuggestedFileExtension) { |
|
277 wildCardExtension += aSuggestedFileExtension; |
|
278 picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); |
|
279 } |
|
280 |
|
281 picker.appendFilters( nsIFilePicker.filterAll ); |
|
282 |
|
283 // Default to lastDir if it is valid, otherwise use the user's default |
|
284 // downloads directory. getPreferredDownloadsDirectory should always |
|
285 // return a valid directory path, so we can safely default to it. |
|
286 let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); |
|
287 picker.displayDirectory = new FileUtils.File(preferredDir); |
|
288 |
|
289 gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) { |
|
290 if (lastDir && isUsableDirectory(lastDir)) |
|
291 picker.displayDirectory = lastDir; |
|
292 |
|
293 if (picker.show() == nsIFilePicker.returnCancel) { |
|
294 // null result means user cancelled. |
|
295 aLauncher.saveDestinationAvailable(null); |
|
296 return; |
|
297 } |
|
298 |
|
299 // Be sure to save the directory the user chose through the Save As... |
|
300 // dialog as the new browser.download.dir since the old one |
|
301 // didn't exist. |
|
302 result = picker.file; |
|
303 |
|
304 if (result) { |
|
305 try { |
|
306 // Remove the file so that it's not there when we ensure non-existence later; |
|
307 // this is safe because for the file to exist, the user would have had to |
|
308 // confirm that he wanted the file overwritten. |
|
309 if (result.exists()) |
|
310 result.remove(false); |
|
311 } |
|
312 catch (ex) { |
|
313 // As it turns out, the failure to remove the file, for example due to |
|
314 // permission error, will be handled below eventually somehow. |
|
315 } |
|
316 |
|
317 var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile); |
|
318 |
|
319 // Do not store the last save directory as a pref inside the private browsing mode |
|
320 gDownloadLastDir.setFile(aLauncher.source, newDir); |
|
321 |
|
322 try { |
|
323 result = this.validateLeafName(newDir, result.leafName, null); |
|
324 } |
|
325 catch (ex) { |
|
326 // When the chosen download directory is write-protected, |
|
327 // display an informative error message. |
|
328 // In all cases, download will be stopped. |
|
329 |
|
330 if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) { |
|
331 this.displayBadPermissionAlert(); |
|
332 aLauncher.saveDestinationAvailable(null); |
|
333 return; |
|
334 } |
|
335 |
|
336 } |
|
337 } |
|
338 aLauncher.saveDestinationAvailable(result); |
|
339 }.bind(this)); |
|
340 }.bind(this)).then(null, Components.utils.reportError); |
|
341 }, |
|
342 |
|
343 /** |
|
344 * Ensures that a local folder/file combination does not already exist in |
|
345 * the file system (or finds such a combination with a reasonably similar |
|
346 * leaf name), creates the corresponding file, and returns it. |
|
347 * |
|
348 * @param aLocalFolder |
|
349 * the folder where the file resides |
|
350 * @param aLeafName |
|
351 * the string name of the file (may be empty if no name is known, |
|
352 * in which case a name will be chosen) |
|
353 * @param aFileExt |
|
354 * the extension of the file, if one is known; this will be ignored |
|
355 * if aLeafName is non-empty |
|
356 * @return nsILocalFile |
|
357 * the created file |
|
358 * @throw an error such as permission doesn't allow creation of |
|
359 * file, etc. |
|
360 */ |
|
361 validateLeafName: function (aLocalFolder, aLeafName, aFileExt) |
|
362 { |
|
363 if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) { |
|
364 throw new Components.Exception("Destination directory non-existing or permission error", |
|
365 Components.results.NS_ERROR_FILE_ACCESS_DENIED); |
|
366 } |
|
367 // Remove any leading periods, since we don't want to save hidden files |
|
368 // automatically. |
|
369 aLeafName = aLeafName.replace(/^\.+/, ""); |
|
370 |
|
371 if (aLeafName == "") |
|
372 aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : ""); |
|
373 aLocalFolder.append(aLeafName); |
|
374 |
|
375 // The following assignment can throw an exception, but |
|
376 // is now caught properly in the caller of validateLeafName. |
|
377 var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder); |
|
378 |
|
379 #ifdef XP_WIN |
|
380 let ext; |
|
381 try { |
|
382 // We can fail here if there's no primary extension set |
|
383 ext = "." + this.mLauncher.MIMEInfo.primaryExtension; |
|
384 } catch (e) { } |
|
385 |
|
386 // Append a file extension if it's an executable that doesn't have one |
|
387 // but make sure we actually have an extension to add |
|
388 let leaf = createdFile.leafName; |
|
389 if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) { |
|
390 createdFile.remove(false); |
|
391 aLocalFolder.leafName = leaf + ext; |
|
392 createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder); |
|
393 } |
|
394 #endif |
|
395 |
|
396 return createdFile; |
|
397 }, |
|
398 |
|
399 // ---------- implementation methods ---------- |
|
400 |
|
401 // initDialog: Fill various dialog fields with initial content. |
|
402 initDialog : function() { |
|
403 // Put file name in window title. |
|
404 var suggestedFileName = this.mLauncher.suggestedFileName; |
|
405 |
|
406 // Some URIs do not implement nsIURL, so we can't just QI. |
|
407 var url = this.mLauncher.source; |
|
408 if (url instanceof Components.interfaces.nsINestedURI) |
|
409 url = url.innermostURI; |
|
410 |
|
411 var fname = ""; |
|
412 var iconPath = "goat"; |
|
413 this.mSourcePath = url.prePath; |
|
414 if (url instanceof Components.interfaces.nsIURL) { |
|
415 // A url, use file name from it. |
|
416 fname = iconPath = url.fileName; |
|
417 this.mSourcePath += url.directory; |
|
418 } else { |
|
419 // A generic uri, use path. |
|
420 fname = url.path; |
|
421 this.mSourcePath += url.path; |
|
422 } |
|
423 |
|
424 if (suggestedFileName) |
|
425 fname = iconPath = suggestedFileName; |
|
426 |
|
427 var displayName = fname.replace(/ +/g, " "); |
|
428 |
|
429 this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]); |
|
430 this.mDialog.document.title = this.mTitle; |
|
431 |
|
432 // Put content type, filename and location into intro. |
|
433 this.initIntro(url, fname, displayName); |
|
434 |
|
435 var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType; |
|
436 this.dialogElement("contentTypeImage").setAttribute("src", iconString); |
|
437 |
|
438 // if always-save and is-executable and no-handler |
|
439 // then set up simple ui |
|
440 var mimeType = this.mLauncher.MIMEInfo.MIMEType; |
|
441 var shouldntRememberChoice = (mimeType == "application/octet-stream" || |
|
442 mimeType == "application/x-msdownload" || |
|
443 this.mLauncher.targetFileIsExecutable); |
|
444 if (shouldntRememberChoice && !this.openWithDefaultOK()) { |
|
445 // hide featured choice |
|
446 this.dialogElement("normalBox").collapsed = true; |
|
447 // show basic choice |
|
448 this.dialogElement("basicBox").collapsed = false; |
|
449 // change button labels and icons; use "save" icon for the accept |
|
450 // button since it's the only action possible |
|
451 let acceptButton = this.mDialog.document.documentElement |
|
452 .getButton("accept"); |
|
453 acceptButton.label = this.dialogElement("strings") |
|
454 .getString("unknownAccept.label"); |
|
455 acceptButton.setAttribute("icon", "save"); |
|
456 this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label"); |
|
457 // hide other handler |
|
458 this.dialogElement("openHandler").collapsed = true; |
|
459 // set save as the selected option |
|
460 this.dialogElement("mode").selectedItem = this.dialogElement("save"); |
|
461 } |
|
462 else { |
|
463 this.initAppAndSaveToDiskValues(); |
|
464 |
|
465 // Initialize "always ask me" box. This should always be disabled |
|
466 // and set to true for the ambiguous type application/octet-stream. |
|
467 // We don't also check for application/x-msdownload here since we |
|
468 // want users to be able to autodownload .exe files. |
|
469 var rememberChoice = this.dialogElement("rememberChoice"); |
|
470 |
|
471 #if 0 |
|
472 // Just because we have a content-type of application/octet-stream |
|
473 // here doesn't actually mean that the content is of that type. Many |
|
474 // servers default to sending text/plain for file types they don't know |
|
475 // about. To account for this, the uriloader does some checking to see |
|
476 // if a file sent as text/plain contains binary characters, and if so (*) |
|
477 // it morphs the content-type into application/octet-stream so that |
|
478 // the file can be properly handled. Since this is not generic binary |
|
479 // data, rather, a data format that the system probably knows about, |
|
480 // we don't want to use the content-type provided by this dialog's |
|
481 // opener, as that's the generic application/octet-stream that the |
|
482 // uriloader has passed, rather we want to ask the MIME Service. |
|
483 // This is so we don't needlessly disable the "autohandle" checkbox. |
|
484 |
|
485 // commented out to close the opening brace in the if statement. |
|
486 // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService); |
|
487 // var type = mimeService.getTypeFromURI(this.mLauncher.source); |
|
488 // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, ""); |
|
489 |
|
490 // if (type == "application/octet-stream") { |
|
491 #endif |
|
492 if (shouldntRememberChoice) { |
|
493 rememberChoice.checked = false; |
|
494 rememberChoice.disabled = true; |
|
495 } |
|
496 else { |
|
497 rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling && |
|
498 this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally; |
|
499 } |
|
500 this.toggleRememberChoice(rememberChoice); |
|
501 |
|
502 // XXXben - menulist won't init properly, hack. |
|
503 var openHandler = this.dialogElement("openHandler"); |
|
504 openHandler.parentNode.removeChild(openHandler); |
|
505 var openHandlerBox = this.dialogElement("openHandlerBox"); |
|
506 openHandlerBox.appendChild(openHandler); |
|
507 } |
|
508 |
|
509 this.mDialog.setTimeout("dialog.postShowCallback()", 0); |
|
510 |
|
511 let acceptDelay = Services.prefs.getIntPref("security.dialog_enable_delay"); |
|
512 this.mDialog.document.documentElement.getButton("accept").disabled = true; |
|
513 this._showTimer = Components.classes["@mozilla.org/timer;1"] |
|
514 .createInstance(nsITimer); |
|
515 this._showTimer.initWithCallback(this, acceptDelay, nsITimer.TYPE_ONE_SHOT); |
|
516 }, |
|
517 |
|
518 notify: function (aTimer) { |
|
519 if (aTimer == this._showTimer) { |
|
520 if (!this.mDialog) { |
|
521 this.reallyShow(); |
|
522 } else { |
|
523 // The user may have already canceled the dialog. |
|
524 try { |
|
525 if (!this._blurred) { |
|
526 this.mDialog.document.documentElement.getButton("accept").disabled = false; |
|
527 } |
|
528 } catch (ex) {} |
|
529 this._delayExpired = true; |
|
530 } |
|
531 // The timer won't release us, so we have to release it. |
|
532 this._showTimer = null; |
|
533 } |
|
534 else if (aTimer == this._saveToDiskTimer) { |
|
535 // Since saveToDisk may open a file picker and therefore block this routine, |
|
536 // we should only call it once the dialog is closed. |
|
537 this.mLauncher.saveToDisk(null, false); |
|
538 this._saveToDiskTimer = null; |
|
539 } |
|
540 }, |
|
541 |
|
542 postShowCallback: function () { |
|
543 this.mDialog.sizeToContent(); |
|
544 |
|
545 // Set initial focus |
|
546 this.dialogElement("mode").focus(); |
|
547 }, |
|
548 |
|
549 // initIntro: |
|
550 initIntro: function(url, filename, displayname) { |
|
551 this.dialogElement( "location" ).value = displayname; |
|
552 this.dialogElement( "location" ).setAttribute("realname", filename); |
|
553 this.dialogElement( "location" ).setAttribute("tooltiptext", displayname); |
|
554 |
|
555 // if mSourcePath is a local file, then let's use the pretty path name |
|
556 // instead of an ugly url... |
|
557 var pathString; |
|
558 if (url instanceof Components.interfaces.nsIFileURL) { |
|
559 try { |
|
560 // Getting .file might throw, or .parent could be null |
|
561 pathString = url.file.parent.path; |
|
562 } catch (ex) {} |
|
563 } |
|
564 |
|
565 if (!pathString) { |
|
566 // wasn't a fileURL |
|
567 var tmpurl = url.clone(); // don't want to change the real url |
|
568 try { |
|
569 tmpurl.userPass = ""; |
|
570 } catch (ex) {} |
|
571 pathString = tmpurl.prePath; |
|
572 } |
|
573 |
|
574 // Set the location text, which is separate from the intro text so it can be cropped |
|
575 var location = this.dialogElement( "source" ); |
|
576 location.value = pathString; |
|
577 location.setAttribute("tooltiptext", this.mSourcePath); |
|
578 |
|
579 // Show the type of file. |
|
580 var type = this.dialogElement("type"); |
|
581 var mimeInfo = this.mLauncher.MIMEInfo; |
|
582 |
|
583 // 1. Try to use the pretty description of the type, if one is available. |
|
584 var typeString = mimeInfo.description; |
|
585 |
|
586 if (typeString == "") { |
|
587 // 2. If there is none, use the extension to identify the file, e.g. "ZIP file" |
|
588 var primaryExtension = ""; |
|
589 try { |
|
590 primaryExtension = mimeInfo.primaryExtension; |
|
591 } |
|
592 catch (ex) { |
|
593 } |
|
594 if (primaryExtension != "") |
|
595 typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]); |
|
596 // 3. If we can't even do that, just give up and show the MIME type. |
|
597 else |
|
598 typeString = mimeInfo.MIMEType; |
|
599 } |
|
600 // When the length is unknown, contentLength would be -1 |
|
601 if (this.mLauncher.contentLength >= 0) { |
|
602 let [size, unit] = DownloadUtils. |
|
603 convertByteUnits(this.mLauncher.contentLength); |
|
604 type.value = this.dialogElement("strings") |
|
605 .getFormattedString("orderedFileSizeWithType", |
|
606 [typeString, size, unit]); |
|
607 } |
|
608 else { |
|
609 type.value = typeString; |
|
610 } |
|
611 }, |
|
612 |
|
613 _blurred: false, |
|
614 _delayExpired: false, |
|
615 onBlur: function(aEvent) { |
|
616 this._blurred = true; |
|
617 this.mDialog.document.documentElement.getButton("accept").disabled = true; |
|
618 }, |
|
619 |
|
620 onFocus: function(aEvent) { |
|
621 this._blurred = false; |
|
622 if (this._delayExpired) { |
|
623 var script = "document.documentElement.getButton('accept').disabled = false"; |
|
624 this.mDialog.setTimeout(script, 250); |
|
625 } |
|
626 }, |
|
627 |
|
628 // Returns true if opening the default application makes sense. |
|
629 openWithDefaultOK: function() { |
|
630 // The checking is different on Windows... |
|
631 #ifdef XP_WIN |
|
632 // Windows presents some special cases. |
|
633 // We need to prevent use of "system default" when the file is |
|
634 // executable (so the user doesn't launch nasty programs downloaded |
|
635 // from the web), and, enable use of "system default" if it isn't |
|
636 // executable (because we will prompt the user for the default app |
|
637 // in that case). |
|
638 |
|
639 // Default is Ok if the file isn't executable (and vice-versa). |
|
640 return !this.mLauncher.targetFileIsExecutable; |
|
641 #else |
|
642 // On other platforms, default is Ok if there is a default app. |
|
643 // Note that nsIMIMEInfo providers need to ensure that this holds true |
|
644 // on each platform. |
|
645 return this.mLauncher.MIMEInfo.hasDefaultHandler; |
|
646 #endif |
|
647 }, |
|
648 |
|
649 // Set "default" application description field. |
|
650 initDefaultApp: function() { |
|
651 // Use description, if we can get one. |
|
652 var desc = this.mLauncher.MIMEInfo.defaultDescription; |
|
653 if (desc) { |
|
654 var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]); |
|
655 this.dialogElement("defaultHandler").label = defaultApp; |
|
656 } |
|
657 else { |
|
658 this.dialogElement("modeDeck").setAttribute("selectedIndex", "1"); |
|
659 // Hide the default handler item too, in case the user picks a |
|
660 // custom handler at a later date which triggers the menulist to show. |
|
661 this.dialogElement("defaultHandler").hidden = true; |
|
662 } |
|
663 }, |
|
664 |
|
665 // getPath: |
|
666 getPath: function (aFile) { |
|
667 #ifdef XP_MACOSX |
|
668 return aFile.leafName || aFile.path; |
|
669 #else |
|
670 return aFile.path; |
|
671 #endif |
|
672 }, |
|
673 |
|
674 // initAppAndSaveToDiskValues: |
|
675 initAppAndSaveToDiskValues: function() { |
|
676 var modeGroup = this.dialogElement("mode"); |
|
677 |
|
678 // We don't let users open .exe files or random binary data directly |
|
679 // from the browser at the moment because of security concerns. |
|
680 var openWithDefaultOK = this.openWithDefaultOK(); |
|
681 var mimeType = this.mLauncher.MIMEInfo.MIMEType; |
|
682 if (this.mLauncher.targetFileIsExecutable || ( |
|
683 (mimeType == "application/octet-stream" || |
|
684 mimeType == "application/x-msdownload") && |
|
685 !openWithDefaultOK)) { |
|
686 this.dialogElement("open").disabled = true; |
|
687 var openHandler = this.dialogElement("openHandler"); |
|
688 openHandler.disabled = true; |
|
689 openHandler.selectedItem = null; |
|
690 modeGroup.selectedItem = this.dialogElement("save"); |
|
691 return; |
|
692 } |
|
693 |
|
694 // Fill in helper app info, if there is any. |
|
695 try { |
|
696 this.chosenApp = |
|
697 this.mLauncher.MIMEInfo.preferredApplicationHandler |
|
698 .QueryInterface(Components.interfaces.nsILocalHandlerApp); |
|
699 } catch (e) { |
|
700 this.chosenApp = null; |
|
701 } |
|
702 // Initialize "default application" field. |
|
703 this.initDefaultApp(); |
|
704 |
|
705 var otherHandler = this.dialogElement("otherHandler"); |
|
706 |
|
707 // Fill application name textbox. |
|
708 if (this.chosenApp && this.chosenApp.executable && |
|
709 this.chosenApp.executable.path) { |
|
710 otherHandler.setAttribute("path", |
|
711 this.getPath(this.chosenApp.executable)); |
|
712 |
|
713 otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); |
|
714 otherHandler.hidden = false; |
|
715 } |
|
716 |
|
717 var useDefault = this.dialogElement("useSystemDefault"); |
|
718 var openHandler = this.dialogElement("openHandler"); |
|
719 openHandler.selectedIndex = 0; |
|
720 |
|
721 if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) { |
|
722 // Open (using system default). |
|
723 modeGroup.selectedItem = this.dialogElement("open"); |
|
724 } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) { |
|
725 // Open with given helper app. |
|
726 modeGroup.selectedItem = this.dialogElement("open"); |
|
727 openHandler.selectedIndex = 1; |
|
728 } else { |
|
729 // Save to disk. |
|
730 modeGroup.selectedItem = this.dialogElement("save"); |
|
731 } |
|
732 |
|
733 // If we don't have a "default app" then disable that choice. |
|
734 if (!openWithDefaultOK) { |
|
735 var useDefault = this.dialogElement("defaultHandler"); |
|
736 var isSelected = useDefault.selected; |
|
737 |
|
738 // Disable that choice. |
|
739 useDefault.hidden = true; |
|
740 // If that's the default, then switch to "save to disk." |
|
741 if (isSelected) { |
|
742 openHandler.selectedIndex = 1; |
|
743 modeGroup.selectedItem = this.dialogElement("save"); |
|
744 } |
|
745 } |
|
746 |
|
747 otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false; |
|
748 this.updateOKButton(); |
|
749 }, |
|
750 |
|
751 // Returns the user-selected application |
|
752 helperAppChoice: function() { |
|
753 return this.chosenApp; |
|
754 }, |
|
755 |
|
756 get saveToDisk() { |
|
757 return this.dialogElement("save").selected; |
|
758 }, |
|
759 |
|
760 get useOtherHandler() { |
|
761 return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1; |
|
762 }, |
|
763 |
|
764 get useSystemDefault() { |
|
765 return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0; |
|
766 }, |
|
767 |
|
768 toggleRememberChoice: function (aCheckbox) { |
|
769 this.dialogElement("settingsChange").hidden = !aCheckbox.checked; |
|
770 this.mDialog.sizeToContent(); |
|
771 }, |
|
772 |
|
773 openHandlerCommand: function () { |
|
774 var openHandler = this.dialogElement("openHandler"); |
|
775 if (openHandler.selectedItem.id == "choose") |
|
776 this.chooseApp(); |
|
777 else |
|
778 openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id); |
|
779 }, |
|
780 |
|
781 updateOKButton: function() { |
|
782 var ok = false; |
|
783 if (this.dialogElement("save").selected) { |
|
784 // This is always OK. |
|
785 ok = true; |
|
786 } |
|
787 else if (this.dialogElement("open").selected) { |
|
788 switch (this.dialogElement("openHandler").selectedIndex) { |
|
789 case 0: |
|
790 // No app need be specified in this case. |
|
791 ok = true; |
|
792 break; |
|
793 case 1: |
|
794 // only enable the OK button if we have a default app to use or if |
|
795 // the user chose an app.... |
|
796 ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); |
|
797 break; |
|
798 } |
|
799 } |
|
800 |
|
801 // Enable Ok button if ok to press. |
|
802 this.mDialog.document.documentElement.getButton("accept").disabled = !ok; |
|
803 }, |
|
804 |
|
805 // Returns true iff the user-specified helper app has been modified. |
|
806 appChanged: function() { |
|
807 return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler; |
|
808 }, |
|
809 |
|
810 updateMIMEInfo: function() { |
|
811 // Don't update mime type preferences when the preferred action is set to |
|
812 // the internal handler -- this dialog is the result of the handler fallback |
|
813 // (e.g. Content-Disposition was set as attachment) |
|
814 var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally && |
|
815 !this.dialogElement("rememberChoice").checked; |
|
816 |
|
817 var needUpdate = false; |
|
818 // If current selection differs from what's in the mime info object, |
|
819 // then we need to update. |
|
820 if (this.saveToDisk) { |
|
821 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk; |
|
822 if (needUpdate) |
|
823 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk; |
|
824 } |
|
825 else if (this.useSystemDefault) { |
|
826 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault; |
|
827 if (needUpdate) |
|
828 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault; |
|
829 } |
|
830 else { |
|
831 // For "open with", we need to check both preferred action and whether the user chose |
|
832 // a new app. |
|
833 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged(); |
|
834 if (needUpdate) { |
|
835 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp; |
|
836 // App may have changed - Update application |
|
837 var app = this.helperAppChoice(); |
|
838 this.mLauncher.MIMEInfo.preferredApplicationHandler = app; |
|
839 } |
|
840 } |
|
841 // We will also need to update if the "always ask" flag has changed. |
|
842 needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked); |
|
843 |
|
844 // One last special case: If the input "always ask" flag was false, then we always |
|
845 // update. In that case we are displaying the helper app dialog for the first |
|
846 // time for this mime type and we need to store the user's action in the mimeTypes.rdf |
|
847 // data source (whether that action has changed or not; if it didn't change, then we need |
|
848 // to store the "always ask" flag so the helper app dialog will or won't display |
|
849 // next time, per the user's selection). |
|
850 needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling; |
|
851 |
|
852 // Make sure mime info has updated setting for the "always ask" flag. |
|
853 this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked; |
|
854 |
|
855 return needUpdate && !discardUpdate; |
|
856 }, |
|
857 |
|
858 // See if the user changed things, and if so, update the |
|
859 // mimeTypes.rdf entry for this mime type. |
|
860 updateHelperAppPref: function() { |
|
861 var ha = new this.mDialog.HelperApps(); |
|
862 ha.updateTypeInfo(this.mLauncher.MIMEInfo); |
|
863 ha.destroy(); |
|
864 }, |
|
865 |
|
866 // onOK: |
|
867 onOK: function() { |
|
868 // Verify typed app path, if necessary. |
|
869 if (this.useOtherHandler) { |
|
870 var helperApp = this.helperAppChoice(); |
|
871 if (!helperApp || !helperApp.executable || |
|
872 !helperApp.executable.exists()) { |
|
873 // Show alert and try again. |
|
874 var bundle = this.dialogElement("strings"); |
|
875 var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]); |
|
876 Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg); |
|
877 |
|
878 // Disable the OK button. |
|
879 this.mDialog.document.documentElement.getButton("accept").disabled = true; |
|
880 this.dialogElement("mode").focus(); |
|
881 |
|
882 // Clear chosen application. |
|
883 this.chosenApp = null; |
|
884 |
|
885 // Leave dialog up. |
|
886 return false; |
|
887 } |
|
888 } |
|
889 |
|
890 // Remove our web progress listener (a progress dialog will be |
|
891 // taking over). |
|
892 this.mLauncher.setWebProgressListener(null); |
|
893 |
|
894 // saveToDisk and launchWithApplication can return errors in |
|
895 // certain circumstances (e.g. The user clicks cancel in the |
|
896 // "Save to Disk" dialog. In those cases, we don't want to |
|
897 // update the helper application preferences in the RDF file. |
|
898 try { |
|
899 var needUpdate = this.updateMIMEInfo(); |
|
900 |
|
901 if (this.dialogElement("save").selected) { |
|
902 // If we're using a default download location, create a path |
|
903 // for the file to be saved to to pass to |saveToDisk| - otherwise |
|
904 // we must ask the user to pick a save name. |
|
905 |
|
906 #if 0 |
|
907 var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); |
|
908 var targetFile = null; |
|
909 try { |
|
910 targetFile = prefs.getComplexValue("browser.download.defaultFolder", |
|
911 Components.interfaces.nsILocalFile); |
|
912 var leafName = this.dialogElement("location").getAttribute("realname"); |
|
913 // Ensure that we don't overwrite any existing files here. |
|
914 targetFile = this.validateLeafName(targetFile, leafName, null); |
|
915 } |
|
916 catch(e) { } |
|
917 |
|
918 this.mLauncher.saveToDisk(targetFile, false); |
|
919 #endif |
|
920 |
|
921 // see @notify |
|
922 // we cannot use opener's setTimeout, see bug 420405 |
|
923 this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"] |
|
924 .createInstance(nsITimer); |
|
925 this._saveToDiskTimer.initWithCallback(this, 0, |
|
926 nsITimer.TYPE_ONE_SHOT); |
|
927 } |
|
928 else |
|
929 this.mLauncher.launchWithApplication(null, false); |
|
930 |
|
931 // Update user pref for this mime type (if necessary). We do not |
|
932 // store anything in the mime type preferences for the ambiguous |
|
933 // type application/octet-stream. We do NOT do this for |
|
934 // application/x-msdownload since we want users to be able to |
|
935 // autodownload these to disk. |
|
936 if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream") |
|
937 this.updateHelperAppPref(); |
|
938 } catch(e) { } |
|
939 |
|
940 // Unhook dialog from this object. |
|
941 this.mDialog.dialog = null; |
|
942 |
|
943 // Close up dialog by returning true. |
|
944 return true; |
|
945 }, |
|
946 |
|
947 // onCancel: |
|
948 onCancel: function() { |
|
949 // Remove our web progress listener. |
|
950 this.mLauncher.setWebProgressListener(null); |
|
951 |
|
952 // Cancel app launcher. |
|
953 try { |
|
954 this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED); |
|
955 } catch(exception) { |
|
956 } |
|
957 |
|
958 // Unhook dialog from this object. |
|
959 this.mDialog.dialog = null; |
|
960 |
|
961 // Close up dialog by returning true. |
|
962 return true; |
|
963 }, |
|
964 |
|
965 // dialogElement: Convenience. |
|
966 dialogElement: function(id) { |
|
967 return this.mDialog.document.getElementById(id); |
|
968 }, |
|
969 |
|
970 // Retrieve the pretty description from the file |
|
971 getFileDisplayName: function getFileDisplayName(file) |
|
972 { |
|
973 #ifdef XP_WIN |
|
974 if (file instanceof Components.interfaces.nsILocalFileWin) { |
|
975 try { |
|
976 return file.getVersionInfoField("FileDescription"); |
|
977 } catch (e) {} |
|
978 } |
|
979 #endif |
|
980 #ifdef XP_MACOSX |
|
981 if (file instanceof Components.interfaces.nsILocalFileMac) { |
|
982 try { |
|
983 return file.bundleDisplayName; |
|
984 } catch (e) {} |
|
985 } |
|
986 #endif |
|
987 return file.leafName; |
|
988 }, |
|
989 |
|
990 // chooseApp: Open file picker and prompt user for application. |
|
991 chooseApp: function() { |
|
992 #ifdef XP_WIN |
|
993 // Protect against the lack of an extension |
|
994 var fileExtension = ""; |
|
995 try { |
|
996 fileExtension = this.mLauncher.MIMEInfo.primaryExtension; |
|
997 } catch(ex) { |
|
998 } |
|
999 |
|
1000 // Try to use the pretty description of the type, if one is available. |
|
1001 var typeString = this.mLauncher.MIMEInfo.description; |
|
1002 |
|
1003 if (!typeString) { |
|
1004 // If there is none, use the extension to |
|
1005 // identify the file, e.g. "ZIP file" |
|
1006 if (fileExtension) { |
|
1007 typeString = |
|
1008 this.dialogElement("strings"). |
|
1009 getFormattedString("fileType", [fileExtension.toUpperCase()]); |
|
1010 } else { |
|
1011 // If we can't even do that, just give up and show the MIME type. |
|
1012 typeString = this.mLauncher.MIMEInfo.MIMEType; |
|
1013 } |
|
1014 } |
|
1015 |
|
1016 var params = {}; |
|
1017 params.title = |
|
1018 this.dialogElement("strings").getString("chooseAppFilePickerTitle"); |
|
1019 params.description = typeString; |
|
1020 params.filename = this.mLauncher.suggestedFileName; |
|
1021 params.mimeInfo = this.mLauncher.MIMEInfo; |
|
1022 params.handlerApp = null; |
|
1023 |
|
1024 this.mDialog.openDialog("chrome://global/content/appPicker.xul", null, |
|
1025 "chrome,modal,centerscreen,titlebar,dialog=yes", |
|
1026 params); |
|
1027 |
|
1028 if (params.handlerApp && |
|
1029 params.handlerApp.executable && |
|
1030 params.handlerApp.executable.isFile()) { |
|
1031 // Remember the file they chose to run. |
|
1032 this.chosenApp = params.handlerApp; |
|
1033 |
|
1034 #else |
|
1035 var nsIFilePicker = Components.interfaces.nsIFilePicker; |
|
1036 var fp = Components.classes["@mozilla.org/filepicker;1"] |
|
1037 .createInstance(nsIFilePicker); |
|
1038 fp.init(this.mDialog, |
|
1039 this.dialogElement("strings").getString("chooseAppFilePickerTitle"), |
|
1040 nsIFilePicker.modeOpen); |
|
1041 |
|
1042 fp.appendFilters(nsIFilePicker.filterApps); |
|
1043 |
|
1044 if (fp.show() == nsIFilePicker.returnOK && fp.file) { |
|
1045 // Remember the file they chose to run. |
|
1046 var localHandlerApp = |
|
1047 Components.classes["@mozilla.org/uriloader/local-handler-app;1"]. |
|
1048 createInstance(Components.interfaces.nsILocalHandlerApp); |
|
1049 localHandlerApp.executable = fp.file; |
|
1050 this.chosenApp = localHandlerApp; |
|
1051 #endif |
|
1052 |
|
1053 // Show the "handler" menulist since we have a (user-specified) |
|
1054 // application now. |
|
1055 this.dialogElement("modeDeck").setAttribute("selectedIndex", "0"); |
|
1056 |
|
1057 // Update dialog. |
|
1058 var otherHandler = this.dialogElement("otherHandler"); |
|
1059 otherHandler.removeAttribute("hidden"); |
|
1060 otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable)); |
|
1061 otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); |
|
1062 this.dialogElement("openHandler").selectedIndex = 1; |
|
1063 this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler"); |
|
1064 |
|
1065 this.dialogElement("mode").selectedItem = this.dialogElement("open"); |
|
1066 } |
|
1067 else { |
|
1068 var openHandler = this.dialogElement("openHandler"); |
|
1069 var lastSelectedID = openHandler.getAttribute("lastSelectedItemID"); |
|
1070 if (!lastSelectedID) |
|
1071 lastSelectedID = "defaultHandler"; |
|
1072 openHandler.selectedItem = this.dialogElement(lastSelectedID); |
|
1073 } |
|
1074 }, |
|
1075 |
|
1076 // Turn this on to get debugging messages. |
|
1077 debug: false, |
|
1078 |
|
1079 // Dump text (if debug is on). |
|
1080 dump: function( text ) { |
|
1081 if ( this.debug ) { |
|
1082 dump( text ); |
|
1083 } |
|
1084 }, |
|
1085 |
|
1086 // dumpObj: |
|
1087 dumpObj: function( spec ) { |
|
1088 var val = "<undefined>"; |
|
1089 try { |
|
1090 val = eval( "this."+spec ).toString(); |
|
1091 } catch( exception ) { |
|
1092 } |
|
1093 this.dump( spec + "=" + val + "\n" ); |
|
1094 }, |
|
1095 |
|
1096 // dumpObjectProperties |
|
1097 dumpObjectProperties: function( desc, obj ) { |
|
1098 for( prop in obj ) { |
|
1099 this.dump( desc + "." + prop + "=" ); |
|
1100 var val = "<undefined>"; |
|
1101 try { |
|
1102 val = obj[ prop ]; |
|
1103 } catch ( exception ) { |
|
1104 } |
|
1105 this.dump( val + "\n" ); |
|
1106 } |
|
1107 } |
|
1108 } |
|
1109 |
|
1110 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]); |