Bug 1025000 - move runtime details and permissions table inside webide. r=jryans
authorPaul Rouget <paul@mozilla.com>
Thu, 19 Jun 2014 05:19:56 +0800
changeset 189589 ffb5584d22ac407b2b6e5d5862bb7602c810fd6d
parent 189588 13ee1cbe7d2c95147de5e6c755f514b1cf1cadc7
child 189590 de0630683d16ec23f9e8a858b92a278925f906f7
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjryans
bugs1025000
milestone33.0a1
Bug 1025000 - move runtime details and permissions table inside webide. r=jryans
browser/devtools/webide/content/addons.js
browser/devtools/webide/content/addons.xhtml
browser/devtools/webide/content/jar.mn
browser/devtools/webide/content/permissionstable.js
browser/devtools/webide/content/permissionstable.xhtml
browser/devtools/webide/content/runtimedetails.js
browser/devtools/webide/content/runtimedetails.xhtml
browser/devtools/webide/content/webide.js
browser/devtools/webide/content/webide.xul
browser/devtools/webide/locales/en-US/webide.dtd
browser/devtools/webide/modules/runtimes.js
browser/devtools/webide/test/chrome.ini
browser/devtools/webide/test/head.js
browser/devtools/webide/test/test_deviceinfo.html
browser/devtools/webide/themes/jar.mn
browser/devtools/webide/themes/tabledoc.css
--- a/browser/devtools/webide/content/addons.js
+++ b/browser/devtools/webide/content/addons.js
@@ -8,25 +8,27 @@ const {require} = Cu.import("resource://
 const {GetAvailableAddons} = require("devtools/webide/addons");
 const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.querySelector("#aboutaddons").onclick = function() {
     window.parent.UI.openInBrowser("about:addons");
   }
-  document.querySelector("#close").onclick = function() {
-    window.parent.UI.closeLastDeckPanel();
-  }
+  document.querySelector("#close").onclick = CloseUI;
   GetAvailableAddons().then(BuildUI, (e) => {
     console.error(e);
     window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
   });
 }, true);
 
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
 function BuildUI(addons) {
   BuildItem(addons.adb, true /* is adb */);
   for (let addon of addons.simulators) {
     BuildItem(addon, false /* is adb */);
   }
 }
 
 function BuildItem(addon, isADB) {
--- a/browser/devtools/webide/content/addons.xhtml
+++ b/browser/devtools/webide/content/addons.xhtml
@@ -14,17 +14,17 @@
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/addons.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://webide/content/addons.js"></script>
   </head>
   <body>
 
     <div id="controls">
       <a id="aboutaddons">&addons_aboutaddons;</a>
-      <a id="close">&addons_close;</a>
+      <a id="close">&deck_close;</a>
     </div>
 
     <h1>&addons_title;</h1>
 
     <ul></ul>
 
   </body>
 </html>
--- a/browser/devtools/webide/content/jar.mn
+++ b/browser/devtools/webide/content/jar.mn
@@ -8,14 +8,18 @@ webide.jar:
     content/webide.js                 (webide.js)
     content/newapp.xul                (newapp.xul)
     content/newapp.js                 (newapp.js)
     content/details.xhtml             (details.xhtml)
     content/details.js                (details.js)
     content/cli.js                    (cli.js)
     content/addons.js                 (addons.js)
     content/addons.xhtml              (addons.xhtml)
+    content/permissionstable.js       (permissionstable.js)
+    content/permissionstable.xhtml    (permissionstable.xhtml)
+    content/runtimedetails.js         (runtimedetails.js)
+    content/runtimedetails.xhtml      (runtimedetails.xhtml)
 
 # Temporarily include locales in content, until we're ready
 # to localize webide
 
     content/webide.dtd                (../locales/en-US/webide.dtd)
     content/webide.properties         (../locales/en-US/webide.properties)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/permissionstable.js
@@ -0,0 +1,75 @@
+/* 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/. */
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const {AppManager} = require("devtools/webide/app-manager");
+const {Connection} = require("devtools/client/connection-manager");
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+  document.querySelector("#close").onclick = CloseUI;
+  AppManager.on("app-manager-update", OnAppManagerUpdate);
+  BuildUI();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+  window.removeEventListener("unload", onUnload);
+  AppManager.off("app-manager-update", OnAppManagerUpdate);
+});
+
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
+function OnAppManagerUpdate(event, what) {
+  if (what == "connection" || what == "list-tabs-response") {
+    BuildUI();
+  }
+}
+
+let getRawPermissionsTablePromise; // Used by tests
+function BuildUI() {
+  let table = document.querySelector("table");
+  let lines = table.querySelectorAll(".line");
+  for (let line of lines) {
+    line.remove();
+  }
+
+  if (AppManager.connection &&
+      AppManager.connection.status == Connection.Status.CONNECTED &&
+      AppManager.deviceFront) {
+    getRawPermissionsTablePromise = AppManager.deviceFront.getRawPermissionsTable();
+    getRawPermissionsTablePromise.then(json => {
+      let permissionsTable = json.rawPermissionsTable;
+      for (let name in permissionsTable) {
+        let tr = document.createElement("tr");
+        tr.className = "line";
+        let td = document.createElement("td");
+        td.textContent = name;
+        tr.appendChild(td);
+        for (let type of ["app","privileged","certified"]) {
+          let td = document.createElement("td");
+          if (permissionsTable[name][type] == json.ALLOW_ACTION) {
+            td.textContent = "✓";
+            td.className = "permallow";
+          }
+          if (permissionsTable[name][type] == json.PROMPT_ACTION) {
+            td.textContent = "!";
+            td.className = "permprompt";
+          }
+          if (permissionsTable[name][type] == json.DENY_ACTION) {
+            td.textContent = "✕";
+            td.className = "permdeny"
+          }
+          tr.appendChild(td);
+        }
+        table.appendChild(tr);
+      }
+    });
+  } else {
+    CloseUI();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/permissionstable.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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 [
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf8"/>
+    <link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://webide/content/permissionstable.js"></script>
+  </head>
+  <body>
+
+    <div id="controls">
+      <a id="close">&deck_close;</a>
+    </div>
+
+    <h1>&permissionstable_title;</h1>
+
+    <table class="permissionstable">
+      <tr>
+        <th>&permissionstable_name_header;</th>
+        <th>type:web</th>
+        <th>type:privileged</th>
+        <th>type:certified</th>
+      </tr>
+    </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/runtimedetails.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const {AppManager} = require("devtools/webide/app-manager");
+const {Connection} = require("devtools/client/connection-manager");
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+  document.querySelector("#close").onclick = CloseUI;
+  AppManager.on("app-manager-update", OnAppManagerUpdate);
+  BuildUI();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+  window.removeEventListener("unload", onUnload);
+  AppManager.off("app-manager-update", OnAppManagerUpdate);
+});
+
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
+function OnAppManagerUpdate(event, what) {
+  if (what == "connection" || what == "list-tabs-response") {
+    BuildUI();
+  }
+}
+
+let getDescriptionPromise; // Used by tests
+function BuildUI() {
+  let table = document.querySelector("table");
+  table.innerHTML = "";
+  if (AppManager.connection &&
+      AppManager.connection.status == Connection.Status.CONNECTED &&
+      AppManager.deviceFront) {
+    getDescriptionPromise = AppManager.deviceFront.getDescription();
+    getDescriptionPromise.then(json => {
+      for (let name in json) {
+        let tr = document.createElement("tr");
+        let td = document.createElement("td");
+        td.textContent = name;
+        tr.appendChild(td);
+        td = document.createElement("td");
+        td.textContent = json[name];
+        tr.appendChild(td);
+        table.appendChild(tr);
+      }
+    });
+  } else {
+    CloseUI();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/runtimedetails.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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 [
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf8"/>
+    <link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://webide/content/runtimedetails.js"></script>
+  </head>
+  <body>
+
+    <div id="controls">
+      <a id="close">&deck_close;</a>
+    </div>
+
+    <h1>&runtimedetails_title;</h1>
+
+    <table></table>
+  </body>
+</html>
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -394,45 +394,31 @@ let UI = {
 
     if (project.location) {
       Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
     }
   },
 
   /********** DECK **********/
 
-  _lastSelectedPanels: [],
-
   resetFocus: function() {
     document.commandDispatcher.focusedElement = document.documentElement;
   },
 
   selectDeckPanel: function(id) {
     this.hidePanels();
     this.resetFocus();
     let deck = document.querySelector("#deck");
     let panel = deck.querySelector("#deck-panel-" + id);
-    this._lastSelectedPanels.push(deck.selectedIndex);
     deck.selectedPanel = panel;
   },
 
-  closeLastDeckPanel: function() {
-    this.resetFocus();
-    let deck = document.querySelector("#deck");
-    if (this._lastSelectedPanels.length > 0) {
-      deck.selectedIndex = this._lastSelectedPanels.pop();
-    } else {
-      this.resetDeck();
-    }
-  },
-
   resetDeck: function() {
     this.resetFocus();
     let deck = document.querySelector("#deck");
-    this._lastSelectedPanels = [];
     deck.selectedPanel = null;
   },
 
   /********** COMMANDS **********/
 
   updateCommands: function() {
 
     if (document.querySelector("window").classList.contains("busy")) {
@@ -789,91 +775,21 @@ let Cmds = {
        return longstr.string().then(dataURL => {
          longstr.release().then(null, console.error);
          UI.openInBrowser(dataURL);
        });
     }), "taking screenshot");
   },
 
   showPermissionsTable: function() {
-    return UI.busyUntil(AppManager.deviceFront.getRawPermissionsTable().then(json => {
-      let styleContent = "";
-      styleContent += "body {background:white; font-family: monospace}";
-      styleContent += "table {border-collapse: collapse}";
-      styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
-      styleContent += "th {min-width: 130px}";
-      styleContent += "td {text-align: center}";
-      styleContent += "th:first-of-type, td:first-of-type {text-align:left}";
-      styleContent += ".permallow  {color:rgb(152, 207, 57)}";
-      styleContent += ".permprompt {color:rgb(0,158,237)}";
-      styleContent += ".permdeny   {color:rgb(204,73,8)}";
-      let style = document.createElementNS(HTML, "style");
-      style.textContent = styleContent;
-      let table = document.createElementNS(HTML, "table");
-      table.innerHTML = "<tr><th>Name</th><th>type:web</th><th>type:privileged</th><th>type:certified</th></tr>";
-      let permissionsTable = json.rawPermissionsTable;
-      for (let name in permissionsTable) {
-        let tr = document.createElementNS(HTML, "tr");
-        let td = document.createElementNS(HTML, "td");
-        td.textContent = name;
-        tr.appendChild(td);
-        for (let type of ["app","privileged","certified"]) {
-          let td = document.createElementNS(HTML, "td");
-          if (permissionsTable[name][type] == json.ALLOW_ACTION) {
-            td.textContent = "✓";
-            td.className = "permallow";
-          }
-          if (permissionsTable[name][type] == json.PROMPT_ACTION) {
-            td.textContent = "!";
-            td.className = "permprompt";
-          }
-          if (permissionsTable[name][type] == json.DENY_ACTION) {
-            td.textContent = "✕";
-            td.className = "permdeny"
-          }
-          tr.appendChild(td);
-        }
-        table.appendChild(tr);
-      }
-      let body = document.createElementNS(HTML, "body");
-      body.appendChild(style);
-      body.appendChild(table);
-      let url = "data:text/html;charset=utf-8,";
-      url += encodeURIComponent(body.outerHTML);
-      UI.openInBrowser(url);
-    }), "showing permission table");
+    UI.selectDeckPanel("permissionstable");
   },
 
   showRuntimeDetails: function() {
-    return UI.busyUntil(AppManager.deviceFront.getDescription().then(json => {
-      let styleContent = "";
-      styleContent += "body {background:white; font-family: monospace}";
-      styleContent += "table {border-collapse: collapse}";
-      styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
-      let style = document.createElementNS(HTML, "style");
-      style.textContent = styleContent;
-      let table = document.createElementNS(HTML, "table");
-      for (let name in json) {
-        let tr = document.createElementNS(HTML, "tr");
-        let td = document.createElementNS(HTML, "td");
-        td.textContent = name;
-        tr.appendChild(td);
-        td = document.createElementNS(HTML, "td");
-        td.textContent = json[name];
-        tr.appendChild(td);
-        table.appendChild(tr);
-      }
-      let body = document.createElementNS(HTML, "body");
-      body.appendChild(style);
-      body.appendChild(table);
-      let url = "data:text/html;charset=utf-8,";
-      url += encodeURIComponent(body.outerHTML);
-      UI.openInBrowser(url);
-    }), "showing runtime details");
-
+    UI.selectDeckPanel("runtimedetails");
   },
 
   play: function() {
     switch(AppManager.selectedProject.type) {
       case "packaged":
       case "hosted":
         return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
         break;
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -157,16 +157,18 @@
 
   </popupset>
 
   <notificationbox flex="1" id="notificationbox">
     <deck flex="1" id="deck" selectedIndex="-1">
       <iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
       <iframe id="deck-panel-projecteditor" flex="1"/>
       <iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
+      <iframe id="deck-panel-permissionstable" flex="1" src="permissionstable.xhtml"/>
+      <iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
     </deck>
   </notificationbox>
 
   <splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
 
   <!-- toolbox iframe will be inserted here -->
 
 </window>
--- a/browser/devtools/webide/locales/en-US/webide.dtd
+++ b/browser/devtools/webide/locales/en-US/webide.dtd
@@ -77,12 +77,23 @@
 <!ENTITY details_removeProject_button "Remove Project">
 
 <!-- New App -->
 <!ENTITY newAppWindowTitle "New App">
 <!ENTITY newAppHeader "Select template">
 <!ENTITY newAppLoadingTemplate "Loading templates…">
 <!ENTITY newAppProjectName "Project Name:">
 
+
+<!-- Decks -->
+
+<!ENTITY deck_close "close">
+
 <!-- Addons -->
 <!ENTITY addons_title "Extra Components:">
-<!ENTITY addons_close "close">
 <!ENTITY addons_aboutaddons "Open Addons Manager">
+
+<!-- Permissions Table -->
+<!ENTITY permissionstable_title "Permissions Table">
+<!ENTITY permissionstable_name_header "Name">
+
+<!-- Runtime Details -->
+<!ENTITY runtimedetails_title "Runtime Info">
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -56,17 +56,16 @@ SimulatorRuntime.prototype = {
     return this.version;
   },
   getName: function() {
     return Simulator.getByVersion(this.version).appinfo.label;
   },
 }
 
 let gLocalRuntime = {
-  supportApps: false, // Temporary static value
   connect: function(connection) {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     connection.port = null;
     connection.host = null; // Force Pipe transport
     connection.connect();
--- a/browser/devtools/webide/test/chrome.ini
+++ b/browser/devtools/webide/test/chrome.ini
@@ -26,8 +26,9 @@ support-files =
 
 [test_basic.html]
 [test_newapp.html]
 [test_import.html]
 [test_runtime.html]
 [test_cli.html]
 [test_manifestUpdate.html]
 [test_addons.html]
+[test_deviceinfo.html]
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -90,8 +90,23 @@ function removeAllProjects() {
 function nextTick() {
   let deferred = promise.defer();
   SimpleTest.executeSoon(() => {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
+
+function documentIsLoaded(doc) {
+  let deferred = promise.defer();
+  if (doc.readyState == "complete") {
+    deferred.resolve();
+  } else {
+    doc.addEventListener("readystatechange", function onChange() {
+      if (doc.readyState == "complete") {
+        doc.removeEventListener("readystatechange", onChange);
+        deferred.resolve();
+      }
+    });
+  }
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/test_deviceinfo.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+
+<html>
+
+  <head>
+    <meta charset="utf8">
+    <title></title>
+
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+    <script type="application/javascript;version=1.8" src="head.js"></script>
+    <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  </head>
+
+  <body>
+
+    <script type="application/javascript;version=1.8">
+      window.onload = function() {
+        SimpleTest.waitForExplicitFinish();
+
+        Task.spawn(function* () {
+          Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+          DebuggerServer.init(function () { return true; });
+          DebuggerServer.addBrowserActors();
+
+          let win = yield openWebIDE();
+
+          let permIframe = win.document.querySelector("#deck-panel-permissionstable");
+          let infoIframe = win.document.querySelector("#deck-panel-runtimedetails");
+
+          yield documentIsLoaded(permIframe.contentWindow.document);
+          yield documentIsLoaded(infoIframe.contentWindow.document);
+
+          win.AppManager.update("runtimelist");
+
+          let panelNode = win.document.querySelector("#runtime-panel");
+          let items = panelNode.querySelectorAll(".runtime-panel-item-custom");
+          is(items.length, 2, "Found 2 custom runtimes button");
+
+          let deferred = promise.defer();
+          win.AppManager.on("app-manager-update", function onUpdate(e,w) {
+            if (w == "list-tabs-response") {
+              win.AppManager.off("app-manager-update", onUpdate);
+              deferred.resolve();
+            }
+          });
+
+          items[1].click();
+
+          yield deferred.promise;
+
+          yield nextTick();
+
+          let perm = win.document.querySelector("#cmd_showPermissionsTable");
+          let info = win.document.querySelector("#cmd_showRuntimeDetails");
+
+          ok(!perm.hasAttribute("disabled"), "perm cmd enabled");
+          ok(!info.hasAttribute("disabled"), "info cmd enabled");
+
+          let deck = win.document.querySelector("#deck");
+
+          win.Cmds.showRuntimeDetails();
+          is(deck.selectedPanel, infoIframe, "info iframe selected");
+
+          yield infoIframe.contentWindow.getRawPermissionsTablePromise;
+
+          yield nextTick();
+
+          // device info and permissions content is checked in other tests
+          // We just test one value to make sure we get something
+
+          let doc = infoIframe.contentWindow.document;
+          let trs = doc.querySelectorAll("tr");
+          let found = false;
+
+          for (let tr of trs) {
+            let [name,val] = tr.querySelectorAll("td");
+            if (name.textContent == "appid") {
+              found = true;
+              is(val.textContent, Services.appinfo.ID, "appid has the right value");
+            }
+          }
+          ok(found, "Found appid line");
+
+          win.Cmds.showPermissionsTable();
+          is(deck.selectedPanel, permIframe, "permission iframe selected");
+
+          yield infoIframe.contentWindow.getDescriptionPromise;
+
+          yield nextTick();
+
+          doc = permIframe.contentWindow.document;
+          trs = doc.querySelectorAll(".line");
+          found = false;
+          for (let tr of trs) {
+            let [name,v1,v2,v3] = tr.querySelectorAll("td");
+            if (name.textContent == "geolocation") {
+              found = true;
+              is(v1.className, "permprompt", "geolocation perm is valid");
+              is(v2.className, "permprompt", "geolocation perm is valid");
+              is(v3.className, "permprompt", "geolocation perm is valid");
+            }
+          }
+          ok(found, "Found geolocation line");
+
+          doc.querySelector("#close").click();
+
+          ok(!deck.selectedPanel, "No panel selected");
+
+          DebuggerServer.destroy();
+
+          yield closeWebIDE(win);
+
+          SimpleTest.finish();
+
+
+        }).then(null, e => {
+          ok(false, "Exception: " + e);
+          SimpleTest.finish();
+        });
+      }
+
+
+    </script>
+  </body>
+</html>
--- a/browser/devtools/webide/themes/jar.mn
+++ b/browser/devtools/webide/themes/jar.mn
@@ -5,8 +5,9 @@
 webide.jar:
 % skin webide classic/1.0 %skin/
 * skin/webide.css         (webide.css)
   skin/icons.png          (icons.png)
   skin/details.css        (details.css)
   skin/newapp.css         (newapp.css)
   skin/throbber.svg       (throbber.svg)
   skin/addons.css         (addons.css)
+  skin/tabledoc.css       (tabledoc.css)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/themes/tabledoc.css
@@ -0,0 +1,54 @@
+body {
+  background: white;
+}
+
+#controls {
+  position: fixed;
+  top: 10px;
+  right: 10px;
+}
+
+#controls > a {
+  color: #4C9ED9;
+  font-size: small;
+  cursor: pointer;
+  border-bottom: 1px dotted;
+}
+
+#close {
+  margin-left: 10px;
+}
+
+table {
+  font-family: monospace;
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 5px;
+  border: 1px solid #EEE;
+}
+
+th {
+  min-width: 130px;
+}
+
+.permissionstable td {
+  text-align: center;
+}
+
+th:first-of-type, td:first-of-type {
+  text-align: left;
+}
+
+.permallow {
+  color: rgb(152,207,57);
+}
+
+.permprompt {
+  color: rgb(0,158,237);
+}
+
+.permdeny {
+  color: rgb(204,73,8);
+}