Bug 790483 - Click-to-play plugins fail to show placeholder after resizing. r=jaws, a=lsblakk
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Tue, 17 Sep 2013 23:23:05 +0200
changeset 160760 618d9395bfbdfb81b461b933aa520b5fcdb5c02b
parent 160759 9d6d3d4f27279de9bb28d5ce75b9673430963be7
child 160761 f081b1e1b442910bdf1d60b502a404d510a12028
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, lsblakk
bugs790483
milestone26.0a2
Bug 790483 - Click-to-play plugins fail to show placeholder after resizing. r=jaws, a=lsblakk
browser/base/content/browser-plugins.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_CTP_resize.js
browser/base/content/test/head.js
browser/base/content/test/plugin_small.html
toolkit/mozapps/plugins/content/pluginProblemBinding.css
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -315,18 +315,31 @@ var gPluginHandler = {
       case "PluginRemoved":
         shouldShowNotification = true;
         break;
     }
 
     // Hide the in-content UI if it's too big. The crashed plugin handler already did this.
     if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
       let overlay = this.getPluginUI(plugin, "main");
-      if (overlay != null && this.isTooSmall(plugin, overlay))
-        overlay.style.visibility = "hidden";
+      if (overlay != null) {
+        if (!this.isTooSmall(plugin, overlay))
+          overlay.style.visibility = "visible";
+
+        plugin.addEventListener("overflow", function(event) {
+          overlay.style.visibility = "hidden";
+        });
+        plugin.addEventListener("underflow", function(event) {
+          // this is triggered if only one dimension underflows,
+          // the other dimension might still overflow
+          if (!gPluginHandler.isTooSmall(plugin, overlay)) {
+            overlay.style.visibility = "visible";
+          }
+        });
+      }
     }
 
     // Only show the notification after we've done the isTooSmall check, so
     // that the notification can decide whether to show the "alert" icon
     if (shouldShowNotification) {
       this._showClickToPlayNotification(browser);
     }
   },
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -188,16 +188,17 @@ MOCHITEST_BROWSER_FILES = \
                  browser_clearplugindata_noage.html \
                  browser_clearplugindata.html \
                  browser_clearplugindata.js \
                  browser_contentAreaClick.js \
                  browser_contextSearchTabPosition.js \
                  browser_CTP_drag_drop.js \
                  browser_CTP_data_urls.js \
                  browser_CTP_nonplugins.js \
+                 browser_CTP_resize.js \
                  browser_ctrlTab.js \
                  browser_customize_popupNotification.js \
                  browser_customize.js \
                  browser_disablechrome.js \
                  browser_discovery.js \
                  browser_duplicateIDs.js \
                  browser_findbarClose.js \
                  browser_fullscreen-window-open.js \
@@ -312,16 +313,17 @@ MOCHITEST_BROWSER_FILES = \
                  plugin_bug749455.html \
                  plugin_bug752516.html \
                  plugin_bug787619.html \
                  plugin_bug797677.html \
                  plugin_bug820497.html \
                  plugin_clickToPlayAllow.html \
                  plugin_clickToPlayDeny.html \
                  plugin_hidden_to_visible.html \
+                 plugin_small.html \
                  plugin_test.html \
                  plugin_test2.html \
                  plugin_test3.html \
                  plugin_two_types.html \
                  plugin_data_url.html \
                  plugin_unknown.html \
                  POSTSearchEngine.xml \
                  print_postdata.sjs \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_CTP_resize.js
