Bug 1302504 - use mozbrowser to add support for options_ui on Android r?mixedpuppy draft
authorMatthew Wein <mwein@mozilla.com>
Fri, 07 Jul 2017 15:03:33 -0400 (2017-07-07)
changeset 605518 d16d6806741f0e6e20a0190f0a2c06f46d401f78
parent 605370 5eeee16f1659ca1f7c4e28e5834d051eb060eafc
child 636517 42fa08839d08de12e578889d4c253ee624bc3dd4
push id67436
push usermwein@mozilla.com
push dateFri, 07 Jul 2017 21:35:43 +0000 (2017-07-07)
reviewersmixedpuppy
bugs1302504
milestone56.0a1
Bug 1302504 - use mozbrowser to add support for options_ui on Android r?mixedpuppy MozReview-Commit-ID: IsTOC3pNxJP
mobile/android/chrome/content/aboutAddons.js
mobile/android/chrome/content/aboutAddons.xhtml
mobile/android/locales/en-US/chrome/aboutAddons.dtd
mobile/android/themes/core/aboutAddons.css
toolkit/mozapps/extensions/content/extensions.js
--- a/mobile/android/chrome/content/aboutAddons.js
+++ b/mobile/android/chrome/content/aboutAddons.js
@@ -98,17 +98,17 @@ var ContextMenus = {
 }
 
 function init() {
   window.addEventListener("popstate", onPopState);
 
   AddonManager.addInstallListener(Addons);
   AddonManager.addAddonListener(Addons);
   Addons.init();
-  showList();
+  showAddons();
   ContextMenus.init();
 }
 
 
 function uninit() {
   AddonManager.removeInstallListener(Addons);
   AddonManager.removeAddonListener(Addons);
 }
@@ -123,29 +123,38 @@ function onPopState(aEvent) {
   if (aEvent.state) {
     // Show the detail page for an addon
     Addons.showDetails(Addons._getElementForAddon(aEvent.state.id));
   } else {
     // Clear any previous detail addon
     let detailItem = document.querySelector("#addons-details > .addon-item");
     detailItem.addon = null;
 
-    showList();
+    showAddons();
   }
 }
 
