Bug 327977 ? Password manager overwrites page data for password-only forms. r=gavin
[wine-gecko.git] / toolkit / content / contentAreaUtils.js
blob34a7ef2b16916425cb41c745f8806394c050465c
1 # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 mozilla.org code.
17 # The Initial Developer of the Original Code is
18 # Netscape Communications Corporation.
19 # Portions created by the Initial Developer are Copyright (C) 1998
20 # the Initial Developer. All Rights Reserved.
22 # Contributor(s):
23 # Ben Goodger <ben@netscape.com> (Save File)
24 # Fredrik Holmqvist <thesuckiestemail@yahoo.se>
25 # Asaf Romano <mozilla.mano@sent.com>
27 # Alternatively, the contents of this file may be used under the terms of
28 # either the GNU General Public License Version 2 or later (the "GPL"), or
29 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 # in which case the provisions of the GPL or the LGPL are applicable instead
31 # of those above. If you wish to allow use of your version of this file only
32 # under the terms of either the GPL or the LGPL, and not to allow others to
33 # use your version of this file under the terms of the MPL, indicate your
34 # decision by deleting the provisions above and replace them with the notice
35 # and other provisions required by the GPL or the LGPL. If you do not delete
36 # the provisions above, a recipient may use your version of this file under
37 # the terms of any one of the MPL, the GPL or the LGPL.
39 # ***** END LICENSE BLOCK *****
41 /**
42 * urlSecurityCheck: JavaScript wrapper for checkLoadURIWithPrincipal
43 * and checkLoadURIStrWithPrincipal.
44 * If |aPrincipal| is not allowed to link to |aURL|, this function throws with
45 * an error message.
47 * @param aURL
48 * The URL a page has linked to. This could be passed either as a string
49 * or as a nsIURI object.
50 * @param aPrincipal
51 * The principal of the document from which aURL came.
52 * @param aFlags
53 * Flags to be passed to checkLoadURIStr. If undefined,
54 * nsIScriptSecurityManager.STANDARD will be passed.
56 function urlSecurityCheck(aURL, aPrincipal, aFlags)
58 const nsIScriptSecurityManager =
59 Components.interfaces.nsIScriptSecurityManager;
60 var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
61 .getService(nsIScriptSecurityManager);
62 if (aFlags === undefined)
63 aFlags = nsIScriptSecurityManager.STANDARD;
65 try {
66 if (aURL instanceof Components.interfaces.nsIURI)
67 secMan.checkLoadURIWithPrincipal(aPrincipal, aURL, aFlags);
68 else
69 secMan.checkLoadURIStrWithPrincipal(aPrincipal, aURL, aFlags);
70 } catch (e) {
71 // XXXmano: dump the principal url here too
72 throw "Load of " + aURL + " denied.";
76 /**
77 * Determine whether or not a given focused DOMWindow is in the content area.
78 **/
79 function isContentFrame(aFocusedWindow)
81 if (!aFocusedWindow)
82 return false;
84 return (aFocusedWindow.top == window.content);
88 // Clientelle: (Make sure you don't break any of these)
89 // - File -> Save Page/Frame As...
90 // - Context -> Save Page/Frame As...
91 // - Context -> Save Link As...
92 // - Alt-Click links in web pages
93 // - Alt-Click links in the UI
95 // Try saving each of these types:
96 // - A complete webpage using File->Save Page As, and Context->Save Page As
97 // - A webpage as HTML only using the above methods
98 // - A webpage as Text only using the above methods
99 // - An image with an extension (e.g. .jpg) in its file name, using
100 // Context->Save Image As...
101 // - An image without an extension (e.g. a banner ad on cnn.com) using
102 // the above method.
103 // - A linked document using Save Link As...
104 // - A linked document using Alt-click Save Link As...
106 function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
107 aSkipPrompt, aReferrer)
109 internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
110 aFilePickerTitleKey, null, aReferrer, aSkipPrompt);
113 // Just like saveURL, but will get some info off the image before
114 // calling internalSave
115 // Clientelle: (Make sure you don't break any of these)
116 // - Context -> Save Image As...
117 const imgICache = Components.interfaces.imgICache;
118 const nsISupportsCString = Components.interfaces.nsISupportsCString;
120 function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
121 aSkipPrompt, aReferrer)
123 var contentType = null;
124 var contentDisposition = null;
125 if (!aShouldBypassCache) {
126 try {
127 var imageCache = Components.classes["@mozilla.org/image/cache;1"]
128 .getService(imgICache);
129 var props =
130 imageCache.findEntryProperties(makeURI(aURL, getCharsetforSave(null)));
131 if (props) {
132 contentType = props.get("type", nsISupportsCString);
133 contentDisposition = props.get("content-disposition",
134 nsISupportsCString);
136 } catch (e) {
137 // Failure to get type and content-disposition off the image is non-fatal
140 internalSave(aURL, null, aFileName, contentDisposition, contentType,
141 aShouldBypassCache, aFilePickerTitleKey, null, aReferrer, aSkipPrompt);
144 function saveFrameDocument()
146 var focusedWindow = document.commandDispatcher.focusedWindow;
147 if (isContentFrame(focusedWindow))
148 saveDocument(focusedWindow.document);
151 function saveDocument(aDocument, aSkipPrompt)
153 if (!aDocument)
154 throw "Must have a document when calling saveDocument";
156 // We want to use cached data because the document is currently visible.
157 var contentDisposition = null;
158 try {
159 contentDisposition =
160 aDocument.defaultView
161 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
162 .getInterface(Components.interfaces.nsIDOMWindowUtils)
163 .getDocumentMetadata("content-disposition");
164 } catch (ex) {
165 // Failure to get a content-disposition is ok
167 internalSave(aDocument.location.href, aDocument, null, contentDisposition,
168 aDocument.contentType, false, null, null,
169 aDocument.referrer ? makeURI(aDocument.referrer) : null,
170 aSkipPrompt);
173 function DownloadListener(win, transfer) {
174 function makeClosure(name) {
175 return function() {
176 transfer[name].apply(transfer, arguments);
180 this.window = win;
182 // Now... we need to forward all calls to our transfer
183 for (var i in transfer) {
184 if (i != "QueryInterface")
185 this[i] = makeClosure(i);
189 DownloadListener.prototype = {
190 QueryInterface: function dl_qi(aIID)
192 if (aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
193 aIID.equals(Components.interfaces.nsIWebProgressListener) ||
194 aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
195 aIID.equals(Components.interfaces.nsISupports)) {
196 return this;
198 throw Components.results.NS_ERROR_NO_INTERFACE;
201 getInterface: function dl_gi(aIID)
203 if (aIID.equals(Components.interfaces.nsIAuthPrompt) ||
204 aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
205 var ww =
206 Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
207 .getService(Components.interfaces.nsIPromptFactory);
208 return ww.getPrompt(this.window, aIID);
211 throw Components.results.NS_ERROR_NO_INTERFACE;
215 const kSaveAsType_Complete = 0; // Save document with attached objects.
216 // const kSaveAsType_URL = 1; // Save document or URL by itself.
217 const kSaveAsType_Text = 2; // Save document, converting to plain text.
220 * internalSave: Used when saving a document or URL. This method:
221 * - Determines a local target filename to use (unless parameter
222 * aChosenData is non-null)
223 * - Determines content-type if possible
224 * - Prompts the user to confirm the destination filename and save mode
225 * (content-type affects this)
226 * - Creates a 'Persist' object (which will perform the saving in the
227 * background) and then starts it.
229 * @param aURL The String representation of the URL of the document being saved
230 * @param aDocument The document to be saved
231 * @param aDefaultFileName The caller-provided suggested filename if we don't
232 * find a better one
233 * @param aContentDisposition The caller-provided content-disposition header
234 * to use.
235 * @param aContentType The caller-provided content-type to use
236 * @param aShouldBypassCache If true, the document will always be refetched
237 * from the server
238 * @param aFilePickerTitleKey Alternate title for the file picker
239 * @param aChosenData If non-null this contains an instance of object AutoChosen
240 * (see below) which holds pre-determined data so that the user does not
241 * need to be prompted for a target filename.
242 * @param aReferrer the referrer URI object (not URL string) to use, or null
243 if no referrer should be sent.
244 * @param aSkipPrompt If true, the file will be saved to the default download folder.
246 function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
247 aContentType, aShouldBypassCache, aFilePickerTitleKey,
248 aChosenData, aReferrer, aSkipPrompt)
250 if (aSkipPrompt == undefined)
251 aSkipPrompt = false;
253 // Note: aDocument == null when this code is used by save-link-as...
254 var saveMode = GetSaveModeForContentType(aContentType);
255 var isDocument = aDocument != null && saveMode != SAVEMODE_FILEONLY;
256 var saveAsType = kSaveAsType_Complete;
258 var file, fileURL;
259 // Find the URI object for aURL and the FileName/Extension to use when saving.
260 // FileName/Extension will be ignored if aChosenData supplied.
261 var fileInfo = new FileInfo(aDefaultFileName);
262 if (aChosenData)
263 file = aChosenData.file;
264 else {
265 var charset = null;
266 if (aDocument)
267 charset = aDocument.characterSet;
268 else if (aReferrer)
269 charset = aReferrer.originCharset;
270 initFileInfo(fileInfo, aURL, charset, aDocument,
271 aContentType, aContentDisposition);
272 var fpParams = {
273 fpTitleKey: aFilePickerTitleKey,
274 isDocument: isDocument,
275 fileInfo: fileInfo,
276 contentType: aContentType,
277 saveMode: saveMode,
278 saveAsType: saveAsType,
279 file: file,
280 fileURL: fileURL
283 if (!getTargetFile(fpParams, aSkipPrompt))
284 // If the method returned false this is because the user cancelled from
285 // the save file picker dialog.
286 return;
288 saveAsType = fpParams.saveAsType;
289 saveMode = fpParams.saveMode;
290 file = fpParams.file;
291 fileURL = fpParams.fileURL;
294 if (!fileURL)
295 fileURL = makeFileURI(file);
297 // XXX We depend on the following holding true in appendFiltersForContentType():
298 // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
299 // If we should save as text, the saveAsType is kSaveAsType_Text.
300 var useSaveDocument = isDocument &&
301 (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
302 ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text)));
303 // If we're saving a document, and are saving either in complete mode or
304 // as converted text, pass the document to the web browser persist component.
305 // If we're just saving the HTML (second option in the list), send only the URI.
306 var source = useSaveDocument ? aDocument : fileInfo.uri;
307 var persistArgs = {
308 source : source,
309 contentType : (!aChosenData && useSaveDocument &&
310 saveAsType == kSaveAsType_Text) ?
311 "text/plain" : null,
312 target : fileURL,
313 postData : isDocument ? getPostData() : null,
314 bypassCache : aShouldBypassCache
317 var persist = makeWebBrowserPersist();
319 // Calculate persist flags.
320 const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
321 const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
322 if (aShouldBypassCache)
323 persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
324 else
325 persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
327 // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
328 persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
330 // Create download and initiate it (below)
331 var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
333 if (useSaveDocument) {
334 // Saving a Document, not a URI:
335 var filesFolder = null;
336 if (persistArgs.contentType != "text/plain") {
337 // Create the local directory into which to save associated files.
338 filesFolder = file.clone();
340 var nameWithoutExtension = getFileBaseName(filesFolder.leafName);
341 var filesFolderLeafName = getStringBundle().formatStringFromName("filesFolder",
342 [nameWithoutExtension],
345 filesFolder.leafName = filesFolderLeafName;
348 var encodingFlags = 0;
349 if (persistArgs.contentType == "text/plain") {
350 encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
351 encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
352 encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;
354 else {
355 encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
358 const kWrapColumn = 80;
359 tr.init((aChosenData ? aChosenData.uri : fileInfo.uri),
360 persistArgs.target, "", null, null, null, persist);
361 persist.progressListener = new DownloadListener(window, tr);
362 persist.saveDocument(persistArgs.source, persistArgs.target, filesFolder,
363 persistArgs.contentType, encodingFlags, kWrapColumn);
364 } else {
365 tr.init((aChosenData ? aChosenData.uri : source),
366 persistArgs.target, "", null, null, null, persist);
367 persist.progressListener = new DownloadListener(window, tr);
368 persist.saveURI((aChosenData ? aChosenData.uri : source),
369 null, aReferrer, persistArgs.postData, null,
370 persistArgs.target);
375 * Structure for holding info about automatically supplied parameters for
376 * internalSave(...). This allows parameters to be supplied so the user does not
377 * need to be prompted for file info.
378 * @param aFileAutoChosen This is an nsILocalFile object that has been
379 * pre-determined as the filename for the target to save to
380 * @param aUriAutoChosen This is the nsIURI object for the target
382 function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
383 this.file = aFileAutoChosen;
384 this.uri = aUriAutoChosen;
388 * Structure for holding info about a URL and the target filename it should be
389 * saved to. This structure is populated by initFileInfo(...).
390 * @param aSuggestedFileName This is used by initFileInfo(...) when it
391 * cannot 'discover' the filename from the url
392 * @param aFileName The target filename
393 * @param aFileBaseName The filename without the file extension
394 * @param aFileExt The extension of the filename
395 * @param aUri An nsIURI object for the url that is being saved
397 function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) {
398 this.suggestedFileName = aSuggestedFileName;
399 this.fileName = aFileName;
400 this.fileBaseName = aFileBaseName;
401 this.fileExt = aFileExt;
402 this.uri = aUri;
406 * Determine what the 'default' filename string is, its file extension and the
407 * filename without the extension. This filename is used when prompting the user
408 * for confirmation in the file picker dialog.
409 * @param aFI A FileInfo structure into which we'll put the results of this method.
410 * @param aURL The String representation of the URL of the document being saved
411 * @param aURLCharset The charset of aURL.
412 * @param aDocument The document to be saved
413 * @param aContentType The content type we're saving, if it could be
414 * determined by the caller.
415 * @param aContentDisposition The content-disposition header for the object
416 * we're saving, if it could be determined by the caller.
418 function initFileInfo(aFI, aURL, aURLCharset, aDocument,
419 aContentType, aContentDisposition)
421 try {
422 // Get an nsIURI object from aURL if possible:
423 try {
424 aFI.uri = makeURI(aURL, aURLCharset);
425 // Assuming nsiUri is valid, calling QueryInterface(...) on it will
426 // populate extra object fields (eg filename and file extension).
427 var url = aFI.uri.QueryInterface(Components.interfaces.nsIURL);
428 aFI.fileExt = url.fileExtension;
429 } catch (e) {
432 // Get the default filename:
433 aFI.fileName = getDefaultFileName((aFI.suggestedFileName || aFI.fileName),
434 aFI.uri, aDocument, aContentDisposition);
435 // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
436 // if saveURL(...) was the original caller (hence both aContentType and
437 // aDocument are blank). If they were saving a link to a website then make
438 // the extension .htm .
439 if (!aFI.fileExt && !aDocument && !aContentType && (/^http(s?):\/\//i.test(aURL))) {
440 aFI.fileExt = "htm";
441 aFI.fileBaseName = aFI.fileName;
442 } else {
443 aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
444 aFI.fileBaseName = getFileBaseName(aFI.fileName);
446 } catch (e) {
450 function getTargetFile(aFpP, aSkipPrompt)
452 const prefSvcContractID = "@mozilla.org/preferences-service;1";
453 const prefSvcIID = Components.interfaces.nsIPrefService;
454 var prefs = Components.classes[prefSvcContractID]
455 .getService(prefSvcIID).getBranch("browser.download.");
457 const nsILocalFile = Components.interfaces.nsILocalFile;
459 // For information on download folder preferences, see
460 // mozilla/browser/components/preferences/main.js
462 var useDownloadDir = prefs.getBoolPref("useDownloadDir");
463 var dir = null;
465 // Default to lastDir if useDownloadDir is false, and lastDir
466 // is configured and valid. Otherwise, use the user's default
467 // downloads directory configured through download prefs.
468 var dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
469 .getService(Components.interfaces.nsIDownloadManager);
470 try {
471 var lastDir = prefs.getComplexValue("lastDir", nsILocalFile);
472 if ((!aSkipPrompt || !useDownloadDir) && lastDir.exists())
473 dir = lastDir;
474 else
475 dir = dnldMgr.userDownloadsDirectory;
476 } catch(ex) {
477 dir = dnldMgr.userDownloadsDirectory;
480 if (!aSkipPrompt || !useDownloadDir || !dir || (dir && !dir.exists())) {
481 if (!dir || (dir && !dir.exists())) {
482 // Default to desktop.
483 var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
484 .getService(Components.interfaces.nsIProperties);
485 dir = fileLocator.get("Desk", nsILocalFile);
488 var fp = makeFilePicker();
489 var titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
490 var bundle = getStringBundle();
491 fp.init(window, bundle.GetStringFromName(titleKey),
492 Components.interfaces.nsIFilePicker.modeSave);
494 fp.defaultExtension = aFpP.fileInfo.fileExt;
495 fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName,
496 aFpP.fileInfo.fileExt);
497 appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt,
498 aFpP.saveMode);
500 if (dir)
501 fp.displayDirectory = dir;
503 if (aFpP.isDocument) {
504 try {
505 fp.filterIndex = prefs.getIntPref("save_converter_index");
507 catch (e) {
511 if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file)
512 return false;
514 var directory = fp.file.parent.QueryInterface(nsILocalFile);
515 prefs.setComplexValue("lastDir", nsILocalFile, directory);
517 fp.file.leafName = validateFileName(fp.file.leafName);
518 aFpP.saveAsType = fp.filterIndex;
519 aFpP.file = fp.file;
520 aFpP.fileURL = fp.fileURL;
522 if (aFpP.isDocument)
523 prefs.setIntPref("save_converter_index", aFpP.saveAsType);
525 else {
526 dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
527 aFpP.fileInfo.fileExt));
528 var file = dir;
530 // Since we're automatically downloading, we don't get the file picker's
531 // logic to check for existing files, so we need to do that here.
533 // Note - this code is identical to that in
534 // mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
535 // If you are updating this code, update that code too! We can't share code
536 // here since that code is called in a js component.
537 var collisionCount = 0;
538 while (file.exists()) {
539 collisionCount++;
540 if (collisionCount == 1) {
541 // Append "(2)" before the last dot in (or at the end of) the filename
542 // special case .ext.gz etc files so we don't wind up with .tar(2).gz
543 if (file.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
544 file.leafName = file.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
545 else
546 file.leafName = file.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
548 else {
549 // replace the last (n) in the filename with (n+1)
550 file.leafName = file.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
553 aFpP.file = file;
556 return true;
559 // We have no DOM, and can only save the URL as is.
560 const SAVEMODE_FILEONLY = 0x00;
561 // We have a DOM and can save as complete.
562 const SAVEMODE_COMPLETE_DOM = 0x01;
563 // We have a DOM which we can serialize as text.
564 const SAVEMODE_COMPLETE_TEXT = 0x02;
566 // If we are able to save a complete DOM, the 'save as complete' filter
567 // must be the first filter appended. The 'save page only' counterpart
568 // must be the second filter appended. And the 'save as complete text'
569 // filter must be the third filter appended.
570 function appendFiltersForContentType(aFilePicker, aContentType, aFileExtension, aSaveMode)
572 var bundle = getStringBundle();
573 // The bundle name for saving only a specific content type.
574 var bundleName;
575 // The corresponding filter string for a specific content type.
576 var filterString;
578 // XXX all the cases that are handled explicitly here MUST be handled
579 // in GetSaveModeForContentType to return a non-fileonly filter.
580 switch (aContentType) {
581 case "text/html":
582 bundleName = "WebPageHTMLOnlyFilter";
583 filterString = "*.htm; *.html";
584 break;
586 case "application/xhtml+xml":
587 bundleName = "WebPageXHTMLOnlyFilter";
588 filterString = "*.xht; *.xhtml";
589 break;
591 case "image/svg+xml":
592 bundleName = "WebPageSVGOnlyFilter";
593 filterString = "*.svg; *.svgz";
594 break;
596 case "text/xml":
597 case "application/xml":
598 bundleName = "WebPageXMLOnlyFilter";
599 filterString = "*.xml";
600 break;
602 default:
603 if (aSaveMode != SAVEMODE_FILEONLY)
604 throw "Invalid save mode for type '" + aContentType + "'";
606 var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension);
607 if (mimeInfo) {
609 var extEnumerator = mimeInfo.getFileExtensions();
611 var extString = "";
612 while (extEnumerator.hasMore()) {
613 var extension = extEnumerator.getNext();
614 if (extString)
615 extString += "; "; // If adding more than one extension,
616 // separate by semi-colon
617 extString += "*." + extension;
620 if (extString)
621 aFilePicker.appendFilter(mimeInfo.description, extString);
624 break;
627 if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
628 aFilePicker.appendFilter(bundle.GetStringFromName("WebPageCompleteFilter"), filterString);
629 // We should always offer a choice to save document only if
630 // we allow saving as complete.
631 aFilePicker.appendFilter(bundle.GetStringFromName(bundleName), filterString);
634 if (aSaveMode & SAVEMODE_COMPLETE_TEXT)
635 aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);
637 // Always append the all files (*) filter
638 aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
641 function getPostData()
643 try {
644 var sessionHistory = getWebNavigation().sessionHistory;
645 return sessionHistory.getEntryAtIndex(sessionHistory.index, false)
646 .QueryInterface(Components.interfaces.nsISHEntry)
647 .postData;
649 catch (e) {
651 return null;
654 function getStringBundle()
656 return Components.classes["@mozilla.org/intl/stringbundle;1"]
657 .getService(Components.interfaces.nsIStringBundleService)
658 .createBundle("chrome://global/locale/contentAreaCommands.properties");
661 function makeWebBrowserPersist()
663 const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
664 const persistIID = Components.interfaces.nsIWebBrowserPersist;
665 return Components.classes[persistContractID].createInstance(persistIID);
669 * Constructs a new URI, using nsIIOService.
670 * @param aURL The URI spec.
671 * @param aOriginCharset The charset of the URI.
672 * @param aBaseURI Base URI to resolve aURL, or null.
673 * @return an nsIURI object based on aURL.
675 function makeURI(aURL, aOriginCharset, aBaseURI)
677 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
678 .getService(Components.interfaces.nsIIOService);
679 return ioService.newURI(aURL, aOriginCharset, aBaseURI);
682 function makeFileURI(aFile)
684 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
685 .getService(Components.interfaces.nsIIOService);
686 return ioService.newFileURI(aFile);
689 function makeFilePicker()
691 const fpContractID = "@mozilla.org/filepicker;1";
692 const fpIID = Components.interfaces.nsIFilePicker;
693 return Components.classes[fpContractID].createInstance(fpIID);
696 function getMIMEService()
698 const mimeSvcContractID = "@mozilla.org/mime;1";
699 const mimeSvcIID = Components.interfaces.nsIMIMEService;
700 const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
701 return mimeSvc;
704 // Given aFileName, find the fileName without the extension on the end.
705 function getFileBaseName(aFileName)
707 // Remove the file extension from aFileName:
708 return aFileName.replace(/\.[^.]*$/, "");
711 function getMIMETypeForURI(aURI)
713 try {
714 return getMIMEService().getTypeFromURI(aURI);
716 catch (e) {
718 return null;
721 function getMIMEInfoForType(aMIMEType, aExtension)
723 if (aMIMEType || aExtension) {
724 try {
725 return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
727 catch (e) {
730 return null;
733 function getDefaultFileName(aDefaultFileName, aURI, aDocument,
734 aContentDisposition)
736 // 1) look for a filename in the content-disposition header, if any
737 if (aContentDisposition) {
738 const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
739 const mhpIID = Components.interfaces.nsIMIMEHeaderParam;
740 const mhp = Components.classes[mhpContractID].getService(mhpIID);
741 var dummy = { value: null }; // Need an out param...
742 var charset = getCharsetforSave(aDocument);
744 var fileName = null;
745 try {
746 fileName = mhp.getParameter(aContentDisposition, "filename", charset,
747 true, dummy);
749 catch (e) {
750 try {
751 fileName = mhp.getParameter(aContentDisposition, "name", charset, true,
752 dummy);
754 catch (e) {
757 if (fileName)
758 return fileName;
761 try {
762 var url = aURI.QueryInterface(Components.interfaces.nsIURL);
763 if (url.fileName != "") {
764 // 2) Use the actual file name, if present
765 var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
766 .getService(Components.interfaces.nsITextToSubURI);
767 return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName));
769 } catch (e) {
770 // This is something like a data: and so forth URI... no filename here.
773 if (aDocument) {
774 var docTitle = validateFileName(aDocument.title).replace(/^\s+|\s+$/g, "");
775 if (docTitle) {
776 // 3) Use the document title
777 return docTitle;
781 if (aDefaultFileName)
782 // 4) Use the caller-provided name, if any
783 return validateFileName(aDefaultFileName);
785 // 5) If this is a directory, use the last directory name
786 var path = aURI.path.match(/\/([^\/]+)\/$/);
787 if (path && path.length > 1)
788 return validateFileName(path[1]);
790 try {
791 if (aURI.host)
792 // 6) Use the host.
793 return aURI.host;
794 } catch (e) {
795 // Some files have no information at all, like Javascript generated pages
797 try {
798 // 7) Use the default file name
799 return getStringBundle().GetStringFromName("DefaultSaveFileName");
800 } catch (e) {
801 //in case localized string cannot be found
803 // 8) If all else fails, use "index"
804 return "index";
807 function validateFileName(aFileName)
809 var re = /[\/]+/g;
810 if (navigator.appVersion.indexOf("Windows") != -1) {
811 re = /[\\\/\|]+/g;
812 aFileName = aFileName.replace(/[\"]+/g, "'");
813 aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
814 aFileName = aFileName.replace(/[\<]+/g, "(");
815 aFileName = aFileName.replace(/[\>]+/g, ")");
817 else if (navigator.appVersion.indexOf("Macintosh") != -1)
818 re = /[\:\/]+/g;
820 return aFileName.replace(re, "_");
823 function getNormalizedLeafName(aFile, aDefaultExtension)
825 if (!aDefaultExtension)
826 return aFile;
828 #ifdef XP_WIN
829 // Remove trailing dots and spaces on windows
830 aFile = aFile.replace(/[\s.]+$/, "");
831 #endif
833 // Remove leading dots
834 aFile = aFile.replace(/^\.+/, "");
836 // Fix up the file name we're saving to to include the default extension
837 var i = aFile.lastIndexOf(".");
838 if (aFile.substr(i + 1) != aDefaultExtension)
839 return aFile + "." + aDefaultExtension;
841 return aFile;
844 function getDefaultExtension(aFilename, aURI, aContentType)
846 if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
847 return ""; // temporary fix for bug 120327
849 // First try the extension from the filename
850 const stdURLContractID = "@mozilla.org/network/standard-url;1";
851 const stdURLIID = Components.interfaces.nsIURL;
852 var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
853 url.filePath = aFilename;
855 var ext = url.fileExtension;
857 // This mirrors some code in nsExternalHelperAppService::DoContent
858 // Use the filename first and then the URI if that fails
860 var mimeInfo = getMIMEInfoForType(aContentType, ext);
862 if (ext && mimeInfo && mimeInfo.extensionExists(ext))
863 return ext;
865 // Well, that failed. Now try the extension from the URI
866 var urlext;
867 try {
868 url = aURI.QueryInterface(Components.interfaces.nsIURL);
869 urlext = url.fileExtension;
870 } catch (e) {
873 if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
874 return urlext;
876 else {
877 try {
878 if (mimeInfo)
879 return mimeInfo.primaryExtension;
881 catch (e) {
883 // Fall back on the extensions in the filename and URI for lack
884 // of anything better.
885 return ext || urlext;
889 function GetSaveModeForContentType(aContentType)
891 var saveMode = SAVEMODE_FILEONLY;
892 switch (aContentType) {
893 case "text/html":
894 case "application/xhtml+xml":
895 case "image/svg+xml":
896 saveMode |= SAVEMODE_COMPLETE_TEXT;
897 // Fall through
898 case "text/xml":
899 case "application/xml":
900 saveMode |= SAVEMODE_COMPLETE_DOM;
901 break;
904 return saveMode;
907 function getCharsetforSave(aDocument)
909 if (aDocument)
910 return aDocument.characterSet;
912 if (document.commandDispatcher.focusedWindow)
913 return document.commandDispatcher.focusedWindow.document.characterSet;
915 return window.content.document.characterSet;
919 * Open a URL from chrome, determining if we can handle it internally or need to
920 * launch an external application to handle it.
921 * @param aURL The URL to be opened
923 function openURL(aURL)
925 var ios = Components.classes["@mozilla.org/network/io-service;1"]
926 .getService(Components.interfaces.nsIIOService);
927 var uri = ios.newURI(aURL, null, null);
929 var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
930 .getService(Components.interfaces.nsIExternalProtocolService);
932 if (!protocolSvc.isExposedProtocol(uri.scheme)) {
933 // If we're not a browser, use the external protocol service to load the URI.
934 protocolSvc.loadUrl(uri);
936 else {
937 var loadgroup = Components.classes["@mozilla.org/network/load-group;1"]
938 .createInstance(Components.interfaces.nsILoadGroup);
939 var appstartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
940 .getService(Components.interfaces.nsIAppStartup);
942 var loadListener = {
943 onStartRequest: function ll_start(aRequest, aContext) {
944 appstartup.enterLastWindowClosingSurvivalArea();
946 onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) {
947 appstartup.exitLastWindowClosingSurvivalArea();
949 QueryInterface: function ll_QI(iid) {
950 if (iid.equals(Components.interfaces.nsISupports) ||
951 iid.equals(Components.interfaces.nsIRequestObserver) ||
952 iid.equals(Components.interfaces.nsISupportsWeakReference))
953 return this;
954 throw Components.results.NS_ERROR_NO_INTERFACE;
957 loadgroup.groupObserver = loadListener;
959 var uriListener = {
960 onStartURIOpen: function(uri) { return false; },
961 doContent: function(ctype, preferred, request, handler) { return false; },
962 isPreferred: function(ctype, desired) { return false; },
963 canHandleContent: function(ctype, preferred, desired) { return false; },
964 loadCookie: null,
965 parentContentListener: null,
966 getInterface: function(iid) {
967 if (iid.equals(Components.interfaces.nsIURIContentListener))
968 return this;
969 if (iid.equals(Components.interfaces.nsILoadGroup))
970 return loadgroup;
971 throw Components.results.NS_ERROR_NO_INTERFACE;
975 var channel = ios.newChannelFromURI(uri);
976 var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
977 .getService(Components.interfaces.nsIURILoader);
978 uriLoader.openURI(channel, true, uriListener);