Bug 1472528 - Design and implementation of about:policies page r?felipe draft
authorKanika Saini <ksaini@mozilla.com>
Mon, 02 Jul 2018 12:55:31 +0530
changeset 828966 44452b6ea8b0c151c17c0917545cf0e81ad1ce9b
parent 826542 4e56a2f51ad739ca52046723448f3129a58f1666
push id118736
push userbmo:ksaini@mozilla.com
push dateTue, 14 Aug 2018 15:39:47 +0000
reviewersfelipe
bugs1472528
milestone63.0a1
Bug 1472528 - Design and implementation of about:policies page r?felipe MozReview-Commit-ID: 2fWe0YJYY7H
browser/components/enterprisepolicies/content/aboutPolicies.css
browser/components/enterprisepolicies/content/aboutPolicies.js
browser/components/enterprisepolicies/content/aboutPolicies.xhtml
browser/locales/en-US/browser/aboutPolicies.ftl
toolkit/content/aboutSupport.js
--- a/browser/components/enterprisepolicies/content/aboutPolicies.css
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.css
@@ -1,3 +1,141 @@
 /* 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/. */
+
+@import url("chrome://global/skin/in-content/common.css");
+
+html {
+  height: 100%;
+}
+
+body {
+  display: flex;
+  align-items: stretch;
+  height: 100%;
+}
+
+#sectionTitle {
+  float: left;
+}
+
+#sectionTitle:dir(rtl) {
+  float: right;
+}
+
+/** Categories **/
+
+.category {
+  cursor: pointer;
+  /* Center category names */
+  display: flex;
+  align-items: center;
+}
+
+.category .category-name {
+  pointer-events: none;
+}
+
+#categories hr {
+  border-top-color: rgba(255,255,255,0.15);
+}
+
+/** Content area **/
+
+.main-content {
+  flex: 1;
+}
+
+.tab {
+  padding: 0.5em 0;
+}
+
+.tab table {
+  width: 100%;
+}
+
+th, td, table {
+  border-collapse: collapse;
+  border: none;
+  text-align: start;
+}
+
+th {
+  padding-bottom: 8px;
+  font-size: larger;
+}
+
+td {
+  padding-bottom: 8px;
+}
+
+.active-policies tr:nth-child(even) {
+  background-color: white;
+}
+
+.errors tr:nth-child(even) {
+  background-color: white;
+}
+
+/*
+ * In Documentation Tab, this property sets the policies row in an
+ * alternate color scheme of white and grey as each policy comprises
+ * of two tbody tags, one for the description and the other for the
+ * collapsible information block.
+ */
+
+tbody:nth-child(4n + 1) {
+  background-color: white;
+}
+
+.lastpolicyrow {
+  border-bottom: 3px solid #0c0c0d;
+}
+
+tr.lastpolicyrow td {
+  padding-bottom: 16px;
+}
+
+.array {
+  border-bottom: 1px solid var(--in-content-box-border-color);
+  padding-bottom: 4px;
+}
+
+.icon {
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: 16px;
+  -moz-context-properties: fill;
+  display: inline-block;
+  fill: var(--newtab-icon-primary-color);
+  height: 14px;
+  vertical-align: middle;
+  width: 14px;
+}
+
+.icon.machine-only {
+  background-image: url("chrome://browser/skin/developer.svg");
+}
+
+.collapsible {
+  cursor: pointer;
+  border: none;
+  outline: none;
+}
+
+.content {
+  display: none;
+}
+
+.content-style {
+  background-color: white;
+  color: var(--in-content-category-text-selected);
+}
+
+tbody.collapsible td {
+  padding-bottom: 8px;
+}
+
+.schema {
+  font-family: monospace;
+  white-space: pre;
+}
\ No newline at end of file
--- a/browser/components/enterprisepolicies/content/aboutPolicies.js
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.js
@@ -1,4 +1,276 @@
 /* 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/. */
 
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  schema: "resource:///modules/policies/schema.jsm",
+});
+
+function col(text, className) {
+  let column = document.createElement("td");
+  if (className) {
+    column.classList.add(className);
+  }
+  let content = document.createTextNode(text);
+  column.appendChild(content);
+  return column;
+}
+
+function machine_only_col(text) {
+  let icon = document.createElement("span");
+  icon.classList.add("icon");
+  icon.classList.add("machine-only");
+  icon.title = "Machine-only";
+  let column = document.createElement("td");
+  let content = document.createTextNode(text);
+  column.appendChild(content);
+  column.appendChild(icon);
+  return column;
+}
+
+/*
+ * This function generates the Active Policies content to be displayed by calling
+ * a recursive function called generatePolicy() according to the policy schema.
+ */
+
+function generateActivePolicies(data) {
+
+  let new_cont = document.getElementById("activeContent");
+  new_cont.classList.add("active-policies");
+
+  for (let policyName in data) {
+    if (schema.properties[policyName].type == "array") {
+      for (let count in data[policyName]) {
+        let isFirstRow = (count == 0);
+        let isLastRow = (count == data[policyName].length - 1);
+        let row = document.createElement("tr");
+        row.appendChild(col(isFirstRow ? policyName : ""));
+        generatePolicy(data[policyName][count], row, 1, new_cont, isLastRow, !isFirstRow);
+      }
+    } else if (schema.properties[policyName].type == "object") {
+      let count = 0;
+      for (let obj in data[policyName]) {
+        let isFirstRow = (count == 0);
+        let isLastRow = (count == data[policyName].length - 1);
+        let row = document.createElement("tr");
+        row.appendChild(col(isFirstRow ? policyName : ""));
+        row.appendChild(col(obj));
+        generatePolicy(data[policyName][obj], row, 2, new_cont, isLastRow);
+        count++;
+      }
+    } else {
+      let row = document.createElement("tr");
+      row.appendChild(col(policyName));
+      row.appendChild(col(JSON.stringify(data[policyName])));
+      row.classList.add("lastpolicyrow");
+      new_cont.appendChild(row);
+    }
+  }
+}
+
+/*
+ * This is a helper recursive function that iterates levels of each
+ * policy and formats the content to be displayed accordingly.
+ */
+
+function generatePolicy(data, row, depth, new_cont, islast, arr_sep = false) {
+  if (Array.isArray(data)) {
+    for (let count in data) {
+      if (count == 0) {
+        if (count == data.length - 1) {
+          generatePolicy(data[count], row, depth + 1, new_cont, islast ? islast : false, false);
+        } else {
+          generatePolicy(data[count], row, depth + 1, new_cont, false, true);
+        }
+      } else if (count == data.length - 1) {
+        let last_row = document.createElement("tr");
+        for (let i = 0; i < depth; i++) {
+            last_row.appendChild(col(""));
+        }
+
+        generatePolicy(data[count], last_row, depth + 1, new_cont, islast ? islast : false, arr_sep);
+      } else {
+        let new_row = document.createElement("tr");
+        for (let i = 0; i < depth; i++) {
+          new_row.appendChild(col(""));
+        }
+
+        generatePolicy(data[count], new_row, depth + 1, new_cont, false, true);
+      }
+    }
+  } else if (typeof data == "object" && Object.keys(data).length > 0) {
+    let count = 0;
+      for (let obj in data) {
+        if (count == 0) {
+          row.appendChild(col(obj));
+          if (count == Object.keys(data).length - 1) {
+            generatePolicy(data[obj], row, depth + 1, new_cont, islast ? islast : false, arr_sep);
+          } else {
+            generatePolicy(data[obj], row, depth + 1, new_cont, false, false);
+          }
+        } else if (count == Object.keys(data).length - 1) {
+          let last_row = document.createElement("tr");
+          for (let i = 0; i < depth; i++) {
+            last_row.appendChild(col(""));
+          }
+
+          if (arr_sep) {
+            last_row.appendChild(col(obj, "array"));
+          } else {
+            last_row.appendChild(col(obj));
+          }
+
+          generatePolicy(data[obj], last_row, depth + 1, new_cont, islast ? islast : false, arr_sep);
+        } else {
+          let new_row = document.createElement("tr");
+          for (let i = 0; i < depth; i++) {
+            new_row.appendChild(col(""));
+          }
+
+          new_row.appendChild(col(obj));
+          generatePolicy(data[obj], new_row, depth + 1, new_cont, false, false);
+        }
+        count++;
+      }
+  } else {
+    if (arr_sep) {
+      row.appendChild(col(JSON.stringify(data), "array"));
+    } else {
+      row.appendChild(col(JSON.stringify(data)));
+    }
+    if (islast) {
+      row.classList.add("lastpolicyrow");
+    }
+    new_cont.appendChild(row);
+  }
+}
+
+function generateErrors() {
+  const consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+  const storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
+  const consoleEvents = storage.getEvents();
+  const prefixes = ["Enterprise Policies",
+                    "JsonSchemaValidator.jsm",
+                    "Policies.jsm",
+                    "GPOParser.jsm",
+                    "Enterprise Policies Child",
+                    "BookmarksPolicies.jsm",
+                    "ProxyPolicies.jsm",
+                    "WebsiteFilter Policy"];
+
+  let new_cont = document.getElementById("errorsContent");
+  new_cont.classList.add("errors");
+
+  let flag = false;
+  for (let err of consoleEvents) {
+    if (prefixes.includes(err.prefix)) {
+      flag = true;
+      let row = document.createElement("tr");
+      row.appendChild(col(err.arguments[0], "schema"));
+      new_cont.appendChild(row);
+    }
+  }
+
+  if (!flag) {
+    let errors_tab = document.getElementById("category-errors");
+    errors_tab.style.display = "none";
+  }
+}
+
+function generateDocumentation() {
+  let new_cont = document.getElementById("documentationContent");
+  new_cont.setAttribute("id", "documentationContent");
+
+  for (let policyName in schema.properties) {
+    let main_tbody = document.createElement("tbody");
+    main_tbody.classList.add("collapsible");
+    main_tbody.addEventListener("click", function() {
+      let content = this.nextElementSibling;
+      content.classList.toggle("content");
+    });
+    let row = document.createElement("tr");
+    if (schema.properties[policyName].machine_only) {
+      row.appendChild(machine_only_col(policyName));
+    } else {
+      row.appendChild(col(policyName));
+    }
+    row.appendChild(col(schema.properties[policyName].description));
+    main_tbody.appendChild(row);
+    let sec_tbody = document.createElement("tbody");
+    sec_tbody.classList.add("content");
+    sec_tbody.classList.add("content-style");
+    let schema_row = document.createElement("tr");
+    if (schema.properties[policyName].properties) {
+      let column = col(JSON.stringify(schema.properties[policyName].properties, null, 1), "schema");
+      column.colSpan = "2";
+      schema_row.appendChild(column);
+      sec_tbody.appendChild(schema_row);
+    } else {
+      let column = col("type: " + schema.properties[policyName].type, "schema");
+      column.colSpan = "2";
+      schema_row.appendChild(column);
+      sec_tbody.appendChild(schema_row);
+      if (schema.properties[policyName].enum) {
+        let enum_row = document.createElement("tr");
+        column = col("enum: " + JSON.stringify(schema.properties[policyName].enum, null, 1), "schema");
+        column.colSpan = "2";
+        enum_row.appendChild(column);
+        sec_tbody.appendChild(enum_row);
+      }
+    }
+    new_cont.appendChild(main_tbody);
+    new_cont.appendChild(sec_tbody);
+  }
+}
+
+let gInited = false;
+function init() {
+  if (gInited) {
+    return;
+  }
+  gInited = true;
+
+  let data = Services.policies.getActivePolicies();
+  generateActivePolicies(data);
+  generateErrors();
+  generateDocumentation();
+
+  // Event delegation on #categories element
+  let menu = document.getElementById("categories");
+  menu.addEventListener("click", function click(e) {
+    if (e.target && e.target.parentNode == menu)
+      show(e.target);
+  });
+
+  if (location.hash) {
+    let sectionButton = document.getElementById("category-" + location.hash.substring(1));
+    if (sectionButton) {
+      sectionButton.click();
+    }
+  }
+}
+
+function show(button) {
+  let current_tab = document.querySelector(".active");
+  let category = button.getAttribute("id").substring("category-".length);
+  let content = document.getElementById(category);
+  if (current_tab == content)
+    return;
+  current_tab.classList.remove("active");
+  current_tab.hidden = true;
+  content.classList.add("active");
+  content.hidden = false;
+
+  let current_button = document.querySelector("[selected=true]");
+  current_button.removeAttribute("selected");
+  button.setAttribute("selected", "true");
+
+  let title = document.getElementById("sectionTitle");
+  title.textContent = button.children[0].textContent;
+  location.hash = category;
+}
--- a/browser/components/enterprisepolicies/content/aboutPolicies.xhtml
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.xhtml
@@ -2,16 +2,68 @@
 <!--
 # 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/.
 -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-  	<link rel="stylesheet" href="chrome://browser/content/policies/aboutPolicies.css" type="text/css" />
