Bug 1269081 - Only apply Firefox's default styles if the add-on author sets "browser_style" to true in the manifest. r=kmag, a=lizzard
authorBlake Winton <bwinton@latte.ca>
Mon, 02 May 2016 10:59:36 -0400
changeset 332810 00a32da7e4d6e85fdcf3618fb757d7cd8df4eefb
parent 332809 fb681e961325a5fd7d98c903f16dc024e17e49b5
child 332811 2c40e21dafaa718d667bb2718f0f36f402b67614
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag, lizzard
bugs1269081
milestone48.0a2
Bug 1269081 - Only apply Firefox's default styles if the add-on author sets "browser_style" to true in the manifest. r=kmag, a=lizzard MozReview-Commit-ID: JjH0sCkh1U7
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-utils.js
browser/components/extensions/extension.css
browser/components/extensions/schemas/browser_action.json
browser/components/extensions/schemas/page_action.json
browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -33,16 +33,22 @@ function BrowserAction(options, extensio
     enabled: true,
     title: options.default_title || extension.name,
     badgeText: "",
     badgeBackgroundColor: null,
     icon: IconDetails.normalize({path: options.default_icon}, extension),
     popup: options.default_popup || "",
   };
 
+  this.browserStyle = options.browser_style || false;
+  if (options.browser_style === null) {
+    this.extension.logger.warn("Please specify whether you want browser_style " +
+                               "or not in your browser_action options.");
+  }
+
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                    extension);
 
   EventEmitter.decorate(this);
 }
 
 BrowserAction.prototype = {
   build() {
@@ -86,17 +92,17 @@ BrowserAction.prototype = {
         this.tabManager.addActiveTabPermission(tab);
 
         // If the widget has a popup URL defined, we open a popup, but do not
         // dispatch a click event to the extension.
         // If it has no popup URL defined, we dispatch a click event, but do not
         // open a popup.
         if (popupURL) {
           try {
-            new ViewPopup(this.extension, event.target, popupURL);
+            new ViewPopup(this.extension, event.target, popupURL, this.browserStyle);
           } catch (e) {
             Cu.reportError(e);
             event.preventDefault();
           }
         } else {
           // This isn't not a hack, but it seems to provide the correct behavior
           // with the fewest complications.
           event.preventDefault();
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -21,16 +21,22 @@ function PageAction(options, extension) 
 
   this.defaults = {
     show: false,
     title: options.default_title || extension.name,
     icon: IconDetails.normalize({path: options.default_icon}, extension),
     popup: options.default_popup || "",
   };
 
+  this.browserStyle = options.browser_style || false;
+  if (options.browser_style === null) {
+    this.extension.logger.warn("Please specify whether you want browser_style " +
+                               "or not in your page_action options.");
+  }
+
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                    extension);
 
   this.tabContext.on("location-change", this.handleLocationChange.bind(this)); // eslint-disable-line mozilla/balanced-listeners
 
   // WeakMap[ChromeWindow -> <xul:image>]
   this.buttons = new WeakMap();
 
@@ -147,17 +153,18 @@ PageAction.prototype = {
 
     this.tabManager.addActiveTabPermission(tab);
 
     // If the widget has a popup URL defined, we open a popup, but do not
     // dispatch a click event to the extension.
     // If it has no popup URL defined, we dispatch a click event, but do not
     // open a popup.
     if (popupURL) {
-      new PanelPopup(this.extension, this.getButton(window), popupURL);
+      new PanelPopup(this.extension, this.getButton(window), popupURL,
+                     this.browserStyle);
     } else {
       this.emit("click", tab);
     }
   },
 
   handleLocationChange(eventType, tab, fromBrowse) {
     if (fromBrowse) {
       this.tabContext.clear(tab);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -154,26 +154,27 @@ XPCOMUtils.defineLazyGetter(global, "sty
     let macStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
                                                        styleSheetService.AGENT_SHEET);
     stylesheets.push(macStyleSheet);
   }
   return stylesheets;
 });
 
 class BasePopup {
-  constructor(extension, viewNode, popupURL) {
+  constructor(extension, viewNode, popupURL, browserStyle) {
     let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
 
     Services.scriptSecurityManager.checkLoadURIWithPrincipal(
       extension.principal, popupURI,
       Services.scriptSecurityManager.DISALLOW_SCRIPT);
 
     this.extension = extension;
     this.popupURI = popupURI;
     this.viewNode = viewNode;
+    this.browserStyle = browserStyle;
     this.window = viewNode.ownerDocument.defaultView;
 
     this.contentReady = new Promise(resolve => {
       this._resolveContentReady = resolve;
     });
 
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
 
@@ -207,17 +208,17 @@ class BasePopup {
 
   handleEvent(event) {
     switch (event.type) {
       case this.DESTROY_EVENT:
         this.destroy();
         break;
 
       case "DOMWindowCreated":
-        if (event.target === this.browser.contentDocument) {
+        if (this.browserStyle && event.target === this.browser.contentDocument) {
           let winUtils = this.browser.contentWindow
               .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
           for (let stylesheet of global.stylesheets) {
             winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
           }
         }
         break;
 
@@ -325,28 +326,28 @@ class BasePopup {
     this.browser.style.width = `${width}px`;
     this.browser.style.height = `${height}px`;
 
     this._resolveContentReady();
   }
 }
 
 global.PanelPopup = class PanelPopup extends BasePopup {
-  constructor(extension, imageNode, popupURL) {
+  constructor(extension, imageNode, popupURL, browserStyle) {
     let document = imageNode.ownerDocument;
 
     let panel = document.createElement("panel");
     panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
     panel.setAttribute("class", "browser-extension-panel");
     panel.setAttribute("type", "arrow");
     panel.setAttribute("role", "group");
 
     document.getElementById("mainPopupSet").appendChild(panel);
 
-    super(extension, panel, popupURL);
+    super(extension, panel, popupURL, browserStyle);
 
     this.contentReady.then(() => {
       panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
     });
   }
 
   get DESTROY_EVENT() {
     return "popuphidden";
--- a/browser/components/extensions/extension.css
+++ b/browser/components/extensions/extension.css
@@ -133,47 +133,158 @@ button.default.pressed {
 
 button.default.focused {
   border-color: #fff;
 }
 
 /* Radio Buttons */
 .radioItem {
   margin-bottom: 6px;
+  text-align: left;
+}
+
+input[type="radio"] {
+  display: none;
 }
 
 input[type="radio"] + label {
   -moz-user-select: none;
-  text-align: left;
+}
+
+input[type="radio"] + label::before {
+  background-color: #fff;
+  background-position: center;
+  border: 1px solid #b1b1b1;
+  border-radius: 50%;
+  content: "";
+  display: inline-block;
+  height: 16px;
+  margin-right: 6px;
+  vertical-align: text-top;
+  width: 16px;
+}
+
+input[type="radio"]:hover + label::before,
+.radioItem.hover input[type="radio"]:not(active) + label::before {
+  background-color: #fbfbfb;
+  border-color: #b1b1b1;
+}
+
+input[type="radio"]:hover:active + label::before,
+.radioItem.pressed input[type="radio"]:not(active) + label::before {
+  background-color: #ebebeb;
+  border-color: #858585;
+}
+
+input[type="radio"]:checked + label::before {
+  background-color: #0996f8;
+  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iNCIgZmlsbD0iI2ZmZiIgLz4KPC9zdmc+Cg==);
+  border-color: #0670cc;
+}
+
+input[type="radio"]:checked:hover + label::before,
+.radioItem.hover input[type="radio"]:checked:not(active) + label::before {
+  background-color: #0670cc;
+  border-color: #005bab;
+}
+
+input[type="radio"]:checked:hover:active + label::before,
+.radioItem.pressed input[type="radio"]:checked:not(active) + label::before {
+  background-color: #005bab;
+  border-color: #004480;
 }
 
 .radioItem.disabled input[type="radio"] + label,
 .radioItem.disabled input[type="radio"]:hover + label,
 .radioItem.disabled input[type="radio"]:hover:active + label {
   color: #999;
   opacity: .5;
 }
 
+.radioItem.focused input[type="radio"] + label::before {
+  border-color: #0996f8;
+  box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+.radioItem.focused input[type="radio"]:checked + label::before {
+  border-color: #fff;
+}
+
 /* Checkboxes */
 .checkboxItem {
   margin-bottom: 6px;
+  text-align: left;
+}
+
+input[type="checkbox"] {
+  display: none;
 }
 
 input[type="checkbox"] + label {
   -moz-user-select: none;
-  text-align: left;
+}
+
+input[type="checkbox"] + label::before {
+  background-color: #fff;
+  background-position: center;
+  border: 1px solid #b1b1b1;
+  content: "";
+  display: inline-block;
+  height: 16px;
+  margin-right: 6px;
+  vertical-align: text-top;
+  width: 16px;
+}
+
+input[type="checkbox"]:hover + label::before,
+.checkboxItem.hover input[type="checkbox"]:not(active) + label::before {
+  background-color: #fbfbfb;
+  border-color: #b1b1b1;
+}
+
+input[type="checkbox"]:hover:active + label::before,
+.checkboxItem.pressed input[type="checkbox"]:not(active) + label::before {
+  background-color: #ebebeb;
+  border-color: #858585;
+}
+
+input[type="checkbox"]:checked + label::before {
+  background-color: #0996f8;
+  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNNy43LDEyLjkgQzcuNCwxMy4zIDYuOCwxMy40IDYuNCwxMyBMMy4yLDkuOCBDMi44LDkuNCAyLjgsOC42IDMuMiw4LjIgQzMuNiw3LjggNC40LDcuOCA0LjgsOC4yIEw2LjksMTAuMyBMMTEuMSw0LjQgQzExLjUsMy45IDEyLjIsMy44IDEyLjcsNC4xIEMxMy4yLDQuNSAxMy4zLDUuMiAxMyw1LjcgTDcuNywxMi45IEw3LjcsMTIuOSBaIiBmaWxsPSIjZmZmIiAvPgo8L3N2Zz4K);
+  border-color: #0670cc;
+}
+
+input[type="checkbox"]:checked:hover + label::before,
+.checkboxItem.hover input[type="checkbox"]:checked:not(active) + label::before {
+  background-color: #0670cc;
+  border-color: #005bab;
+}
+
+input[type="checkbox"]:checked:hover:active + label::before,
+.checkboxItem.pressed input[type="checkbox"]:checked:not(active) + label::before {
+  background-color: #005bab;
+  border-color: #004480;
 }
 
 .checkboxItem.disabled input[type="checkbox"] + label,
 .checkboxItem.disabled input[type="checkbox"]:hover + label,
 .checkboxItem.disabled input[type="checkbox"]:hover:active + label {
   color: #999;
   opacity: .5;
 }
 
+.checkboxItem.focused input[type="checkbox"] + label::before {
+  border-color: #0996f8;
+  box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+.checkboxItem.focused input[type="checkbox"]:checked + label::before {
+  border-color: #fff;
+}
+
 /* Expander Button */
 button.expander {
   background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==);
   background-position: center;
   background-repeat: no-repeat;
   height: 24px;
   padding: 0;
   width: 24px;
@@ -349,17 +460,17 @@ textarea:focus:hover {
 }
 
 .panel-formElements-item:last-child {
   margin-bottom: 0;
 }
 
 .panel-formElements-item label {
   flex-shrink: 0;
-  margin: 0 6px;
+  margin-right: 6px;
   text-align: right;
 }
 
 .panel-formElements-item input[type="text"],
 .panel-formElements-item select {
   flex-grow: 1;
 }
 
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -22,16 +22,20 @@
                 "$ref": "IconPath",
                 "optional": true
               },
               "default_popup": {
                 "type": "string",
                 "format": "relativeUrl",
                 "optional": true,
                 "preprocess": "localize"
+              },
+              "browser_style": {
+                "type": "boolean",
+                "optional": true
               }
             },
             "optional": true
           }
         }
       }
     ]
   },
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -22,16 +22,20 @@
                 "$ref": "IconPath",
                 "optional": true
               },
               "default_popup": {
                 "type": "string",
                 "format": "relativeUrl",
                 "optional": true,
                 "preprocess": "localize"
+              },
+              "browser_style": {
+                "type": "boolean",
+                "optional": true
               }
             },
             "optional": true
           }
         }
       }
     ]
   },
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -7,28 +7,27 @@ function* testInArea(area) {
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "background": {
         "page": "data/background.html",
       },
       "browser_action": {
         "default_popup": "popup-a.html",
+        "browser_style": true,
       },
     },
 
     files: {
       "popup-a.html": scriptPage("popup-a.js"),
       "popup-a.js": function() {
         window.onload = () => {
-          if (window.getComputedStyle(document.body).backgroundColor == "rgb(252, 252, 252)") {
-            browser.runtime.sendMessage("from-popup-a");
-          } else {
-            browser.runtime.sendMessage("popup-a-failed-style-check");
-          }
+          let background = window.getComputedStyle(document.body).backgroundColor;
+          browser.test.assertEq("rgb(252, 252, 252)", background);
+          browser.runtime.sendMessage("from-popup-a");
         };
         browser.runtime.onMessage.addListener(msg => {
           if (msg == "close-popup") {
             window.close();
           }
         });
       },
 
@@ -75,18 +74,16 @@ function* testInArea(area) {
         sendClick = ({expectEvent, expectPopup, runNextTest}) => {
           expect = {event: expectEvent, popup: expectPopup, runNextTest};
           browser.test.sendMessage("send-click");
         };
 
         browser.runtime.onMessage.addListener(msg => {
           if (msg == "close-popup") {
             return;
-          } else if (msg == "popup-a-failed-style-check") {
-            browser.test.fail("popup failed style check");
           } else if (expect.popup) {
             browser.test.assertEq(msg, `from-popup-${expect.popup}`,
                                   "expected popup opened");
           } else {
             browser.test.fail(`unexpected popup: ${msg}`);
           }
 
           expect.popup = null;
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -13,17 +13,21 @@ add_task(function* testPageActionPopup()
       "page_action": {
         "default_popup": "popup-a.html",
       },
     },
 
     files: {
       "popup-a.html": scriptPage("popup-a.js"),
       "popup-a.js": function() {
-        browser.runtime.sendMessage("from-popup-a");
+        window.onload = () => {
+          let background = window.getComputedStyle(document.body).backgroundColor;
+          browser.test.assertEq("transparent", background);
+          browser.runtime.sendMessage("from-popup-a");
+        };
         browser.runtime.onMessage.addListener(msg => {
           if (msg == "close-popup") {
             window.close();
           }
         });
       },
 
       "data/popup-b.html": scriptPage("popup-b.js"),