Bug 1409148 - Add dynamic sizing on the plugin overlay. r=johannh,dthayer draft
authorFelipe Gomes <felipc@gmail.com>
Fri, 10 Nov 2017 14:05:04 -0200
changeset 696400 b86c5b28b7152ac6971fc631ba62d3296dca4c7e
parent 696399 d1f612366905fb249b6202ee237c728a4422c681
child 696401 5e8d5e6f0d2b52acafdc3b85c654a3831b534a98
push id88703
push userfelipc@gmail.com
push dateFri, 10 Nov 2017 16:09:56 +0000
reviewersjohannh, dthayer
bugs1409148
milestone58.0a1
Bug 1409148 - Add dynamic sizing on the plugin overlay. r=johannh,dthayer MozReview-Commit-ID: 9cxCs9GeJY8
browser/base/content/test/plugins/plugin_test.html
browser/modules/PluginContent.jsm
toolkit/pluginproblem/content/pluginProblemContent.css
toolkit/themes/shared/plugins/pluginProblem.css
--- a/browser/base/content/test/plugins/plugin_test.html
+++ b/browser/base/content/test/plugins/plugin_test.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
-<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+<embed id="test" style="width: 300px; height: 300px" type="application/x-test">
 </body>
 </html>
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -25,19 +25,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 this.PluginContent = function(global) {
   this.init(global);
 };
 
 const FLASH_MIME_TYPE = "application/x-shockwave-flash";
 const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css");
 
-const OVERLAY_DISPLAY_HIDDEN = 0;
-const OVERLAY_DISPLAY_VISIBLE = 1;
-const OVERLAY_DISPLAY_MINIMAL = 2;
+const OVERLAY_DISPLAY = {
+  HIDDEN: 0, // The overlay will be transparent
+  BLANK: 1, // The overlay will be just a grey box
+  TINY: 2, // The overlay with a 16x16 plugin icon
+  REDUCED: 3, // The overlay with a 32x32 plugin icon
+  NOTEXT: 4, // The overlay with a 48x48 plugin icon and the close button
+  FULL: 5, // The full overlay: 48x48 plugin icon, close button and label
+};
 
 PluginContent.prototype = {
   init(global) {
     this.global = global;
     // Need to hold onto the content window or else it'll get destroyed
     this.content = this.global.content;
     // Cache of plugin actions for the current page.
     this.pluginData = new Map();
@@ -284,54 +289,90 @@ PluginContent.prototype = {
              blocklistState,
            };
   },
 
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility(plugin, overlay, overlayDisplayState) {
-    overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY_HIDDEN);
-    overlay.classList.toggle("minimal", overlayDisplayState == OVERLAY_DISPLAY_MINIMAL)
-    if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+    overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY.HIDDEN);
+    if (overlayDisplayState != OVERLAY_DISPLAY.HIDDEN) {
       overlay.removeAttribute("dismissed");
     }
   },
 
   /**
-   * Check whether the plugin should be visible on the page. A plugin should
-   * not be visible if the overlay is too big, or if any other page content
-   * overlays it.
+   * Adjust the style in which the overlay will be displayed. It might be adjusted
+   * based on its size, or if there's some other element covering all corners of
+   * the overlay.
    *
-   * This function will handle showing or hiding the overlay.
-   * @returns true if the plugin is invisible.
+   * This function will handle adjusting the style of the overlay, but will
+   * not handle hiding it. That is done by setVisibility with the return value
+   * from this function.
+   *
+   * @returns A value from OVERLAY_DISPLAY.
    */
