Bug 455164 - Proof-of-concept of applying transform to the popup. This attempts to fix attachment 338465 but the popup is clipped on the parts that extend outside of the normal menupopup rectangle. draft
authorJared Wein <jwein@mozilla.com>
Thu, 27 Apr 2017 17:55:45 -0400
changeset 569733 05ae32f6f2886808982aa0d1fabb132c936566b4
parent 568509 0f5ba06c4c5959030a05cb852656d854065e2226
child 626292 ad854e84f842cc43a00ff594b9cdbca7140e580b
push id56267
push userbmo:jaws@mozilla.com
push dateThu, 27 Apr 2017 21:56:00 +0000
bugs455164, 338465
milestone55.0a1
Bug 455164 - Proof-of-concept of applying transform to the popup. This attempts to fix attachment 338465 but the popup is clipped on the parts that extend outside of the normal menupopup rectangle. MozReview-Commit-ID: 7sWdHEXYDoD
toolkit/content/widgets/browser.xml
toolkit/content/widgets/remote-browser.xml
toolkit/modules/SelectContentHelper.jsm
toolkit/modules/SelectParentHelper.jsm
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1081,20 +1081,23 @@
             case "Forms:ShowDropDown": {
               if (!this._selectParentHelper) {
                 this._selectParentHelper =
                   Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
               }
 
               let menulist = document.getElementById(this.getAttribute("selectmenulist"));
               menulist.menupopup.style.direction = data.direction;
-              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom,
-                                                data.uaBackgroundColor, data.uaColor,
-                                                data.uaSelectBackgroundColor, data.uaSelectColor,
-                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
+              let sheet =
+                this._selectParentHelper.setupMenuList(menulist, data.uaSelectBackgroundColor,
+                                                       data.uaSelectColor, data.selectBackgroundColor,
+                                                       data.selectColor, data.selectTextShadow,
+                                                       data.selectTransform);
+              this._selectParentHelper.populateChildren(menulist, data.options, data.selectedIndex,
+                                                zoom, data.uaBackgroundColor, data.uaColor, sheet);
               this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
               break;
             }
 
             case "Forms:HideDropDown": {
               if (this._selectParentHelper) {
                 let menulist = document.getElementById(this.getAttribute("selectmenulist"));
                 this._selectParentHelper.hide(menulist, this);
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -470,20 +470,23 @@
                   Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
               }
 
               let menulist = document.getElementById(this.getAttribute("selectmenulist"));
               menulist.menupopup.style.direction = data.direction;
 
               let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
                          this.isSyntheticDocument ? this._fullZoom : this._textZoom;
-              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
-                                                zoom, data.uaBackgroundColor, data.uaColor,
-                                                data.uaSelectBackgroundColor, data.uaSelectColor,
-                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
+              let sheet =
+                this._selectParentHelper.setupMenuList(menulist, data.uaSelectBackgroundColor,
+                                                       data.uaSelectColor, data.selectBackgroundColor,
+                                                       data.selectColor, data.selectTextShadow,
+                                                       data.selectTransform);
+              this._selectParentHelper.populateChildren(menulist, data.options, data.selectedIndex,
+                                                zoom, data.uaBackgroundColor, data.uaColor, sheet);
               this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
               break;
             }
 
             case "FullZoomChange": {
               this._fullZoom = data.value;
               let event = document.createEvent("Events");
               event.initEvent("FullZoomChange", true, false);
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -96,26 +96,28 @@ this.SelectContentHelper.prototype = {
   showDropDown() {
     this.element.openInParentProcess = true;
     let rect = this._getBoundingContentRect();
     DOMUtils.addPseudoClassLock(this.element, ":focus");
     let computedStyles = getComputedStyles(this.element);
     this._selectBackgroundColor = computedStyles.backgroundColor;
     this._selectColor = computedStyles.color;
     this._selectTextShadow = computedStyles.textShadow;
+    this._selectTransform = computedStyles.transform;
     DOMUtils.clearPseudoClassLocks(this.element);
     this.global.sendAsyncMessage("Forms:ShowDropDown", {
       direction: computedStyles.direction,
       isOpenedViaTouch: this.isOpenedViaTouch,
       options: this._buildOptionList(),
       rect,
       selectedIndex: this.element.selectedIndex,
       selectBackgroundColor: this._selectBackgroundColor,
       selectColor: this._selectColor,
       selectTextShadow: this._selectTextShadow,
+      selectTransform: this._selectTransform,
       uaBackgroundColor: this.uaBackgroundColor,
       uaColor: this.uaColor,
       uaSelectBackgroundColor: this.uaSelectBackgroundColor,
       uaSelectColor: this.uaSelectColor
     });
     gOpen = true;
   },
 
@@ -136,23 +138,25 @@ this.SelectContentHelper.prototype = {
     // Technically we might not need to set this pseudo-class
     // during _update() since the element should organically
     // have :focus, though it is here for belt-and-suspenders.
     DOMUtils.addPseudoClassLock(this.element, ":focus");
     let computedStyles = getComputedStyles(this.element);
     this._selectBackgroundColor = computedStyles.backgroundColor;
     this._selectColor = computedStyles.color;
     this._selectTextShadow = computedStyles.textShadow;
+    this._selectTransform = computedStyles.transform;
     DOMUtils.clearPseudoClassLocks(this.element);
     this.global.sendAsyncMessage("Forms:UpdateDropDown", {
       options: this._buildOptionList(),
       selectedIndex: this.element.selectedIndex,
       selectBackgroundColor: this._selectBackgroundColor,
       selectColor: this._selectColor,
       selectTextShadow: this._selectTextShadow,
+      selectTransform: this._selectTransform,
       uaBackgroundColor: this.uaBackgroundColor,
       uaColor: this.uaColor,
       uaSelectBackgroundColor: this.uaSelectBackgroundColor,
       uaSelectColor: this.uaSelectColor
     });
   },
 
   // Determine user agent background-color and color.
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -22,21 +22,22 @@ var currentBrowser = null;
 var currentMenulist = null;
 var currentZoom = 1;
 var closedWithEnter = false;
 var selectRect;
 var customStylingEnabled = Services.prefs.getBoolPref("dom.forms.select.customstyling");
 var usedSelectBackgroundColor;
 
 this.SelectParentHelper = {
-  populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor,
-           uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor, selectColor,
-           selectTextShadow) {
+  setupMenuList(menulist, uaSelectBackgroundColor, uaSelectColor,
+                selectBackgroundColor, selectColor,
+                selectTextShadow, selectTransform) {
     // Clear the current contents of the popup
     menulist.menupopup.textContent = "";
+    currentMenulist = menulist;
     let stylesheet = menulist.querySelector("#ContentSelectDropdownScopedStylesheet");
     if (stylesheet) {
       stylesheet.remove();
     }
 
     let doc = menulist.ownerDocument;
     let sheet;
     if (customStylingEnabled) {
@@ -69,29 +70,38 @@ this.SelectParentHelper = {
       ruleBody += `color: ${selectColor};`;
     }
 
     if (customStylingEnabled &&
         selectTextShadow != "none") {
       ruleBody += `text-shadow: ${selectTextShadow};`;
     }
 
+    if (customStylingEnabled &&
+        selectTransform != "none") {
+      // Set transform-origin to mimic the offset from the
+      // arrow-button on LTR environments.
+      ruleBody += "transform-origin: 16.65px -5px 0;";
+      // Need to set opacity to a value other than 1 to force
+      // transparent windows or else a black box will be drawn
+      // in the area that the popup has been transformed away from.
+      ruleBody += "opacity: .99";
+      ruleBody += `transform: ${selectTransform};`;
+    }
+
     if (ruleBody) {
       sheet.insertRule(`menupopup {
         ${ruleBody}
       }`, 0);
       menulist.menupopup.setAttribute("customoptionstyling", "true");
     } else {
       menulist.menupopup.removeAttribute("customoptionstyling");
     }
 
-    currentZoom = zoom;
-    currentMenulist = menulist;
-    populateChildren(menulist, items, selectedIndex, zoom,
-                     uaBackgroundColor, uaColor, sheet);
+    return sheet;
   },
 
   open(browser, menulist, rect, isOpenedViaTouch) {
     menulist.hidden = false;
     currentBrowser = browser;
     closedWithEnter = false;
     selectRect = rect;
     this._registerListeners(browser, menulist.menupopup);
@@ -200,20 +210,24 @@ this.SelectParentHelper = {
       let selectedIndex = msg.data.selectedIndex;
       let uaBackgroundColor = msg.data.uaBackgroundColor;
       let uaColor = msg.data.uaColor;
       let uaSelectBackgroundColor = msg.data.uaSelectBackgroundColor;
       let uaSelectColor = msg.data.uaSelectColor;
       let selectBackgroundColor = msg.data.selectBackgroundColor;
       let selectColor = msg.data.selectColor;
       let selectTextShadow = msg.data.selectTextShadow;
-      this.populate(currentMenulist, options, selectedIndex,
-                    currentZoom, uaBackgroundColor, uaColor,
-                    uaSelectBackgroundColor, uaSelectColor,
-                    selectBackgroundColor, selectColor, selectTextShadow);
+      let selectTransform = msg.data.selectTransform;
+
+      let sheet =
+        this.setupMenuList(currentMenulist, uaSelectBackgroundColor,
+                           uaSelectColor, selectBackgroundColor, selectColor,
+                           selectTextShadow, selectTransform);
+      this.populateChildren(currentMenulist, options, selectedIndex, currentZoom,
+                            uaBackgroundColor, uaColor, sheet);
 
       // Restore scroll position to what it was prior to the update.
       scrollBox.scrollTop = scrollTop;
     }
   },
 
   _registerListeners(browser, popup) {
     popup.addEventListener("command", this);
@@ -232,24 +246,24 @@ this.SelectParentHelper = {
     popup.removeEventListener("mouseover", this);
     popup.removeEventListener("mouseout", this);
     browser.ownerGlobal.removeEventListener("mouseup", this, true);
     browser.ownerGlobal.removeEventListener("keydown", this, true);
     browser.ownerGlobal.removeEventListener("fullscreen", this, true);
     browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
   },
 
-};
 
-function populateChildren(menulist, options, selectedIndex, zoom,
+  populateChildren(menulist, options, selectedIndex, zoom,
                           uaBackgroundColor, uaColor, sheet,
                           parentElement = null, isGroupDisabled = false,
                           adjustedTextSize = -1, addSearch = true, nthChildIndex = 1) {
   let element = menulist.menupopup;
   let win = element.ownerGlobal;
+  currentZoom = zoom;
 
   // -1 just means we haven't calculated it yet. When we recurse through this function
   // we will pass in adjustedTextSize to save on recalculations.
   if (adjustedTextSize == -1) {
     // Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
     // the popup's text size is matched with the content's. We can't just apply a CSS transform
     // here as the popup's preferred size is calculated pre-transform.
     let textSize = win.getComputedStyle(element).getPropertyValue("font-size");
@@ -313,17 +327,17 @@ function populateChildren(menulist, opti
     // A disabled optgroup disables all of its child options.
     let isDisabled = isGroupDisabled || option.disabled;
     if (isDisabled) {
       item.setAttribute("disabled", "true");
     }
 
     if (isOptGroup) {
       nthChildIndex =
-        populateChildren(menulist, option.children, selectedIndex, zoom,
+        this.populateChildren(menulist, option.children, selectedIndex, zoom,
                          uaBackgroundColor, uaColor, sheet,
                          item, isDisabled, adjustedTextSize, false, nthChildIndex);
     } else {
       if (option.index == selectedIndex) {
         // We expect the parent element of the popup to be a <xul:menulist> that
         // has the popuponly attribute set to "true". This is necessary in order
         // for a <xul:menupopup> to act like a proper <html:select> dropdown, as
         // the <xul:menulist> does things like remember state and set the
@@ -394,16 +408,17 @@ function populateChildren(menulist, opti
       event.preventDefault();
     }, true);
 
     element.insertBefore(searchbox, element.childNodes[0]);
   }
 
   return nthChildIndex;
 }
+};
 
 function onSearchInput() {
   let searchObj = this;
 
   // Get input from search field, set to all lower case for comparison
   let input = searchObj.value.toLowerCase();
   // Get all items in dropdown (could be options or optgroups)
   let menupopup = searchObj.parentElement;