Bug 1316570 - use data urls for signature images, when inserting images from file, and when setting background image. r=jorgk
authorMagnus Melin <mkmelin+mozilla@iki.fi>
Sun, 13 Nov 2016 23:54:16 +0200
changeset 20703 dd4587a78cfd200881956427c8d3fd468aaf9758
parent 20702 40eadd4a11bd430d0ca17d52ee5db2f65defc187
child 20704 e0055f1fa1ac5ab5879f63d1f32e5b0eb66a7cb5
push id12527
push usermozilla@jorgk.com
push dateMon, 14 Nov 2016 07:19:19 +0000
treeherdercomm-central@dd4587a78cfd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorgk
bugs1316570
Bug 1316570 - use data urls for signature images, when inserting images from file, and when setting background image. r=jorgk
editor/ui/dialogs/content/EdImageOverlay.js
editor/ui/dialogs/content/EdInsSrc.js
mail/components/compose/content/EdColorPropsOverlay.xul
mail/components/compose/jar.mn
mailnews/compose/content/mailComposeEditorOverlay.xul
mailnews/compose/src/nsMsgCompose.cpp
mailnews/compose/src/nsMsgCompose.h
--- a/editor/ui/dialogs/content/EdImageOverlay.js
+++ b/editor/ui/dialogs/content/EdImageOverlay.js
@@ -98,20 +98,20 @@ function onCopyOrCut(event) {
 // Set dialog widgets with attribute data
 // We get them from globalElement copy so this can be used
 //   by AdvancedEdit(), which is shared by all property dialogs
 function InitImage()
 {
   // Set the controls to the image's attributes
   var src = globalElement.getAttribute("src");
 
-  // For image insertion the 'src' attribute is null. 
+  // For image insertion the 'src' attribute is null.
   if (src) {
     // Shorten data URIs for display.
-    gDialog.srcInput.value = src.replace(/(data:[^;]*;base64,)(.*)/i,
+    gDialog.srcInput.value = src.replace(/(data:.+;base64,)(.*)/i,
       function(match, nonDataPart, dataPart) {
         if (!gListenerAttached) {
           gDialog.srcInput.addEventListener("copy", onCopyOrCut);
           gDialog.srcInput.addEventListener("cut", onCopyOrCut);
           gListenerAttached = true;
         }
         gDialog.srcInput.setAttribute("tooltip", "shortenedDataURI");
         gFullDataURI = src;
--- a/editor/ui/dialogs/content/EdInsSrc.js
+++ b/editor/ui/dialogs/content/EdInsSrc.js
@@ -43,17 +43,17 @@ function Startup()
   // Set initial focus
   gDialog.srcInput.focus();
   // Note: We can't set the caret location in a multiline textbox
   SetWindowLocation();
 }
 
 function replaceDataURIs(input)
 {
-  return input.replace(/(data:[^;]*;base64,)([^"' >]+)/gi,
+  return input.replace(/(data:.+;base64,)([^"' >]+)/gi,
     function(match, nonDataPart, dataPart) {
 
       if (gShortDataStrings.has(dataPart)) {
           // We found the exact same data URI, just return the shortened URI.
           return nonDataPart + gShortDataStrings.get(dataPart);
       }
 
       let l = 5;
@@ -82,17 +82,17 @@ function onCopyOrCut(event)
 {
   let startPos = gDialog.srcInput.selectionStart;
   if (startPos == undefined)
     return;
   let endPos = gDialog.srcInput.selectionEnd;
   let clipboard = gDialog.srcInput.value.substring(startPos, endPos);
 
   // Add back the original data URIs we stashed away earlier.
-  clipboard = clipboard.replace(/(data:[^;]*;base64,)([^"' >]+)/gi,
+  clipboard = clipboard.replace(/(data:.+;base64,)([^"' >]+)/gi,
     function(match, nonDataPart, key) {
       if (!gFullDataStrings.has(key))
         return match; // user changed data URI
       return nonDataPart + gFullDataStrings.get(key);
     });
   event.clipboardData.setData("text/plain", clipboard);
   if (event.type == "cut") {
     // We have to cut the selection manually.
@@ -120,17 +120,17 @@ function onPaste(event)
 
 function onAccept()
 {
   let html = gDialog.srcInput.value;
   if (!html)
     return false;
 
   // Add back the original data URIs we stashed away earlier.
-  html = html.replace(/(data:[^;]*;base64,)([^"' >]+)/gi,
+  html = html.replace(/(data:.+;base64,)([^"' >]+)/gi,
     function(match, nonDataPart, key) {
       if (!gFullDataStrings.has(key))
         return match; // user changed data URI
       return nonDataPart + gFullDataStrings.get(key);
     });
 
   try {
     GetCurrentEditor().insertHTML(html);
new file mode 100644
--- /dev/null
+++ b/mail/components/compose/content/EdColorPropsOverlay.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd" >
+
+<overlay id="mailEdColorPropsOverlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript">
+  <![CDATA[
+  function onAccept() {
+    // If it's a file, convert to a data URL.
+    if (gBackgroundImage && /^file:/i.test(gBackgroundImage)) {
+      let nsFile = Services.io.newURI(gBackgroundImage, null, null)
+        .QueryInterface(Components.interfaces.nsIFileURL).file;
+      if (nsFile.exists()) {
+        let reader = new FileReader();
+        reader.addEventListener("load", function() {
+          gBackgroundImage = reader.result;
+          gDialog.BackgroundImageInput.value = reader.result;
+          if (onAccept()) {
+            window.close();
+          }
+        });
+        reader.readAsDataURL(new File(nsFile));
+        return false; // Don't close just yet...
+      }
+    }
+    if (ValidateData()) {
+      // Copy attributes to element we are changing
+      try {
+        GetCurrentEditor().cloneAttributes(gBodyElement, globalElement);
+      } catch (e) {}
+
+      SaveWindowLocation();
+      return true; // do close the window
+    }
+    return false;
+  }
+  ]]>
+  </script>
+
+</overlay>
--- a/mail/components/compose/jar.mn
+++ b/mail/components/compose/jar.mn
@@ -1,17 +1,19 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 messenger.jar:
 % overlay chrome://editor/content/EdImageOverlay.xul chrome://messenger/content/messengercompose/mailComposeEditorOverlay.xul
 % overlay chrome://editor/content/EdImageOverlay.xul chrome://messenger/content/messengercompose/EdImageOverlayOverlay.xul
+% overlay chrome://editor/content/EdColorProps.xul chrome://messenger/content/messengercompose/EdColorPropsOverlay.xul
 % overlay chrome://editor/content/EdLinkProps.xul chrome://messenger/content/messengercompose/mailComposeEditorOverlay.xul
 *   content/messenger/messengercompose/messengercompose.xul        (content/messengercompose.xul)
     content/messenger/messengercompose/MsgComposeCommands.js       (content/MsgComposeCommands.js)
     content/messenger/messengercompose/bigFileObserver.js          (content/bigFileObserver.js)
     content/messenger/messengercompose/cloudAttachmentLinkManager.js (content/cloudAttachmentLinkManager.js)
     content/messenger/messengercompose/EdImageOverlayOverlay.xul   (content/EdImageOverlayOverlay.xul)
+    content/messenger/messengercompose/EdColorPropsOverlay.xul     (content/EdColorPropsOverlay.xul)
     content/messenger/messengercompose/addressingWidgetOverlay.js  (content/addressingWidgetOverlay.js)
 
 comm.jar:
     content/editor/editorOverlay.xul                               (content/editorOverlay.xul)
--- a/mailnews/compose/content/mailComposeEditorOverlay.xul
+++ b/mailnews/compose/content/mailComposeEditorOverlay.xul
@@ -12,17 +12,16 @@
   <![CDATA[
     Components.utils.import("resource://gre/modules/Services.jsm");
 
     var gMsgCompProcessLink = false;
     var gMsgCompInputElement = null;
     var gMsgCompPrevInputValue = null;
     var gMsgCompPrevMozDoNotSendAttribute;
     var gMsgCompAttachSourceElement = null;
-    var gMOZDONOTSEND = "moz-do-not-send";
 
     function OnLoadOverlay()
     {
       gMsgCompAttachSourceElement = document.getElementById("AttachSourceToMail");
       var editor = GetCurrentEditor();
       if (gMsgCompAttachSourceElement && editor &&
           (editor.flags & Components.interfaces.nsIPlaintextEditor.eEditorMailMask))
       {
@@ -39,27 +38,40 @@
           case "linkDlg" :
             gMsgCompInputElement =  gDialog.hrefInput;
             gMsgCompProcessLink = true;
             break;
         }
         if (gMsgCompInputElement)
         {
           SetAttachCheckbox();
-          gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute(gMOZDONOTSEND)
+          gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute("moz-do-not-send")
         }
       }
     }
+    addEventListener("load", OnLoadOverlay, false);
 
-    addEventListener("load", OnLoadOverlay, false);
+    function OnAcceptOverlay()
+    {
+      // Auto-convert file URLs to data URLs. If we're in the link properties
+      // dialog convert only when requested - for the image dialog do it always.
+      if (/^file:/i.test(gMsgCompInputElement.value.trim()) &&
+          (gMsgCompAttachSourceElement.checked || !gMsgCompProcessLink)) {
+        var dataURI = GenerateDataURL(gMsgCompInputElement.value.trim());
+        gMsgCompInputElement.value = dataURI;
+        gMsgCompAttachSourceElement.checked = true;
+        DoAttachSourceCheckbox();
+       }
+     }
+     addEventListener("dialogaccept", OnAcceptOverlay, false);
 
     function SetAttachCheckbox()
     {
       var resetCheckbox = false;
-      var mozDoNotSend = globalElement.getAttribute(gMOZDONOTSEND);
+      var mozDoNotSend = globalElement.getAttribute("moz-do-not-send");
 
       //In case somebody played with the advanced property and changed the moz-do-not-send attribute
       if (mozDoNotSend != gMsgCompPrevMozDoNotSendAttribute)
       {
         gMsgCompPrevMozDoNotSendAttribute = mozDoNotSend;
         resetCheckbox = true;
       }
 
@@ -68,55 +80,59 @@
       {
         gMsgCompPrevInputValue = gMsgCompInputElement.value;
         resetCheckbox = true;
       }
 
       if (gMsgCompInputElement && resetCheckbox)
       {
         // Here is the rule about how to set the checkbox Attach Source To Message:
-        // if the attribute moz-do-not-send has not been set, we look at the scheme of the url
+        // if the attribute "moz-do-not-send" has not been set, we look at the scheme of the url
         // and at some pref to decide what is the best for the user. Else if it is set to true,
         // the checkbox is unchecked else is checked.
-        var attach = true;
+        var attach = false;
         if (mozDoNotSend == null)
         {
-          // We haven't yet set the moz-do-not-send attribute, let's figure out the best setting
-          // the rule should be in sync with to the one in nsMsgComposeAndSend::GetEmbeddedObjectInfo
-
-          if (gMsgCompProcessLink)
-          {
-            //is it a Windows remote file?
-            if (gMsgCompInputElement.value.trimLeft().toLowerCase().startsWith("file://///"))
-            {
-              try {
-                let dontAttachPref =
-                  "mail.compose.dont_attach_source_of_local_network_links";
-                if (Services.prefs.getBoolPref(dontAttachPref))
-                  attach = false;
-              } catch(ex) {};
-            }
-            //is it not a file: location at all?
-            else if (!gMsgCompInputElement.value.trimLeft().toLowerCase().startsWith("file://"))
-              attach = false;
-          }
+          // We haven't yet set the "moz-do-not-send" attribute.
+          // For files, default to attach them.
+          attach = /^file:/i.test(gMsgCompInputElement.value);
         }
         else
-          attach = (mozDoNotSend != "true");
+        {
+          attach = (mozDoNotSend == "false");
+        }
 
         gMsgCompAttachSourceElement.checked = attach;
       }
     }
 
     function DoAttachSourceCheckbox()
     {
       gMsgCompPrevMozDoNotSendAttribute = (!gMsgCompAttachSourceElement.checked).toString();
-      globalElement.setAttribute(gMOZDONOTSEND, gMsgCompPrevMozDoNotSendAttribute)
+      globalElement.setAttribute("moz-do-not-send", gMsgCompPrevMozDoNotSendAttribute);
     }
 
+    function GenerateDataURL(url) {
+      var file = Services.io.newURI(url, null, null)
+        .QueryInterface(Components.interfaces.nsIFileURL).file;
+      var contentType = Components.classes["@mozilla.org/mime;1"]
+        .getService(Components.interfaces.nsIMIMEService)
+        .getTypeFromFile(file);
+      var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+        .createInstance(Components.interfaces.nsIFileInputStream);
+      inputStream.init(file, 0x01, 0600, 0);
+      var stream = Components.classes["@mozilla.org/binaryinputstream;1"]
+        .createInstance(Components.interfaces.nsIBinaryInputStream);
+      stream.setInputStream(inputStream);
+      var encoded = btoa(stream.readBytes(stream.available()));
+      stream.close();
+      return "data:" + contentType +
+        ";filename=" + encodeURIComponent(file.leafName) +
+        ";base64," + encoded;
+    }
   ]]>
   </script>
 
   <hbox id="MakeRelativeHbox">
     <checkbox id="AttachSourceToMail" hidden="true"
               label="&attachImageSource.label;" accesskey="&attachImageSource.accesskey;"
               insertafter="MakeRelativeCheckbox" oncommand="DoAttachSourceCheckbox()"/>
   </hbox>
--- a/mailnews/compose/src/nsMsgCompose.cpp
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -78,16 +78,18 @@
 #include "mozilla/Services.h"
 #include "mozilla/mailnews/MimeHeaderParser.h"
 #include "mozilla/Preferences.h"
 #include "nsStreamConverter.h"
 #include "nsISelection.h"
 #include "nsJSEnvironment.h"
 #include "nsIObserverService.h"
 #include "nsIProtocolHandler.h"
+#include "nsContentUtils.h"
+#include "nsIFileURL.h"
 
 using namespace mozilla;
 using namespace mozilla::mailnews;
 
 static nsresult GetReplyHeaderInfo(int32_t* reply_header_type,
                                    nsString& reply_header_locale,
                                    nsString& reply_header_authorwrote,
                                    nsString& reply_header_ondateauthorwrote,
@@ -4223,29 +4225,128 @@ nsMsgCompose::LoadDataFromFile(nsIFile *
       nsMsgI18NTextFileCharset(textFileCharset);
       sigEncoding.Assign(textFileCharset);
     }
   }
 
   nsAutoCString readStr(readBuf, (int32_t) fileSize);
   PR_FREEIF(readBuf);
 
+  // XXX: ^^^ could really use nsContentUtils::SlurpFileToString instead!
+
   if (NS_FAILED(ConvertToUnicode(sigEncoding.get(), readStr, sigData)))
     CopyASCIItoUTF16(readStr, sigData);
 
   //remove sig meta charset to allow user charset override during composition
   if (removeSigCharset)
   {
     nsAutoCString metaCharset("charset=");
     metaCharset.Append(sigEncoding);
     int32_t pos = sigData.Find(metaCharset.BeginReading(), true);
     if (pos != kNotFound)
       sigData.Cut(pos, metaCharset.Length());
   }
-
+  return NS_OK;
+}
+
+/**
+ * If the data contains file URLs, convert them to data URLs instead.
+ * This is intended to be used in for signature files, so that we can make sure
+ * images loaded into the editor are available on send.
+ */
+nsresult
+nsMsgCompose::ReplaceFileURLs(nsAutoString &aData)
+{
+  int32_t fPos;
+  int32_t offset = -1;
+  while ((fPos = aData.RFind("file://", true, offset)) != kNotFound) {
+    if (fPos != kNotFound && fPos > 0) {
+      char16_t q = aData.CharAt(fPos - 1);
+      bool quoted = (q == '"' || q == '\'');
+      int32_t end = kNotFound;
+      if (quoted) {
+        end = aData.FindChar(q, fPos);
+      }
+      else {
+        int32_t spacePos = aData.FindChar(' ', fPos);
+        int32_t gtPos = aData.FindChar('>', fPos);
+        if (gtPos != kNotFound && spacePos != kNotFound) {
+          end = (spacePos < gtPos) ? spacePos : gtPos;
+        }
+        else if (gtPos == kNotFound && spacePos != kNotFound) {
+          end = spacePos;
+        }
+        else if (gtPos != kNotFound && spacePos == kNotFound) {
+          end = gtPos;
+        }
+      }
+      if (end == kNotFound) {
+        break;
+      }
+      nsString fileURL;
+      fileURL = Substring(aData, fPos, end - fPos);
+      nsString dataURL;
+      nsresult rv = DataURLForFileURL(fileURL, dataURL);
+      NS_ENSURE_SUCCESS(rv, rv);
+      aData.Replace(fPos, end - fPos, dataURL);
+      int32_t gtAfter = aData.FindChar('>', fPos + dataURL.Length());
+      if (gtAfter != kNotFound) {
+        aData.Insert(NS_LITERAL_STRING(" moz-do-not-send='false'"), gtAfter);
+      }
+      offset = fPos - 1;
+    }
+  }
+  return NS_OK;
+}
+
+nsresult
+nsMsgCompose::DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL)
+{
+  nsresult rv;
+  nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> fileUri;
+  rv = NS_NewURI(getter_AddRefs(fileUri), NS_ConvertUTF16toUTF8(aFileURL).get());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(fileUri, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIFile> file;
+  rv = fileUrl->GetFile(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString type;
+  rv = mime->GetTypeFromFile(file, type);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString data;
+  rv = nsContentUtils::SlurpFileToString(file, data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aDataURL.AssignLiteral("data:");
+  AppendUTF8toUTF16(type, aDataURL);
+
+  nsAutoString filename;
+  rv = file->GetLeafName(filename);
+  if (NS_SUCCEEDED(rv)) {
+    nsAutoCString fn;
+    MsgEscapeURL(NS_ConvertUTF16toUTF8(filename),
+      nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, fn);
+    if (!fn.IsEmpty()) {
+      aDataURL.AppendLiteral(";filename=");
+      aDataURL.Append(NS_ConvertUTF8toUTF16(fn));
+    }
+  }
+
+  aDataURL.AppendLiteral(";base64,");
+  char *result = PL_Base64Encode(data.get(), data.Length(), nullptr);
+  nsDependentCString base64data(result);
+  NS_ENSURE_SUCCESS(rv, rv);
+  AppendUTF8toUTF16(base64data, aDataURL);
   return NS_OK;
 }
 
 nsresult
 nsMsgCompose::BuildQuotedMessageAndSignature(void)
 {
   //
   // This should never happen...if it does, just bail out...
@@ -4341,17 +4442,17 @@ nsMsgCompose::ProcessSignature(nsIMsgIde
         }
       }
     }
   }
 
   // Unless signature to be attached from file, use preference value;
   // the htmlSigText value is always going to be treated as html if
   // the htmlSigFormat pref is true, otherwise it is considered text
-  nsString prefSigText;
+  nsAutoString prefSigText;
   if (identity && !attachFile)
     identity->GetHtmlSigText(prefSigText);
   // Now, if they didn't even want to use a signature, we should
   // just return nicely.
   //
   if ((!useSigFile  && prefSigText.IsEmpty()) || NS_FAILED(rv))
     return NS_OK;
 
@@ -4381,35 +4482,50 @@ nsMsgCompose::ProcessSignature(nsIMsgIde
       sigOutput.AppendLiteral(htmlBreak);
       sigOutput.AppendLiteral(htmlsigopen);
       if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) &&
           (reply_on_top != 1 || sig_bottom || !aQuoted)) {
         sigOutput.AppendLiteral(dashes);
       }
 
       sigOutput.AppendLiteral(htmlBreak);
-      sigOutput.AppendLiteral("<img src=\"file:///");
-           /* XXX pp This gives me 4 slashes on Unix, that's at least one to
-              much. Better construct the URL with some service. */
-      // this isn't right on windows - need to convert to url format...
-      sigOutput.Append(NS_ConvertASCIItoUTF16(sigNativePath));
-      sigOutput.AppendLiteral("\" border=0>");
+      sigOutput.AppendLiteral("<img src='");
+
+      nsCOMPtr<nsIURI> fileURI;
+      nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), sigFile);
+      NS_ENSURE_SUCCESS(rv, rv);
+      nsCString fileURL;
+      fileURI->GetSpec(fileURL);
+
+      nsString dataURL;
+      rv = DataURLForFileURL(NS_ConvertUTF8toUTF16(fileURL), dataURL);
+      if (NS_SUCCEEDED(rv)) {
+        sigOutput.Append(dataURL);
+        sigOutput.AppendLiteral("' moz-do-not-send='false' border=0>");
+      }
+      else {
+        sigOutput.AppendLiteral("' border=0>");
+      }
       sigOutput.AppendLiteral(htmlsigclose);
     }
   }
   else if (useSigFile)
   {
     // is this a text sig with an HTML editor?
-    if ( (m_composeHTML) && (!htmlSig) )
+    if ( (m_composeHTML) && (!htmlSig) ) {
       ConvertTextToHTML(sigFile, sigData);
+    }
     // is this a HTML sig with a text window?
-    else if ( (!m_composeHTML) && (htmlSig) )
+    else if ( (!m_composeHTML) && (htmlSig) ) {
       ConvertHTMLToText(sigFile, sigData);
-    else // We have a match...
+    }
+    else { // We have a match...
       LoadDataFromFile(sigFile, sigData);  // Get the data!
+      ReplaceFileURLs(sigData);
+    }
   }
 
   // if we have a prefSigText, append it to sigData.
   if (!prefSigText.IsEmpty())
   {
     // set htmlSig if the pref is supposed to contain HTML code, defaults to false
     rv = identity->GetHtmlSigFormat(&htmlSig);
     if (NS_FAILED(rv))
@@ -4429,18 +4545,20 @@ nsMsgCompose::ProcessSignature(nsIMsgIde
         if (escaped)
         {
           sigData.Append(escaped);
           NS_Free(escaped);
         }
         else
           sigData.Append(prefSigText);
       }
-      else
+      else {
+        ReplaceFileURLs(prefSigText);
         sigData.Append(prefSigText);
+      }
     }
   }
 
   // post-processing for plain-text signatures to ensure we end in CR, LF, or CRLF
   if (!htmlSig && !m_composeHTML)
   {
     int32_t sigLength = sigData.Length();
     if (sigLength > 0 && !(sigData.CharAt(sigLength - 1) == '\r')
--- a/mailnews/compose/src/nsMsgCompose.h
+++ b/mailnews/compose/src/nsMsgCompose.h
@@ -88,16 +88,18 @@ protected:
                            nsTArray<nsMsgMailList>& allMailListArray,
                            nsTArray<nsMsgMailList>& mailListResolved,
                            nsTArray<nsMsgRecipient>& aListMembers);
   nsresult TagConvertible(nsIDOMElement *node,  int32_t *_retval);
   nsresult _NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval);
   nsresult MoveToAboveQuote(void);
   nsresult MoveToBeginningOfDocument(void);
   nsresult MoveToEndOfDocument(void);
+  nsresult ReplaceFileURLs(nsAutoString &sigData);
+  nsresult DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL);
 
 // 3 = To, Cc, Bcc
 #define MAX_OF_RECIPIENT_ARRAY 3
   typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY];
   /**
    * This method parses the compose fields and associates email addresses with
    * the relevant cards from the address books.
    */