home *** CD-ROM | disk | FTP | other *** search
/ ftp.swcp.com / ftp.swcp.com.zip / ftp.swcp.com / mac / mozilla-macos9-1.3.1.sea.bin / Mozilla1.3.1 / Chrome / comm.jar / content / communicator / contentAreaUtils.js < prev    next >
Text File  |  2003-06-08  |  30KB  |  891 lines

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Netscape Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/NPL/
  9.  *
  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.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  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.
  21.  *
  22.  * Contributor(s):
  23.  *   Ben Goodger <ben@netscape.com> (Save File)
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or 
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the NPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the NPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. /**
  40.  * Determine whether or not a given focused DOMWindow is in the content
  41.  * area.
  42.  **/
  43. function isContentFrame(aFocusedWindow)
  44. {
  45.   if (!aFocusedWindow)
  46.     return false;
  47.  
  48.   var focusedTop = Components.lookupMethod(aFocusedWindow, 'top')
  49.                              .call(aFocusedWindow);
  50.  
  51.   return (focusedTop == window.content);
  52. }
  53.  
  54. function urlSecurityCheck(url, doc) 
  55. {
  56.   // URL Loading Security Check
  57.   var focusedWindow = doc.commandDispatcher.focusedWindow;
  58.   var sourceURL = getContentFrameURI(focusedWindow);
  59.   const nsIScriptSecurityManager = Components.interfaces.nsIScriptSecurityManager;
  60.   var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
  61.                          .getService(nsIScriptSecurityManager);
  62.   try {
  63.     secMan.checkLoadURIStr(sourceURL, url, nsIScriptSecurityManager.STANDARD);
  64.   } catch (e) {
  65.     throw "Load of " + url + " denied.";
  66.   }
  67. }
  68.  
  69. function getContentFrameURI(aFocusedWindow)
  70. {
  71.   var contentFrame = isContentFrame(aFocusedWindow) ? aFocusedWindow : window.content;
  72.   return Components.lookupMethod(contentFrame, 'location').call(contentFrame).href;
  73. }
  74.  
  75. function getReferrer(doc)
  76. {
  77.   var focusedWindow = doc.commandDispatcher.focusedWindow;
  78.   var sourceURL = getContentFrameURI(focusedWindow);
  79.  
  80.   try {
  81.     var uri = Components.classes["@mozilla.org/network/standard-url;1"].createInstance(Components.interfaces.nsIURI);
  82.     uri.spec = sourceURL;
  83.     return uri;
  84.   } catch (e) {
  85.     return null;
  86.   }
  87. }
  88.  
  89. function openNewWindowWith(url, sendReferrer) 
  90. {
  91.   urlSecurityCheck(url, document);
  92.  
  93.   // if and only if the current window is a browser window and it has a document with a character
  94.   // set, then extract the current charset menu setting from the current document and use it to
  95.   // initialize the new browser window...
  96.   var charsetArg = null;
  97.   var wintype = document.firstChild.getAttribute('windowtype');
  98.   if (wintype == "navigator:browser")
  99.     charsetArg = "charset=" + window._content.document.characterSet;
  100.  
  101.   var referrer = sendReferrer ? getReferrer(document) : null;
  102.   window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", url, charsetArg, referrer);
  103. }
  104.  
  105. function openNewTabWith(url, sendReferrer, reverseBackgroundPref) 
  106. {
  107.   var browser;
  108.   try {
  109.     // if we're running in a browser window, this should work
  110.     //
  111.     browser = getBrowser();
  112.  
  113.   } catch (ex if ex instanceof ReferenceError) {
  114.  
  115.     // must be running somewhere else (eg mailnews message pane); need to
  116.     // find a browser window first
  117.     //
  118.     var windowMediator =
  119.       Components.classes["@mozilla.org/appshell/window-mediator;1"]
  120.       .getService(Components.interfaces.nsIWindowMediator);
  121.  
  122.     var browserWin = windowMediator.getMostRecentWindow("navigator:browser");
  123.  
  124.     // if there's no existing browser window, open this url in one, and
  125.     // return
  126.     //
  127.     if (!browserWin) {
  128.       window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", 
  129.                         url, null, referrer);
  130.       return;
  131.     }
  132.  
  133.     // otherwise, get the existing browser object
  134.     //
  135.     browser = browserWin.getBrowser();
  136.   }
  137.  
  138.   // Get the XUL document that the browser is actually contained in.
  139.   // This is needed if we are trying to load a URL from a non-navigator
  140.   // window such as the JS Console.
  141.   var browserDocument = browser.ownerDocument;
  142.  
  143.   urlSecurityCheck(url, browserDocument);
  144.  
  145.   var referrer = sendReferrer ? getReferrer(browserDocument) : null;
  146.  
  147.   var tab = browser.addTab(url, referrer); // open link in new tab
  148.   if (pref) {
  149.     var loadInBackground = pref.getBoolPref("browser.tabs.loadInBackground");
  150.     if (reverseBackgroundPref)
  151.       loadInBackground = !loadInBackground;
  152.  
  153.     if (!loadInBackground)
  154.       browser.selectedTab = tab;
  155.   }
  156. }
  157.  
  158. function findParentNode(node, parentNode)
  159. {
  160.   if (node && node.nodeType == Node.TEXT_NODE) {
  161.     node = node.parentNode;
  162.   }
  163.   while (node) {
  164.     var nodeName = node.localName;
  165.     if (!nodeName)
  166.       return null;
  167.     nodeName = nodeName.toLowerCase();
  168.     if (nodeName == "body" || nodeName == "html" ||
  169.         nodeName == "#document") {
  170.       return null;
  171.     }
  172.     if (nodeName == parentNode)
  173.       return node;
  174.     node = node.parentNode;
  175.   }
  176.   return null;
  177. }
  178.  
  179. // Clientelle: (Make sure you don't break any of these)
  180. //  - File    ->  Save Page/Frame As...
  181. //  - Context ->  Save Page/Frame As...
  182. //  - Context ->  Save Link As...
  183. //  - Context ->  Save Image As...
  184. //  - Shift-Click Save Link As
  185. //
  186. // Try saving each of these types:
  187. // - A complete webpage using File->Save Page As, and Context->Save Page As
  188. // - A webpage as HTML only using the above methods
  189. // - A webpage as Text only using the above methods
  190. // - An image with an extension (e.g. .jpg) in its file name, using
  191. //   Context->Save Image As...
  192. // - An image without an extension (e.g. a banner ad on cnn.com) using
  193. //   the above method. 
  194. // - A linked document using Save Link As...
  195. // - A linked document using shift-click Save Link As...
  196. //
  197. function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache)
  198. {
  199.   saveInternal(aURL, null, aFileName, aFilePickerTitleKey, aShouldBypassCache);
  200. }
  201.  
  202. function saveFrameDocument()
  203. {
  204.   var focusedWindow = document.commandDispatcher.focusedWindow;
  205.   if (isContentFrame(focusedWindow))
  206.     saveDocument(focusedWindow.document);
  207. }
  208.  
  209. function saveDocument(aDocument)
  210. {
  211.   // In both cases here, we want to use cached data because the 
  212.   // document is currently visible. 
  213.   if (aDocument) 
  214.     saveInternal(aDocument.location.href, aDocument, false);
  215.   else
  216.     saveInternal(_content.location.href, null, false);
  217. }
  218.  
  219. function saveInternal(aURL, aDocument, 
  220.                       aFileName, aFilePickerTitleKey,
  221.                       aShouldBypassCache)
  222. {
  223.   var data = {
  224.     url: aURL,
  225.     fileName: aFileName,
  226.     filePickerTitle: aFilePickerTitleKey,
  227.     document: aDocument,
  228.     bypassCache: aShouldBypassCache,
  229.     window: window
  230.   };
  231.   var sniffer = new nsHeaderSniffer(aURL, foundHeaderInfo, data);
  232. }
  233.  
  234. function foundHeaderInfo(aSniffer, aData)
  235. {
  236.   var contentType = aSniffer.contentType;
  237.   var contentEncodingType = aSniffer.contentEncodingType;
  238.  
  239.   var shouldDecode = false;
  240.   // Are we allowed to decode?
  241.   try {
  242.     const helperAppService =
  243.       Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
  244.         getService(Components.interfaces.nsIExternalHelperAppService);
  245.     var url = aSniffer.uri.QueryInterface(Components.interfaces.nsIURL);
  246.     var urlExt = url.fileExtension;
  247.     if (helperAppService.applyDecodingForType(contentType) &&
  248.         (!urlExt || helperAppService.applyDecodingForExtension(urlExt))) {
  249.       shouldDecode = true;
  250.     }
  251.   }
  252.   catch (e) {
  253.   }
  254.  
  255.   var fp = makeFilePicker();
  256.   var titleKey = aData.filePickerTitle || "SaveLinkTitle";
  257.   var bundle = getStringBundle();
  258.   fp.init(window, bundle.GetStringFromName(titleKey), 
  259.           Components.interfaces.nsIFilePicker.modeSave);
  260.  
  261.   var saveMode = GetSaveModeForContentType(contentType);
  262.   var isDocument = aData.document != null && saveMode;
  263.   if (!isDocument && !shouldDecode && contentEncodingType) {
  264.     // The data is encoded, we are not going to decode it, and this is not a
  265.     // document save so we won't be doing a "save as, complete" (which would
  266.     // break if we reset the type here).  So just set our content type to
  267.     // correspond to the outermost encoding so we get extensions and the like
  268.     // right.
  269.     contentType = contentEncodingType;
  270.   }
  271.  
  272.   appendFiltersForContentType(fp, contentType,
  273.                               isDocument ? saveMode : SAVEMODE_FILEONLY);
  274.  
  275.   const prefSvcContractID = "@mozilla.org/preferences-service;1";
  276.   const prefSvcIID = Components.interfaces.nsIPrefService;                              
  277.   var prefs = Components.classes[prefSvcContractID].getService(prefSvcIID).getBranch("browser.download.");
  278.   
  279.   const nsILocalFile = Components.interfaces.nsILocalFile;
  280.   try {
  281.     fp.displayDirectory = prefs.getComplexValue("dir", nsILocalFile);
  282.   }
  283.   catch (e) {
  284.   }
  285.  
  286.   if (isDocument) {
  287.     try {
  288.       fp.filterIndex = prefs.getIntPref("save_converter_index");
  289.     }
  290.     catch (e) {
  291.     }
  292.   }
  293.   
  294.   // Determine what the 'default' string to display in the File Picker dialog 
  295.   // should be. 
  296.   var defaultFileName = getDefaultFileName(aData.fileName, 
  297.                                            aSniffer.suggestedFileName, 
  298.                                            aSniffer.uri,
  299.                                            aData.document);
  300.   var defaultExtension = getDefaultExtension(defaultFileName, aSniffer.uri, contentType);
  301.   fp.defaultExtension = defaultExtension;
  302.   fp.defaultString = getNormalizedLeafName(defaultFileName, defaultExtension);
  303.   
  304.   if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file)
  305.     return;
  306.  
  307.   if (isDocument) 
  308.     prefs.setIntPref("save_converter_index", fp.filterIndex);
  309.   var directory = fp.file.parent.QueryInterface(nsILocalFile);
  310.   prefs.setComplexValue("dir", nsILocalFile, directory);
  311.  
  312.   fp.file.leafName = validateFileName(fp.file.leafName);
  313.  
  314.   // XXX We depend on the following holding true in appendFiltersForContentType():
  315.   // If we should save as a complete page, the filterIndex is 0.
  316.   // If we should save as text, the filterIndex is 2.
  317.   var useSaveDocument = isDocument &&
  318.                         ((saveMode & SAVEMODE_COMPLETE_DOM && fp.filterIndex == 0) ||
  319.                          (saveMode & SAVEMODE_COMPLETE_TEXT && fp.filterIndex == 2));
  320.  
  321.   // If we're saving a document, and are saving either in complete mode or 
  322.   // as converted text, pass the document to the web browser persist component.
  323.   // If we're just saving the HTML (second option in the list), send only the URI.
  324.   var source = useSaveDocument ? aData.document : aSniffer.uri;
  325.   var persistArgs = {
  326.     source      : source,
  327.     contentType : (useSaveDocument && fp.filterIndex == 2) ? "text/plain" : contentType,
  328.     target      : fp.file,
  329.     postData    : isDocument ? getPostData() : null,
  330.     bypassCache : aData.bypassCache
  331.   };
  332.   
  333.   var persist = makeWebBrowserPersist();
  334.  
  335.   // Calculate persist flags.
  336.   const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
  337.   const flags = nsIWBP.PERSIST_FLAGS_NO_CONVERSION | nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
  338.   if (aData.bypassCache)
  339.     persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
  340.   else 
  341.     persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
  342.  
  343.   if (shouldDecode)
  344.     persist.persistFlags &= ~nsIWBP.PERSIST_FLAGS_NO_CONVERSION;
  345.     
  346.   // Create download and initiate it (below)
  347.   var dl = Components.classes["@mozilla.org/download;1"].createInstance(Components.interfaces.nsIDownload);
  348.  
  349.   if (useSaveDocument) {
  350.     // Saving a Document, not a URI:
  351.     var filesFolder = null;
  352.     if (persistArgs.contentType != "text/plain") {
  353.       // Create the local directory into which to save associated files. 
  354.       const lfContractID = "@mozilla.org/file/local;1";
  355.       const lfIID = Components.interfaces.nsILocalFile;
  356.       filesFolder = Components .classes[lfContractID].createInstance(lfIID);
  357.       filesFolder.initWithPath(persistArgs.target.path);
  358.       
  359.       var nameWithoutExtension = filesFolder.leafName;
  360.       nameWithoutExtension = nameWithoutExtension.substring(0, nameWithoutExtension.lastIndexOf("."));
  361.       var filesFolderLeafName = getStringBundle().formatStringFromName("filesFolder",
  362.                                                                        [nameWithoutExtension],
  363.                                                                        1);
  364.  
  365.       filesFolder.leafName = filesFolderLeafName;
  366.     }
  367.       
  368.     var encodingFlags = 0;
  369.     if (persistArgs.contentType == "text/plain") {
  370.       encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
  371.       encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
  372.       encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;        
  373.     }
  374.     
  375.     const kWrapColumn = 80;
  376.     dl.init(aSniffer.uri, persistArgs.target, null, null, null, persist);
  377.     persist.saveDocument(persistArgs.source, persistArgs.target, filesFolder, 
  378.                          persistArgs.contentType, encodingFlags, kWrapColumn);
  379.   } else {
  380.     dl.init(source, persistArgs.target, null, null, null, persist);
  381.     persist.saveURI(source, null, null, persistArgs.postData, null, persistArgs.target);
  382.   }
  383. }
  384.  
  385. function nsHeaderSniffer(aURL, aCallback, aData)
  386. {
  387.   this.mCallback = aCallback;
  388.   this.mData = aData;
  389.   
  390.   this.uri = makeURL(aURL);
  391.   
  392.   this.linkChecker = Components.classes["@mozilla.org/network/urichecker;1"]
  393.     .createInstance().QueryInterface(Components.interfaces.nsIURIChecker);
  394.  
  395.   var flags;
  396.   if (aData.bypassCache) {
  397.     flags = Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
  398.   } else {
  399.     flags = Components.interfaces.nsIRequest.LOAD_FROM_CACHE;
  400.   }
  401.  
  402.   this.linkChecker.asyncCheckURI(aURL, this, null, flags);
  403. }
  404.  
  405. nsHeaderSniffer.prototype = {
  406.  
  407.   // ---------- nsISupports methods ----------
  408.   QueryInterface: function (iid) {
  409.     if (!iid.equals(Components.interfaces.nsIRequestObserver) &&
  410.         !iid.equals(Components.interfaces.nsISupports) &&
  411.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  412.         !iid.equals(Components.interfaces.nsIAuthPrompt)) {
  413.       throw Components.results.NS_ERROR_NO_INTERFACE;
  414.     }
  415.     return this;
  416.   },
  417.  
  418.   // ---------- nsIInterfaceRequestor methods ----------
  419.   getInterface : function(iid) {
  420.     return this.QueryInterface(iid);
  421.   },
  422.  
  423.   // ---------- nsIAuthPrompt methods ----------
  424.   prompt : function(dlgTitle, text, pwrealm, savePW, defaultText, result)
  425.   {
  426.     dump("authprompt prompt! pwrealm="+pwrealm+"\n");
  427.     var promptServ = this.promptService;
  428.     if (!promptServ)
  429.       return false;
  430.     var saveCheck = {value:savePW};
  431.     return promptServ.prompt(window, dlgTitle, text, defaultText, pwrealm, saveCheck);
  432.   },
  433.   promptUsernameAndPassword : function(dlgTitle, text, pwrealm, savePW, user, pw)
  434.   {
  435.     dump("authprompt promptUsernameAndPassword!  "+dlgTitle+" "+text+", pwrealm="+pwrealm+"\n");
  436.     var promptServ = this.promptService;
  437.     if (!promptServ)
  438.       return false;
  439.     var saveCheck = {value:savePW};
  440.     return promptServ.promptUsernameAndPassword(window, dlgTitle, text, user, pw, pwrealm, saveCheck);
  441.   },
  442.   promptPassword : function(dlgTitle, text, pwrealm, savePW, pw)
  443.   {
  444.     dump("auth promptPassword!  "+dlgTitle+" "+text+", pwrealm="+pwrealm+"\n");
  445.     var promptServ = this.promptService;
  446.     if (!promptServ)
  447.       return false;
  448.  
  449.     var saveCheck = {value:savePW};
  450.     return promptServ.promptPassword(window, dlgTitle, text, pw, pwrealm, saveCheck);
  451.   },
  452.  
  453.   // ---------- nsIRequestObserver methods ----------
  454.   onStartRequest: function (aRequest, aContext) { },
  455.   
  456.   onStopRequest: function (aRequest, aContext, aStatus) {
  457.     try {
  458.       if (aStatus == 0) { // NS_BINDING_SUCCEEDED, so there's something there
  459.         var linkChecker = aRequest.QueryInterface(Components.interfaces.nsIURIChecker);
  460.         var channel = linkChecker.baseRequest.QueryInterface(Components.interfaces.nsIChannel);
  461.         this.contentType = channel.contentType;
  462.         try {
  463.           var httpChannel = channel.QueryInterface(Components.interfaces.nsIHttpChannel);
  464.           var encodedChannel = channel.QueryInterface(Components.interfaces.nsIEncodedChannel);
  465.           this.contentEncodingType = null;
  466.           // There may be content-encodings on the channel.  Multiple content
  467.           // encodings are allowed, eg "Content-Encoding: gzip, uuencode".  This
  468.           // header would mean that the content was first gzipped and then
  469.           // uuencoded.  The encoding enumerator returns MIME types
  470.           // corresponding to each encoding starting from the end, so the first
  471.           // thing it returns corresponds to the outermost encoding.
  472.           var encodingEnumerator = encodedChannel.contentEncodings;
  473.           if (encodingEnumerator && encodingEnumerator.hasMoreElements()) {
  474.             try {
  475.               this.contentEncodingType =
  476.                 encodingEnumerator.getNext().
  477.                   QueryInterface(Components.interfaces.nsISupportsCString).data;
  478.             } catch (e) {
  479.             }
  480.           }
  481.           this.mContentDisposition = httpChannel.getResponseHeader("content-disposition");
  482.         }
  483.         catch (e) {
  484.         }
  485.         if (!this.contentType || this.contentType == "application/x-unknown-content-type") {
  486.           // We didn't get a type from the server.  Fall back on other type detection mechanisms
  487.           throw "Unknown Type";
  488.         }
  489.       }
  490.       else {
  491.         dump("Error saving link aStatus = 0x" + aStatus.toString(16) + "\n");
  492.         var bundle = getStringBundle();
  493.         var errorTitle = bundle.GetStringFromName("saveLinkErrorTitle");
  494.         var errorMsg = bundle.GetStringFromName("saveLinkErrorMsg");
  495.         const promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  496.         promptService.alert(this.mData.window, errorTitle, errorMsg);
  497.         return;
  498.       }
  499.     }
  500.     catch (e) {
  501.       if (this.mData.document) {
  502.         this.contentType = this.mData.document.contentType;
  503.       } else {
  504.         try {
  505.           var url = this.uri.QueryInterface(Components.interfaces.nsIURL);
  506.           var ext = url.fileExtension;
  507.           if (ext) {
  508.             var mimeInfo = getMIMEInfoForExtension(ext);
  509.             if (mimeInfo)
  510.               this.contentType = mimeInfo.MIMEType;
  511.           }
  512.         }
  513.         catch (e) {
  514.           // Not much we can do here.  Give up.
  515.         }
  516.       }
  517.     }
  518.     this.mCallback(this, this.mData);
  519.   },
  520.  
  521.   // ------------------------------------------------
  522.  
  523.   get promptService()
  524.   {
  525.     var promptSvc;
  526.     try {
  527.       promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  528.       promptSvc = promptSvc.QueryInterface(Components.interfaces.nsIPromptService);
  529.     }
  530.     catch (e) {}
  531.     return promptSvc;
  532.   },
  533.  
  534.   get suggestedFileName()
  535.   {
  536.     var filename = "";
  537.     var name = this.mContentDisposition;
  538.     if (name) {
  539.       const filenamePrefix = "filename=";
  540.       var ix = name.indexOf(filenamePrefix);
  541.       if (ix >= 0) {
  542.         // Adjust ix to point to start of actual name
  543.         ix += filenamePrefix.length;
  544.         filename = name.substr(ix, name.length);
  545.         if (filename != "") {
  546.           ix = filename.lastIndexOf(";");
  547.           if (ix > 0)
  548.             filename = filename.substr(0, ix);
  549.           
  550.           filename = filename.replace(/^"|"$/g, "");
  551.         }
  552.       }
  553.     }
  554.     return filename;
  555.   }  
  556.  
  557. };
  558.  
  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;
  565.  
  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, aSaveMode)
  571. {
  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;
  577.  
  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;
  585.  
  586.   case "application/xhtml+xml":
  587.     bundleName   = "WebPageXHTMLOnlyFilter";
  588.     filterString = "*.xht; *.xhtml";
  589.     break;
  590.  
  591.   case "text/xml":
  592.   case "application/xml":
  593.     bundleName   = "WebPageXMLOnlyFilter";
  594.     filterString = "*.xml";
  595.     break;
  596.  
  597.   default:
  598.     if (aSaveMode != SAVEMODE_FILEONLY) {
  599.       throw "Invalid save mode for type '" + aContentType + "'";
  600.     }
  601.  
  602.     var mimeInfo = getMIMEInfoForType(aContentType);
  603.     if (mimeInfo) {
  604.       var extCount = { };
  605.       var extList = { };
  606.       mimeInfo.GetFileExtensions(extCount, extList);
  607.  
  608.       var extString = "";
  609.       for (var i = 0; i < extCount.value; ++i) {
  610.         if (i > 0) 
  611.           extString += "; "; // If adding more than one extension, separate by semi-colon
  612.         extString += "*." + extList.value[i];
  613.       }
  614.       
  615.       if (extCount.value > 0) {
  616.         aFilePicker.appendFilter(mimeInfo.Description, extString);
  617.       }
  618.     }
  619.  
  620.     break;
  621.   }
  622.  
  623.   if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
  624.     aFilePicker.appendFilter(bundle.GetStringFromName("WebPageCompleteFilter"), filterString);
  625.     // We should always offer a choice to save document only if
  626.     // we allow saving as complete.
  627.     aFilePicker.appendFilter(bundle.GetStringFromName(bundleName), filterString);
  628.   }
  629.  
  630.   if (aSaveMode & SAVEMODE_COMPLETE_TEXT) {
  631.     aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);
  632.   }
  633.  
  634.   // Always append the all files (*) filter
  635.   aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
  636.  
  637. function getPostData()
  638. {
  639.   try {
  640.     var sessionHistory = getWebNavigation().sessionHistory;
  641.     entry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
  642.     entry = entry.QueryInterface(Components.interfaces.nsISHEntry);
  643.     return entry.postData;
  644.   }
  645.   catch (e) {
  646.   }
  647.   return null;
  648. }
  649.  
  650. function getStringBundle()
  651. {
  652.   const bundleURL = "chrome://communicator/locale/contentAreaCommands.properties";
  653.   
  654.   const sbsContractID = "@mozilla.org/intl/stringbundle;1";
  655.   const sbsIID = Components.interfaces.nsIStringBundleService;
  656.   const sbs = Components.classes[sbsContractID].getService(sbsIID);
  657.   
  658.   const lsContractID = "@mozilla.org/intl/nslocaleservice;1";
  659.   const lsIID = Components.interfaces.nsILocaleService;
  660.   const ls = Components.classes[lsContractID].getService(lsIID);
  661.   var appLocale = ls.GetApplicationLocale();
  662.   return sbs.createBundle(bundleURL, appLocale);    
  663. }
  664.  
  665. function makeWebBrowserPersist()
  666. {
  667.   const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
  668.   const persistIID = Components.interfaces.nsIWebBrowserPersist;
  669.   return Components.classes[persistContractID].createInstance(persistIID);
  670. }
  671.  
  672. function makeProgressDialog()
  673. {
  674.   const progressDialogContractID = "@mozilla.org/progressdialog;1";
  675.   const progressDialogIID = Components.interfaces.nsIProgressDialog;
  676.   return Components.classes[progressDialogContractID].createInstance(progressDialogIID);
  677. }
  678.  
  679. function makeURL(aURL)
  680. {
  681.   var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  682.                 .getService(Components.interfaces.nsIIOService);
  683.   return ioService.newURI(aURL, null, null);
  684.   
  685. }
  686.  
  687. function makeFilePicker()
  688. {
  689.   const fpContractID = "@mozilla.org/filepicker;1";
  690.   const fpIID = Components.interfaces.nsIFilePicker;
  691.   return Components.classes[fpContractID].createInstance(fpIID);
  692. }
  693.  
  694. function makeTempFile()
  695. {
  696.   const mimeTypes = "TmpD";
  697.   const flContractID = "@mozilla.org/file/directory_service;1";
  698.   const flIID = Components.interfaces.nsIProperties;
  699.   var fileLocator = Components.classes[flContractID].getService(flIID);
  700.   var tempFile = fileLocator.get(mimeTypes, Components.interfaces.nsIFile);
  701.   tempFile.append("~sav" + Math.floor(Math.random() * 1000) + ".tmp");
  702.   return tempFile;
  703. }
  704.  
  705. function getMIMEService()
  706. {
  707.   const mimeSvcContractID = "@mozilla.org/mime;1";
  708.   const mimeSvcIID = Components.interfaces.nsIMIMEService;
  709.   const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
  710.   return mimeSvc;
  711. }
  712.  
  713. function getMIMEInfoForExtension(aExtension)
  714. {
  715.   try {  
  716.     return getMIMEService().GetFromExtension(aExtension);
  717.   }
  718.   catch (e) {
  719.   }
  720.   return null;
  721. }
  722.  
  723. function getMIMEInfoForType(aMIMEType)
  724. {
  725.   try {  
  726.     return getMIMEService().GetFromMIMEType(aMIMEType);
  727.   }
  728.   catch (e) {
  729.   }
  730.   return null;
  731. }
  732.  
  733. function getDefaultFileName(aDefaultFileName, aNameFromHeaders, aDocumentURI, aDocument)
  734. {
  735.   if (aNameFromHeaders)
  736.     // 1) Use the name suggested by the HTTP headers
  737.     return validateFileName(aNameFromHeaders);
  738.  
  739.   try {
  740.     var url = aDocumentURI.QueryInterface(Components.interfaces.nsIURL);
  741.     if (url.fileName != "") {
  742.       // 2) Use the actual file name, if present
  743.       return unescape(url.fileName);
  744.     }
  745.   } catch (e) {
  746.     try {
  747.       // the file name might be non ASCII
  748.       // try unescape again with a characterSet
  749.       var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
  750.                                    .getService(Components.interfaces.nsITextToSubURI);
  751.       var charset;
  752.       if (aDocument)
  753.         charset = aDocument.characterSet;
  754.       else if (document.commandDispatcher.focusedWindow)
  755.         charset = document.commandDispatcher.focusedWindow.document.characterSet;
  756.       else
  757.         charset = window._content.document.characterSet;
  758.       return textToSubURI.unEscapeURIForUI(charset, url.fileName);
  759.     } catch (e) {
  760.       // This is something like a wyciwyg:, data:, and so forth
  761.       // URI... no usable filename here.
  762.     }
  763.   }
  764.   
  765.   if (aDocument) {
  766.     var docTitle = validateFileName(aDocument.title).replace(/^\s+|\s+$/g, "");
  767.  
  768.     if (docTitle != "") {
  769.       // 3) Use the document title
  770.       return docTitle;
  771.     }
  772.   }
  773.   
  774.   if (aDefaultFileName)
  775.     // 4) Use the caller-provided name, if any
  776.     return validateFileName(aDefaultFileName);
  777.  
  778.   try {
  779.     if (aDocumentURI.host)
  780.       // 5) Use the host.
  781.       return aDocumentURI.host;
  782.   } catch (e) {
  783.     // Some files have no information at all, like Javascript generated pages
  784.   }
  785.   try {
  786.     // 6) Use the default file name
  787.     return getStringBundle().GetStringFromName("DefaultSaveFileName");
  788.   } catch (e) {
  789.     //in case localized string cannot be found
  790.   }
  791.   // 7) If all else fails, use "index"
  792.   return "index";
  793. }
  794.  
  795. function validateFileName(aFileName)
  796. {
  797.   var re = /[\/]+/g;
  798.   if (navigator.appVersion.indexOf("Windows") != -1) {
  799.     re = /[\\\/\|]+/g;
  800.     aFileName = aFileName.replace(/[\"]+/g, "'");
  801.     aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
  802.     aFileName = aFileName.replace(/[\<]+/g, "(");
  803.     aFileName = aFileName.replace(/[\>]+/g, ")");
  804.   }
  805.   else if (navigator.appVersion.indexOf("Macintosh") != -1)
  806.     re = /[\:\/]+/g;
  807.   
  808.   return aFileName.replace(re, "_");
  809. }
  810.  
  811. function getNormalizedLeafName(aFile, aDefaultExtension)
  812. {
  813.   if (!aDefaultExtension)
  814.     return aFile;
  815.   
  816.   // Fix up the file name we're saving to to include the default extension
  817.   const stdURLContractID = "@mozilla.org/network/standard-url;1";
  818.   const stdURLIID = Components.interfaces.nsIURL;
  819.   var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
  820.   url.filePath = aFile;
  821.   
  822.   if (url.fileExtension != aDefaultExtension) {
  823.     return aFile + "." + aDefaultExtension;
  824.   }
  825.  
  826.   return aFile;
  827. }
  828.  
  829. function getDefaultExtension(aFilename, aURI, aContentType)
  830. {
  831.   if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
  832.     return "";   // temporary fix for bug 120327
  833.  
  834.   // This mirrors some code in nsExternalHelperAppService::DoContent
  835.   // Use the filename first and then the URI if that fails
  836.   
  837.   var mimeInfo = getMIMEInfoForType(aContentType);
  838.  
  839.   // First try the extension from the filename
  840.   const stdURLContractID = "@mozilla.org/network/standard-url;1";
  841.   const stdURLIID = Components.interfaces.nsIURL;
  842.   var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
  843.   url.filePath = aFilename;
  844.  
  845.   var ext = url.fileExtension;
  846.  
  847.   if (ext && mimeInfo && mimeInfo.ExtensionExists(ext)) {
  848.     return ext;
  849.   }
  850.   
  851.   // Well, that failed.  Now try the extension from the URI
  852.   var urlext;
  853.   try {
  854.     url = aURI.QueryInterface(Components.interfaces.nsIURL);
  855.     urlext = url.fileExtension;
  856.   } catch (e) {
  857.   }
  858.  
  859.   if (urlext && mimeInfo && mimeInfo.ExtensionExists(urlext)) {
  860.     return urlext;
  861.   }
  862.   else {
  863.     try {
  864.       return mimeInfo.primaryExtension;
  865.     }
  866.     catch (e) {
  867.       // Fall back on the extensions in the filename and URI for lack
  868.       // of anything better.
  869.       return ext || urlext;
  870.     }
  871.   }
  872. }
  873.  
  874. function GetSaveModeForContentType(aContentType)
  875. {
  876.   var saveMode = SAVEMODE_FILEONLY;
  877.   switch (aContentType) {
  878.   case "text/html":
  879.   case "application/xhtml+xml":
  880.     saveMode |= SAVEMODE_COMPLETE_TEXT;
  881.     // Fall through
  882.   case "text/xml":
  883.   case "application/xml":
  884.     saveMode |= SAVEMODE_COMPLETE_DOM;
  885.     break;
  886.   }
  887.  
  888.   return saveMode;
  889. }
  890.