Bug 1356300 - shorten data URL for background images. r=aceman,jorgk a=jorgk
authorJorg K <jorgk@jorgk.com> and aceman <acelists@atlas.sk>
Sat, 15 Apr 2017 05:58:00 +0200
changeset 24469 d8949acb05a3
parent 24468 1694f6f2b300
child 24470 78171c3a96f2
push id2059
push usermozilla@jorgk.com
push dateMon, 17 Apr 2017 06:53:03 +0000
treeherdercomm-aurora@9d19a9b732a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaceman, jorgk, jorgk
bugs1356300
Bug 1356300 - shorten data URL for background images. r=aceman,jorgk a=jorgk
editor/ui/composer/content/editorUtilities.js
editor/ui/dialogs/content/EdColorProps.js
editor/ui/dialogs/content/EdImageOverlay.js
editor/ui/dialogs/content/EdImageOverlay.xul
--- a/editor/ui/composer/content/editorUtilities.js
+++ b/editor/ui/composer/content/editorUtilities.js
@@ -931,8 +931,84 @@ function Clone(obj)
   {
     if (typeof obj[i] == 'object')
       clone[i] = Clone(obj[i]);
     else
       clone[i] = obj[i];
   }
   return clone;
 }
+
+/**
+ * Utility funtions to handle shortended data: URLs in EdColorProps.js and EdImageOverlay.js.
+ */
+
+/**
+ * Is the passed in image URI a shortened data URI?
+ * @return {bool}
+ */
+function isImageDataShortened(aImageData) {
+  return (/^data:/i.test(aImageData) && aImageData.includes("…"));
+}
+
+/**
+ * Event handler for Copy or Cut
+ * @param aEvent  the event
+ */
+function onCopyOrCutShortened(aEvent) {
+  // Put the original data URI onto the clipboard in case the value
+  // is a shortened data URI.
+  let field = aEvent.target;
+  let startPos = field.selectionStart;
+  if (startPos == undefined)
+    return;
+  let endPos = field.selectionEnd;
+  let selection = field.value.substring(startPos, endPos).trim();
+
+  // Test that a) the user selected the whole value,
+  //           b) the value is a data URI,
+  //           c) it contains the ellipsis we added. Otherwise it could be
+  //              a new value that the user pasted in.
+  if (selection == field.value.trim() && isImageDataShortened(selection)) {
+    aEvent.clipboardData.setData("text/plain", field.fullDataURI);
+    if (aEvent.type == "cut") {
+      // We have to cut the selection manually. Since we tested that
+      // everything was selected, we can just reset the field.
+      field.value = "";
+    }
+    aEvent.preventDefault();
+  }
+}
+
+/**
+ * Set up element showing an image URI with a shortened version.
+ * and add event handler for Copy or Cut.
+ *
+ * @param aImageData    the data: URL of the image to be shortened.
+ *                      Note: Original stored in 'aDialogField.fullDataURI'.
+ * @param aDialogField  The field of the dialog to contain the data.
+ * @return {bool}       URL was shortened?
+ */
+function shortenImageData(aImageData, aDialogField) {
+  let shortened = false;
+  aDialogField.value = aImageData.replace(/^(data:.+;base64,)(.*)/i,
+    function(match, nonDataPart, dataPart) {
+      if (dataPart.length <= 35)
+        return match;
+
+      shortened = true;
+      aDialogField.addEventListener("copy", onCopyOrCutShortened);
+      aDialogField.addEventListener("cut", onCopyOrCutShortened);
+      aDialogField.fullDataURI = aImageData;
+      return nonDataPart + dataPart.substring(0, 5) + "…" +
+                           dataPart.substring(dataPart.length - 30);
+    });
+  return shortened;
+}
+
+/**
+ * Return full data URIs for a shortened element.
+ *
+ * @param aDialogField  The field of the dialog containing the data.
+ */
+function restoredImageData(aDialogField) {
+  return aDialogField.fullDataURI;
+}
--- a/editor/ui/dialogs/content/EdColorProps.js
+++ b/editor/ui/dialogs/content/EdColorProps.js
@@ -103,20 +103,21 @@ function Startup()
 
 function InitDialog()
 {
   // Get image from document
   gBackgroundImage = GetHTMLOrCSSStyleValue(globalElement, backgroundStr, cssBackgroundImageStr);
   if (/url\((.*)\)/.test( gBackgroundImage ))
     gBackgroundImage = RegExp.$1;
 
-  gDialog.BackgroundImageInput.value = gBackgroundImage;
-
-  if (gBackgroundImage)
+  if (gBackgroundImage) {
+    // Shorten data URIs for display.
+    shortenImageData(gBackgroundImage, gDialog.BackgroundImageInput);
     gDialog.ColorPreview.setAttribute(styleStr, backImageStyle+gBackgroundImage+");");
+  }
 
   SetRelativeCheckbox();
 
   customTextColor        = GetHTMLOrCSSStyleValue(globalElement, textStr, cssColorStr);
   customTextColor        = ConvertRGBColorIntoHEXColor(customTextColor);
   customLinkColor        = globalElement.getAttribute(linkStr);
   customActiveColor      = globalElement.getAttribute(alinkStr);
   customVisitedColor     = globalElement.getAttribute(vlinkStr);
@@ -331,23 +332,33 @@ function ValidateAndPreviewImage(ShowErr
 {
   // First make a string with just background color
   var styleValue = backColorStyle+previewBGColor+";";
 
   var retVal = true;
   var image = TrimString(gDialog.BackgroundImageInput.value);
   if (image)
   {
-    gBackgroundImage = image;
+    if (isImageDataShortened(image))
+    {
+      gBackgroundImage = restoredImageData(gDialog.BackgroundImageInput);
+    }
+    else
+    {
+      gBackgroundImage = image;
 
-    // Display must use absolute URL if possible
-    var displayImage = gHaveDocumentUrl ? MakeAbsoluteUrl(image) : image;
-    styleValue += backImageStyle+displayImage+");";
+      // Display must use absolute URL if possible
+      var displayImage = gHaveDocumentUrl ? MakeAbsoluteUrl(image) : image;
+      styleValue += backImageStyle+displayImage+");";
+    }
   }
-  else gBackgroundImage = null;
+  else
+  {
+    gBackgroundImage = null;
+  }
 
   // Set style on preview (removes image if not valid)
   gDialog.ColorPreview.setAttribute(styleStr, styleValue);
 
   // Note that an "empty" string is valid
   return retVal;
 }
 
--- a/editor/ui/dialogs/content/EdImageOverlay.js
+++ b/editor/ui/dialogs/content/EdImageOverlay.js
@@ -25,18 +25,16 @@ var gCanRemoveImageMap = false;
 var gRemoveImageMap = false;
 var gImageMapDisabled = false;
 var gActualWidth = "";
 var gActualHeight = "";
 var gOriginalSrc = "";
 var gHaveDocumentUrl = false;
 var gTimerID;
 var gValidateTab;
-var gFullDataURI = null;
-var gListenerAttached = false;
 
 // These must correspond to values in EditorDialog.css for each theme
 // (unfortunately, setting "style" attribute here doesn't work!)
 var gPreviewImageWidth = 80;
 var gPreviewImageHeight = 50;
 
 // dialog initialization code
 
@@ -65,64 +63,31 @@ function ImageStartup()
   gDialog.ImageHolder       = document.getElementById( "preview-image-holder" );
   gDialog.PreviewWidth      = document.getElementById( "PreviewWidth" );
   gDialog.PreviewHeight     = document.getElementById( "PreviewHeight" );
   gDialog.PreviewSize       = document.getElementById( "PreviewSize" );
   gDialog.PreviewImage      = null;
   gDialog.OkButton          = document.documentElement.getButton("accept");
 }
 
-function onCopyOrCut(event) {
-  // Put the original data URI onto the clipboard in case the value
-  // is a shortened data URI.
-  let startPos = gDialog.srcInput.selectionStart;
-  if (startPos == undefined)
-    return;
-  let endPos = gDialog.srcInput.selectionEnd;
-  let selection = gDialog.srcInput.value.substring(startPos, endPos).trim();
-
-  // Test that a) the user selected the whole value,
-  //           b) the value is a data URI,
-  //           c) it contains the ellipsis we added. Otherwise it could be
-  //              a new value that the user pasted in.
-  if (selection == gDialog.srcInput.value.trim() &&
-      /^data:/i.test(selection) && selection.includes("…")) {
-    event.clipboardData.setData("text/plain", gFullDataURI);
-    if (event.type == "cut") {
-      // We have to cut the selection manually. Since we tested that
-      // everything was selected, we can just reset the field.
-      gDialog.srcInput.value = "";
-    }
-    event.preventDefault();
-  }
-}
-
 // 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.
   if (src) {
     // Shorten data URIs for display.
-    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;
-        return nonDataPart + dataPart.substring(0, 5) + "…" +
-                             dataPart.substring(dataPart.length - 30);
-      });
+    if (shortenImageData(src, gDialog.srcInput)) {
+      gDialog.srcInput.removeAttribute("tooltiptext");
+      gDialog.srcInput.setAttribute("tooltip", "shortenedDataURI");
+    }
   }
 
   // Set "Relativize" checkbox according to current URL state
   SetRelativeCheckbox();
 
   // Force loading of image from its source and show preview image
   LoadPreviewImage();
 