-function showList() {
-  // Hide the detail page and show the list
+function showAddons() {
+  // Hide the addon options and show the addons list
   let details = document.querySelector("#addons-details");
-  details.style.display = "none";
+  details.classList.add("hidden");
   let list = document.querySelector("#addons-list");
-  list.style.display = "block";
+  list.classList.remove("hidden");
   document.documentElement.removeAttribute("details");
 }
 
+function showAddonOptions() {
+  // Hide the addon list and show the addon options
+  let list = document.querySelector("#addons-list");
+  list.classList.add("hidden");
+  let details = document.querySelector("#addons-details");
+  details.classList.remove("hidden");
+  document.documentElement.setAttribute("details", "true");
+}
+
 var Addons = {
   _restartCount: 0,
 
   _createItem: function _createItem(aAddon) {
     let outer = document.createElement("div");
     outer.setAttribute("addonID", aAddon.id);
     outer.className = "addon-item list-item";
     outer.setAttribute("role", "button");
@@ -222,21 +231,16 @@ var Addons = {
   _createItemForAddon: function _createItemForAddon(aAddon) {
     let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION);
     let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
     let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0;
     let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0;
 
     let optionsURL = aAddon.optionsURL || "";
 
-    if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
-      // Ignore OPTIONS_TYPE_INLINE_BROWSER until support is added in bug 1302504.
-      optionsURL = "";
-    }
-
     let blocked = "";
     switch(aAddon.blocklistState) {
       case Ci.nsIBlocklistService.STATE_BLOCKED:
         blocked = "blocked";
         break;
       case Ci.nsIBlocklistService.STATE_SOFTBLOCKED:
         blocked = "softBlocked";
         break;
@@ -304,32 +308,16 @@ var Addons = {
     if (aOperations & AddonManager.PENDING_ENABLE)
       return "needs-enable";
     if (aOperations & AddonManager.PENDING_DISABLE)
       return "needs-disable";
     return "";
   },
 
   showDetails: function showDetails(aListItem) {
-    // This function removes and returns the text content of aNode without
-    // removing any child elements. Removing the text nodes ensures any XBL
-    // bindings apply properly.
-    function stripTextNodes(aNode) {
-      var text = "";
-      for (var i = 0; i < aNode.childNodes.length; i++) {
-        if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
-          text += aNode.childNodes[i].textContent;
-          aNode.removeChild(aNode.childNodes[i--]);
-        } else {
-          text += stripTextNodes(aNode.childNodes[i]);
-        }
-      }
-      return text;
-    }
-
     let detailItem = document.querySelector("#addons-details > .addon-item");
     detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled"));
     detailItem.setAttribute("isUnsigned", aListItem.getAttribute("isUnsigned"));
     detailItem.setAttribute("opType", aListItem.getAttribute("opType"));
     detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL"));
     let addon = detailItem.addon = aListItem.addon;
 
     let favicon = document.querySelector("#addons-details > .addon-item .icon");
@@ -350,61 +338,99 @@ var Addons = {
 
     let uninstallBtn = document.getElementById("uninstall-btn");
     if (addon.scope == AddonManager.SCOPE_APPLICATION) {
       uninstallBtn.setAttribute("disabled", "true");
     } else {
       uninstallBtn.removeAttribute("disabled");
     }
 
-    let box = document.querySelector("#addons-details > .addon-item .options-box");
-    box.innerHTML = "";
+    let addonItem = document.querySelector("#addons-details > .addon-item");
+    let optionsBox = addonItem.querySelector(".options-box");
+    let optionsURL = aListItem.getAttribute("optionsURL");
+    switch (parseInt(addon.optionsType)) {
+      case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
+        this.createWebExtensionOptions(optionsBox, optionsURL, addon.optionsBrowserStyle);
+        break;
+      case AddonManager.OPTIONS_TYPE_INLINE:
+        this.createInlineOptions(optionsBox, optionsURL);
+        break;
+    }
+
+    showAddonOptions();
+  },
+
+  createWebExtensionOptions: async function(destination, optionsURL, browserStyle) {
+    destination.innerHTML = "";
 
-    // Retrieve the extensions preferences
+    let frame = document.createElement("iframe");
+    frame.setAttribute("id", "addon-options");
+    frame.setAttribute("mozbrowser", "true");
+    destination.appendChild(frame);
+    // Loading the URL this way prevents the native back
+    // button from applying to the iframe.
+    frame.contentWindow.location.replace(optionsURL);
+  },
+
+  createInlineOptions(destination, optionsURL) {
+    destination.innerHTML = "";
+
+    // This function removes and returns the text content of aNode without
+    // removing any child elements. Removing the text nodes ensures any XBL
+    // bindings apply properly.
+    function stripTextNodes(aNode) {
+      var text = "";
+      for (var i = 0; i < aNode.childNodes.length; i++) {
+        if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
+          text += aNode.childNodes[i].textContent;
+          aNode.removeChild(aNode.childNodes[i--]);
+        } else {
+          text += stripTextNodes(aNode.childNodes[i]);
+        }
+      }
+      return text;
+    }
+
     try {
-      let optionsURL = aListItem.getAttribute("optionsURL");
       let xhr = new XMLHttpRequest();
       xhr.open("GET", optionsURL, true);
       xhr.onload = function(e) {
         if (xhr.responseXML) {
           // Only allow <setting> for now
           let settings = xhr.responseXML.querySelectorAll(":root > setting");
           if (settings.length > 0) {
             for (let i = 0; i < settings.length; i++) {
               var setting = settings[i];
               var desc = stripTextNodes(setting).trim();
               if (!setting.hasAttribute("desc")) {
                 setting.setAttribute("desc", desc);
               }
-              box.appendChild(setting);
+              destination.appendChild(setting);
             }
             // Send an event so add-ons can prepopulate any non-preference based
             // settings
             let event = document.createEvent("Events");
             event.initEvent("AddonOptionsLoad", true, false);
             window.dispatchEvent(event);
           } else {
             // Reset the options URL to hide the options header if there are no
             // valid settings to show.
+            let detailItem = document.querySelector("#addons-details > .addon-item");
             detailItem.setAttribute("optionsURL", "");
           }
 
           // Also send a notification to match the behavior of desktop Firefox
           let id = aListItem.getAttribute("addonID");
           Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
         }
       }
       xhr.send(null);
-    } catch (e) { }
-
-    let list = document.querySelector("#addons-list");
-    list.style.display = "none";
-    let details = document.querySelector("#addons-details");
-    details.style.display = "block";
-    document.documentElement.setAttribute("details", "true");
+    } catch (e) {
+      Cu.reportError(e);
+    }
   },
 
   setEnabled: function setEnabled(aValue, aAddon) {
     let detailItem = document.querySelector("#addons-details > .addon-item");
     let addon = aAddon || detailItem.addon;
     if (!addon)
       return;
 
--- a/mobile/android/chrome/content/aboutAddons.xhtml
+++ b/mobile/android/chrome/content/aboutAddons.xhtml
@@ -28,28 +28,27 @@
     <menuitem id="contextmenu-enable" label="&addonAction.enable;"></menuitem>
     <menuitem id="contextmenu-disable" label="&addonAction.disable;" ></menuitem>
     <menuitem id="contextmenu-uninstall" label="&addonAction.uninstall;" ></menuitem>
   </menu>
 
   <div id="addons-header" class="header">
     <div>&aboutAddons.header2;</div>
   </div>
-  <div id="addons-list" class="list">
+  <div id="addons-list" class="list hidden">
   </div>
 
-  <div id="addons-details" class="list">
+  <div id="addons-details" class="list hidden">
     <div class="addon-item list-item">
       <img class="icon"/>
       <div class="inner">
         <div class="details">
           <div class="title"></div><div class="version"></div>
         </div>
         <div class="description-full"></div>
-        <div class="options-header">&aboutAddons.options;</div>
         <div class="options-box"></div>
       </div>
       <div class="warn-unsigned">&addonUnsigned.message; <a id="unsigned-learn-more">&addonUnsigned.learnMore;</a></div>
       <div class="status status-uninstalled show-on-uninstall"></div>
       <div class="buttons">
         <button id="enable-btn" class="show-on-disable hide-on-enable hide-on-uninstall" >&addonAction.enable;</button>
         <button id="disable-btn" class="show-on-enable hide-on-disable hide-on-uninstall" >&addonAction.disable;</button>
         <button id="uninstall-btn" class="hide-on-uninstall" >&addonAction.uninstall;</button>
--- a/mobile/android/locales/en-US/chrome/aboutAddons.dtd
+++ b/mobile/android/locales/en-US/chrome/aboutAddons.dtd
@@ -1,15 +1,14 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY aboutAddons.title2                     "Add-ons">
 <!ENTITY aboutAddons.header2                    "Your Add-ons">
-<!ENTITY aboutAddons.options                    "Options">
 
 <!ENTITY addonAction.enable                     "Enable">
 <!ENTITY addonAction.disable                    "Disable">
 <!ENTITY addonAction.uninstall                  "Uninstall">
 <!ENTITY addonAction.undo                       "Undo">
 
 <!ENTITY addonUnsigned.message                  "This add-on could not be verified by &brandShortName;.">
 <!ENTITY addonUnsigned.learnMore                "Learn more">
--- a/mobile/android/themes/core/aboutAddons.css
+++ b/mobile/android/themes/core/aboutAddons.css
@@ -3,16 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import "defines.css";
 
 html[details] {
   background-color: var(--color_about_item);
 }
 
+iframe {
+  padding: 0;
+  margin: 0;
+  border:none;
+}
+
 a {
   text-decoration: none;
   color: #0096DD;
 }
 
 a:active {
   color: #0082C6;
 }
@@ -20,16 +26,20 @@ a:active {
 .details {
   width: 100%;
 }
 
 .details > div {
   display: inline;
 }
 
+.hidden {
+  display: none;
+}
+
 .version {
   /* title is not localized, so keep the margin on the left side */
   margin-left: .67em;
 }
 
 .description {
   width: 100%;
   overflow: hidden;
@@ -54,22 +64,16 @@ a:active {
 
 .status {
   border-top: 1px solid var(--color_about_item_border);
   font-weight: bold;
   padding: 0.5em;
   width: 100%;
 }
 
-.options-header {
-  font-weight: bold;
-  text-transform: uppercase;
-  margin-top: 1em;
-}
-
 .addon-item[isDisabled="true"] .options-header,
 .addon-item[optionsURL=""] .options-header,
 .addon-item[isDisabled="true"] .options-box,
 .addon-item[optionsURL=""] .options-box {
   display: none;
 }
 
 #addons-details > .list-item {
@@ -322,17 +326,13 @@ div[opType="needs-restart"] .hide-on-res
 div[opType="needs-uninstall"] .hide-on-uninstall,
 div[isDisabled="true"][opType="needs-uninstall"],
 div[opType="needs-install"] .hide-on-install,
 div[opType="needs-enable"] .hide-on-enable,
 div[opType="needs-disable"] .hide-on-disable {
   display: none;
 }
 
-#addons-list, #addons-details {
-  display: none;
-}
-
 #browse-title:dir(rtl) {
   background-position: left;
   background-image: url("chrome://browser/skin/images/chevron-rtl.png");
 }
 
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -3781,16 +3781,17 @@ var gDetailView = {
       detailViewBoxObject.scrollTo(0, top);
     }
   },
 
   async createOptionsBrowser(parentNode) {
     let browser = document.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
+    browser.setAttribute("id", "addon-options");
     browser.setAttribute("class", "inline-options-browser");
 
     let {optionsURL} = this._addon;
     let remote = !E10SUtils.canLoadURIInProcess(optionsURL, Services.appinfo.PROCESS_TYPE_DEFAULT);
 
     let readyPromise;
     if (remote) {
       browser.setAttribute("remote", "true");