Bug 1472528 - Design and implementation of about:policies page r=flod,felipe
authorKanika Saini <ksaini@mozilla.com>
Mon, 02 Jul 2018 12:55:31 +0530
changeset 431555 ebfd031ace655e9001916a4aa0c42dce476cd92d
parent 431554 d3e5f5bcef6048adf5f324b24e862691a6a2efe4
child 431556 6639ad4e8e86ec64e9d97d6512ea77558d1f4510
push id34443
push usercsabou@mozilla.com
push dateWed, 15 Aug 2018 00:53:32 +0000
treeherdermozilla-central@b80906e2fbc9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflod, felipe
bugs1472528
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1472528 - Design and implementation of about:policies page r=flod,felipe
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 = Enterprise 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
@@ -90,27 +90,18 @@ var snapshotFormatters = {
           policiesText = strings.GetStringFromName("policies.active");
           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);
-        });
+      if (data.policiesStatus != Services.policies.INACTIVE) {
+        let activePolicies = $.new("a", policiesText, null, {href: "about:policies"});
         $("policies-status").appendChild(activePolicies);
       } else {
         $("policies-status").textContent = policiesText;
       }
     } else {
       $("policies-status-row").hidden = true;
     }