-    <script type="application/javascript" src="chrome://browser/content/policies/aboutPolicies.js" />
-  </head>
+    <head>
+        <title data-l10n-id="about-policies-title"/>
+        <link rel="stylesheet" href="chrome://browser/content/policies/aboutPolicies.css" type="text/css" />
+        <link rel="localization" href="browser/aboutPolicies.ftl"/>
+        <script type="application/javascript" src="chrome://global/content/l10n.js"></script>
+        <script type="application/javascript" src="chrome://browser/content/policies/aboutPolicies.js" />
+    </head>
+    <body id="body" onload="init()">
+        <div id="categories">
+            <div class="category" selected="true" id="category-active">
+                <span class="category-name" data-l10n-id="active-policies-tab"/>
+            </div>
+            <div class="category" id="category-documentation">
+                <span class="category-name" data-l10n-id="documentation-tab"/>
+            </div>
+            <div class="category" id="category-errors">
+                <span class="category-name" data-l10n-id="errors-tab"/>
+            </div>
+        </div>
+        <div class="main-content">
+            <div class="header">
+                <div id="sectionTitle" class="header-name" data-l10n-id="active-policies-tab"/>
+            </div>
 
-  <body>
-  </body>
+            <div id="active" class="tab active">
+                <table>
+                    <thead>
+                        <tr>
+                            <th data-l10n-id="policy-name"/>
+                            <th data-l10n-id="policy-value"/>
+                        </tr>
+                    </thead>
+                    <tbody id="activeContent" />
+                </table>
+            </div>
+
+            <div id="documentation" class="tab" hidden="true">
+                <table>
+                    <thead>
+                        <tr>
+                            <th data-l10n-id="policy-name"/>
+                        </tr>
+                    </thead>
+                    <tbody id="documentationContent" />
+                </table>
+            </div>
+
+             <div id="errors" class="tab" hidden="true">
+                <table>
+                    <thead>
+                        <tr>
+                            <th data-l10n-id="policy-errors"/>
+                        </tr>
+                    </thead>
+                    <tbody id="errorsContent" />
+                </table>
+            </div>
+        </div>
+    </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/aboutPolicies.ftl
@@ -0,0 +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/.
+
+about-policies-title = About Policies
+
+# 'Active' is used to describe the policies that are currently active
+active-policies-tab = Active
+errors-tab = Errors
+documentation-tab = Documentation
+
+policy-name = Policy Name
+policy-value = Policy Value
+policy-errors = Policy Errors
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -91,26 +91,17 @@ var snapshotFormatters = {
           break;
 
         default:
           policiesText = strings.GetStringFromName("policies.error");
           break;
       }
 
       if (data.policiesStatus == Services.policies.ACTIVE) {
-        let activePolicies = $.new("a", policiesText);
-        activePolicies.addEventListener("click", function(event) {
-          let activePoliciesJson = {};
-          activePoliciesJson.policies = Services.policies.getActivePolicies();
-          let activePoliciesJsonBlob = new Blob([JSON.stringify(activePoliciesJson)],
-                                                {type: "application/json"});
-          let jsonURL = URL.createObjectURL(activePoliciesJsonBlob);
-          window.open(jsonURL);
-          URL.revokeObjectURL(jsonURL);
-        });
+        let activePolicies = $.new("a", policiesText, null, {href: "about:policies"});
         $("policies-status").appendChild(activePolicies);
       } else {
         $("policies-status").textContent = policiesText;
       }
     } else {
       $("policies-status-row").hidden = true;
     }