@@ -317,20 +282,19 @@ function LoadPreviewImage()
 {
   gDialog.PreviewSize.collapsed = true;
   // XXXbz workaround for bug 265416 / bug 266284
   gDialog.ImageHolder.collapsed = true;
 
   var imageSrc = TrimString(gDialog.srcInput.value);
   if (!imageSrc)
     return;
-  if (/^data:/i.test(imageSrc) && imageSrc.includes("…"))
+  if (isImageDataShortened(imageSrc))
   {
-    // Restore URIs from the original.
-    imageSrc = gFullDataURI;
+    imageSrc = restoredImageData(gDialog.srcInput);
   }
 
   try {
     // Remove the image URL from image cache so it loads fresh
     //  (if we don't do this, loads after the first will always use image cache
     //   and we won't see image edit changes or be able to get actual width and height)
 
     // We must have an absolute URL to preview it or remove it from the cache
@@ -503,20 +467,19 @@ function ValidateImage()
     SwitchToValidatePanel();
     gDialog.srcInput.focus();
     return false;
   }
 
   // We must convert to "file:///" or "http://" format else image doesn't load!
   let src = gDialog.srcInput.value.trim();
 
-  if (/^data:/i.test(src) && src.includes("…"))
+  if (isImageDataShortened(src))
   {
-    // Restore shortened URIs from the original.
-    src = gFullDataURI;
+    src = restoredImageData(gDialog.srcInput);
   }
   else
   {
     var checkbox = document.getElementById("MakeRelativeCheckbox");
     try
     {
       if (checkbox && !checkbox.checked)
       {
--- a/editor/ui/dialogs/content/EdImageOverlay.xul
+++ b/editor/ui/dialogs/content/EdImageOverlay.xul
@@ -21,17 +21,18 @@
     <label control = "srcInput"
       value = "&locationEditField.label;"
       accesskey="&locationEditField.accessKey;"
       tooltiptext="&locationEditField.tooltip;"
       />
     <tooltip id="shortenedDataURI">
       <label value="&locationEditField.shortenedDataURI;"/>
     </tooltip>
-    <textbox id="srcInput" oninput="ChangeImageSrc();" tabindex="1" class="uri-element"/>
+    <textbox id="srcInput" oninput="ChangeImageSrc();" tabindex="1" class="uri-element"
+             tooltiptext="&locationEditField.tooltip;"/>
     <hbox id="MakeRelativeHbox">
       <checkbox 
         id  = "MakeRelativeCheckbox"
         for = "srcInput"
         tabindex="2"/>
       <!-- mail compose will insert custom item here defined in mailComposeEditorOverlay.xul -->
       <spacer flex="1"/>
       <!-- from EdDialogOverlay.xul -->