-  computeOverlayDisplayState(plugin, overlay) {
+  computeAndAdjustOverlayDisplay(plugin, overlay) {
     let fallbackType = plugin.pluginFallbackType;
     if (plugin.pluginFallbackTypeOverride !== undefined) {
       fallbackType = plugin.pluginFallbackTypeOverride;
     }
     if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET) {
-      return OVERLAY_DISPLAY_HIDDEN;
+      return OVERLAY_DISPLAY.HIDDEN;
     }
 
     // If the overlay size is 0, we haven't done layout yet. Presume that
     // plugins are visible until we know otherwise.
     if (overlay.scrollWidth == 0) {
-      return OVERLAY_DISPLAY_VISIBLE;
+      return OVERLAY_DISPLAY.FULL;
     }
 
+    let overlayDisplay = OVERLAY_DISPLAY.FULL;
+
     // Is the <object>'s size too small to hold what we want to show?
     let pluginRect = plugin.getBoundingClientRect();
+    let pluginWidth = Math.ceil(pluginRect.width);
+    let pluginHeight = Math.ceil(pluginRect.height);
+
+    // We must set the attributes while here inside this function in order
+    // for a possible re-style to occur, which will make the scrollWidth/Height
+    // checks below correct. Otherwise, we would be requesting e.g. a TINY
+    // overlay here, but the default styling would be used, and that would make
+    // it overflow, causing it to change to BLANK instead of remaining as TINY.
+
+    if (pluginWidth <= 32 || pluginHeight <= 32) {
+      overlay.setAttribute("sizing", "blank");
+      overlayDisplay = OVERLAY_DISPLAY.BLANK;
+    } else if (pluginWidth <= 80 || pluginHeight <= 60) {
+      overlayDisplay = OVERLAY_DISPLAY.TINY;
+      overlay.setAttribute("sizing", "tiny");
+      overlay.setAttribute("notext", "notext");
+    } else if (pluginWidth <= 120 || pluginHeight <= 80) {
+      overlayDisplay = OVERLAY_DISPLAY.REDUCED;
+      overlay.setAttribute("sizing", "reduced");
+      overlay.setAttribute("notext", "notext");
+    } else if (pluginWidth <= 240 || pluginHeight <= 160) {
+      overlayDisplay = OVERLAY_DISPLAY.NOTEXT;
+      overlay.removeAttribute("sizing");
+      overlay.setAttribute("notext", "notext");
+    } else {
+      overlayDisplay = OVERLAY_DISPLAY.FULL;
+      overlay.removeAttribute("sizing");
+      overlay.removeAttribute("notext");
+    }
+
+
     // XXX bug 446693. The text-shadow on the submitted-report text at
     //     the bottom causes scrollHeight to be larger than it should be.
-    let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
-                    (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
+    let overflows = (overlay.scrollWidth > pluginWidth) ||
+                    (overlay.scrollHeight - 5 > pluginHeight);
     if (overflows) {
-      return OVERLAY_DISPLAY_MINIMAL;
+      overlay.setAttribute("sizing", "blank");
+      return OVERLAY_DISPLAY.BLANK;
     }
 
     // Is the plugin covered up by other content so that it is not clickable?
     // Floating point can confuse .elementFromPoint, so inset just a bit
     let left = pluginRect.left + 2;
     let right = pluginRect.right - 2;
     let top = pluginRect.top + 2;
     let bottom = pluginRect.bottom - 2;
@@ -348,21 +389,22 @@ PluginContent.prototype = {
                            .getInterface(Ci.nsIDOMWindowUtils);
 
     for (let [x, y] of points) {
       if (x < 0 || y < 0) {
         continue;
       }
       let el = cwu.elementFromPoint(x, y, true, true);
       if (el === plugin) {
-        return OVERLAY_DISPLAY_VISIBLE;
+        return overlayDisplay;
       }
     }
 
-    return OVERLAY_DISPLAY_HIDDEN;
+    overlay.setAttribute("sizing", "blank");
+    return OVERLAY_DISPLAY.BLANK;
   },
 
   addLinkClickCallback(linkNode, callbackName /* callbackArgs...*/) {
     // XXX just doing (callback)(arg) was giving a same-origin error. bug?
     let self = this;
     let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
     linkNode.addEventListener("click",
                               function(evt) {
@@ -471,17 +513,17 @@ PluginContent.prototype = {
       }
     }
 
     let plugin = event.target;
 
     if (eventType == "PluginPlaceholderReplaced") {
       plugin.removeAttribute("href");
       let overlay = this.getPluginUI(plugin, "main");
-      this.setVisibility(plugin, overlay, OVERLAY_DISPLAY_VISIBLE);
+      this.setVisibility(plugin, overlay, OVERLAY_DISPLAY.FULL);
       let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
                           .getService(Ci.inIDOMUtils);
       // Add psuedo class so our styling will take effect
       inIDOMUtils.addPseudoClassLock(plugin, "-moz-handler-clicktoplay");
       overlay.addEventListener("click", this, true);
       return;
     }
 
@@ -556,20 +598,20 @@ PluginContent.prototype = {
         break;
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
     let overlay = this.getPluginUI(plugin, "main");
     if (eventType != "PluginCrashed") {
       if (overlay != null) {
         this.setVisibility(plugin, overlay,
-                           this.computeOverlayDisplayState(plugin, overlay));
+                           this.computeAndAdjustOverlayDisplay(plugin, overlay));
         let resizeListener = () => {
           this.setVisibility(plugin, overlay,
-            this.computeOverlayDisplayState(plugin, overlay));
+            this.computeAndAdjustOverlayDisplay(plugin, overlay));
           this.updateNotificationUI();
         };
         plugin.addEventListener("overflow", resizeListener);
         plugin.addEventListener("underflow", resizeListener);
       }
     }
 
     let closeIcon = this.getPluginUI(plugin, "closeIcon");
@@ -898,19 +940,19 @@ PluginContent.prototype = {
           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
         continue;
       }
       let overlay = this.getPluginUI(plugin, "main");
       if (!overlay) {
         continue;
       }
-      let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+      let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
       this.setVisibility(plugin, overlay, overlayDisplayState);
-      if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+      if (overlayDisplayState > OVERLAY_DISPLAY.BLANK) {
         actions.delete(info.permissionString);
         if (actions.size == 0) {
           break;
         }
       }
     }
 
     // If there are any items remaining in `actions` now, they are hidden
@@ -1084,31 +1126,31 @@ PluginContent.prototype = {
     this.addLinkClickCallback(helpIcon, "openHelpPage");
 
     let crashText = this.getPluginUI(plugin, "crashedText");
     crashText.textContent = message;
 
     let link = this.getPluginUI(plugin, "reloadLink");
     this.addLinkClickCallback(link, "reloadPage");
 
-    let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+    let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
 
     // Is the <object>'s size too small to hold what we want to show?
-    if (overlayDisplayState != OVERLAY_DISPLAY_VISIBLE) {
+    if (overlayDisplayState != OVERLAY_DISPLAY.FULL) {
       // First try hiding the crash report submission UI.
       statusDiv.removeAttribute("status");
 
-      overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
+      overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
     }
     this.setVisibility(plugin, overlay, overlayDisplayState);
 
     let doc = plugin.ownerDocument;
     let runID = plugin.runID;
 
-    if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
+    if (overlayDisplayState == OVERLAY_DISPLAY.FULL) {
       // If a previous plugin on the page was too small and resulted in adding a
       // notification bar, then remove it because this plugin instance it big
       // enough to serve as in-content notification.
       this.hideNotificationBar("plugin-crashed");
       doc.mozNoPluginCrashedNotification = true;
 
       // Notify others that the crash reporter UI is now ready.
       // Currently, this event is only used by tests.
--- a/toolkit/pluginproblem/content/pluginProblemContent.css
+++ b/toolkit/pluginproblem/content/pluginProblemContent.css
@@ -63,21 +63,16 @@ a .mainBox:focus,
   visibility: hidden;
 }
 
 .visible > .hoverBox,
 .visible > .closeIcon {
   visibility: visible;
 }
 
-.minimal > .hoverBox,
-.minimal > .closeIcon {
-  visibility: hidden;
-}
-
 .mainBox[chromedir="rtl"] {
   direction: rtl;
 }
 
 a .hoverBox,
 :-moz-handler-clicktoplay .hoverBox,
 :-moz-handler-vulnerable-updatable .hoverBox,
 :-moz-handler-vulnerable-no-update .hoverBox {
--- a/toolkit/themes/shared/plugins/pluginProblem.css
+++ b/toolkit/themes/shared/plugins/pluginProblem.css
@@ -210,11 +210,43 @@ a .msgTapToPlay  {
 :-moz-handler-clicktoplay .mainBox {
   background-color: var(--grey-70);
 }
 
 :-moz-handler-clicktoplay .msgClickToPlay {
   font-size: 13px;
 }
 
+:-moz-handler-clicktoplay .mainBox[sizing=blank] .hoverBox {
+  visibility: hidden;
+}
+
+:-moz-handler-clicktoplay .mainBox[sizing=tiny] .icon {
+  width: 16px;
+  height: 16px;
+}
+
+:-moz-handler-clicktoplay .mainBox[sizing=reduced] .icon {
+  width: 32px;
+  height: 32px;
+}
+
+:-moz-handler-clicktoplay .mainBox[sizing=blank] .closeIcon,
+:-moz-handler-clicktoplay .mainBox[sizing=tiny] .closeIcon,
+:-moz-handler-clicktoplay .mainBox[sizing=reduced] .closeIcon {
+  display: none;
+}
+
+:-moz-handler-clicktoplay .mainBox[notext] .msgClickToPlay {
+  display: none;
+}
+
+:-moz-handler-clicktoplay .mainBox[notext] .icon {
+  /* Override a `margin-bottom: 6px` now that .msgClickToPlay
+   * below the icon is no longer shown, in order to achieve
+   * a perfect vertical centering of the icon.
+   */
+  margin-bottom: 0;
+}
+
 :-moz-handler-clicktoplay .hoverBox:active {
   background-color: var(--grey-60);
 }