@@ -0,0 +1,162 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+  this.url = url;
+  this.opencallback = opencallback;
+  this.closecallback = closecallback;
+
+  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+  url: null,
+  opencallback: null,
+  closecallback: null,
+  tab: null,
+  browser: null,
+
+  handleEvent: function(event) {
+    if (event.type == "TabOpen") {
+      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+      this.tab = event.originalTarget;
+      this.browser = this.tab.linkedBrowser;
+      gBrowser.addEventListener("pageshow", this, false);
+    } else if (event.type == "pageshow") {
+      if (event.target.location.href != this.url)
+        return;
+      gBrowser.removeEventListener("pageshow", this, false);
+      this.tab.addEventListener("TabClose", this, false);
+      var url = this.browser.contentDocument.location.href;
+      is(url, this.url, "Should have opened the correct tab");
+      this.opencallback(this.tab, this.browser.contentWindow);
+    } else if (event.type == "TabClose") {
+      if (event.originalTarget != this.tab)
+        return;
+      this.tab.removeEventListener("TabClose", this, false);
+      this.opencallback = null;
+      this.tab = null;
+      this.browser = null;
+      // Let the window close complete
+      executeSoon(this.closecallback);
+      this.closecallback = null;
+    }
+  }
+};
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+  });
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+  var newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_small.html");
+}
+
+function finishTest() {
+  clearAllPluginPermissions();
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function pageLoad() {
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states
+  executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+  gNextTest = nextTest;
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+  return function() {
+    let doc = gTestBrowser.contentDocument;
+    let elems = doc.getElementsByTagName('embed');
+    if (elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    elems[0].clientTop;
+    executeSoon(func);
+  };
+}
+
+// Test that the overlay is hidden for "small" plugin elements and is shown
+// once they are resized to a size that can hold the overlay
+
+function test1() {
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let doc = gTestBrowser.contentDocument;
+  let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+  ok(overlay, "Test 1, Should have an overlay.");
+  is(window.getComputedStyle(overlay).visibility, 'hidden', "Test 1, Overlay should be hidden");
+
+  plugin.style.width = '300px';
+  executeSoon(test2);
+}
+
+function test2() {
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let doc = gTestBrowser.contentDocument;
+  let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+  ok(overlay, "Test 2, Should have an overlay.");
+  is(window.getComputedStyle(overlay).visibility, 'hidden', "Test 2, Overlay should be hidden");
+
+  plugin.style.height = '300px';
+  let condition = () => window.getComputedStyle(overlay).visibility == 'visible';
+  waitForCondition(condition, test3, "Test 2, Waited too long for overlay to become visible");
+}
+
+function test3() {
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let doc = gTestBrowser.contentDocument;
+  let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+  ok(overlay, "Test 3, Should have an overlay.");
+  is(window.getComputedStyle(overlay).visibility, 'visible', "Test 3, Overlay should be visible");
+
+  plugin.style.width = '10px';
+  plugin.style.height = '10px';
+  let condition = () => window.getComputedStyle(overlay).visibility == 'hidden';
+  waitForCondition(condition, test4, "Test 3, Waited too long for overlay to become hidden");
+}
+
+function test4() {
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let doc = gTestBrowser.contentDocument;
+  let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+  ok(overlay, "Test 4, Should have an overlay.");
+  is(window.getComputedStyle(overlay).visibility, 'hidden', "Test 4, Overlay should be hidden");
+
+  clearAllPluginPermissions();
+  finishTest();
+}
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -106,16 +106,28 @@ function getTestPlugin(aName) {
   for (var i = 0; i < tags.length; i++) {
     if (tags[i].name == pluginName)
       return tags[i];
   }
   ok(false, "Unable to find plugin");
   return null;
 }
 
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+  var plugin = getTestPlugin(pluginName);
+  var oldEnabledState = plugin.enabledState;
+  plugin.enabledState = newEnabledState;
+  SimpleTest.registerCleanupFunction(function() {
+    getTestPlugin(pluginName).enabledState = oldEnabledState;
+  });
+}
+
 // after a test is done using the plugin doorhanger, we should just clear
 // any permissions that may have crept in
 function clearAllPluginPermissions() {
   let perms = Services.perms.enumerator;
   while (perms.hasMoreElements()) {
     let perm = perms.getNext();
     if (perm.type.startsWith('plugin')) {
       Services.perms.remove(perm.host, perm.type);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugin_small.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 10px; height: 10px" type="application/x-test">
+</body>
+</html>
--- a/toolkit/mozapps/plugins/content/pluginProblemBinding.css
+++ b/toolkit/mozapps/plugins/content/pluginProblemBinding.css
@@ -21,12 +21,15 @@ applet:-moz-handler-vulnerable-no-update
 object:-moz-handler-disabled,
 object:-moz-handler-blocked,
 object:-moz-handler-crashed,
 object:-moz-handler-clicktoplay,
 object:-moz-handler-playpreview,
 object:-moz-handler-vulnerable-updatable,
 object:-moz-handler-vulnerable-no-update {
     display: inline-block;
+    /* Initialize the overlay with visibility:hidden to prevent flickering if
+     * the plugin is too small to show the overlay */
+    visibility: hidden;
     overflow: hidden;
     opacity: 1 !important;
     -moz-binding: url('chrome://mozapps/content/plugins/pluginProblem.xml#pluginProblem') !important;
 }