summaryrefslogtreecommitdiffstats
path: root/devtools/client/webide/content
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/webide/content
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/webide/content')
-rw-r--r--devtools/client/webide/content/addons.js135
-rw-r--r--devtools/client/webide/content/addons.xhtml31
-rw-r--r--devtools/client/webide/content/details.js139
-rw-r--r--devtools/client/webide/content/details.xhtml54
-rw-r--r--devtools/client/webide/content/devicepreferences.js81
-rw-r--r--devtools/client/webide/content/devicepreferences.xhtml49
-rw-r--r--devtools/client/webide/content/devicesettings.js81
-rw-r--r--devtools/client/webide/content/devicesettings.xhtml50
-rw-r--r--devtools/client/webide/content/jar.mn38
-rw-r--r--devtools/client/webide/content/logs.js70
-rw-r--r--devtools/client/webide/content/logs.xhtml33
-rw-r--r--devtools/client/webide/content/monitor.js741
-rw-r--r--devtools/client/webide/content/monitor.xhtml31
-rw-r--r--devtools/client/webide/content/moz.build7
-rw-r--r--devtools/client/webide/content/newapp.js175
-rw-r--r--devtools/client/webide/content/newapp.xul33
-rw-r--r--devtools/client/webide/content/permissionstable.js78
-rw-r--r--devtools/client/webide/content/permissionstable.xhtml36
-rw-r--r--devtools/client/webide/content/prefs.js108
-rw-r--r--devtools/client/webide/content/prefs.xhtml112
-rw-r--r--devtools/client/webide/content/project-listing.js42
-rw-r--r--devtools/client/webide/content/project-listing.xhtml35
-rw-r--r--devtools/client/webide/content/project-panel.js11
-rw-r--r--devtools/client/webide/content/runtime-listing.js66
-rw-r--r--devtools/client/webide/content/runtime-listing.xhtml45
-rw-r--r--devtools/client/webide/content/runtime-panel.js11
-rw-r--r--devtools/client/webide/content/runtimedetails.js153
-rw-r--r--devtools/client/webide/content/runtimedetails.xhtml46
-rw-r--r--devtools/client/webide/content/simulator.js352
-rw-r--r--devtools/client/webide/content/simulator.xhtml99
-rw-r--r--devtools/client/webide/content/webide.js1157
-rw-r--r--devtools/client/webide/content/webide.xul178
-rw-r--r--devtools/client/webide/content/wifi-auth.js44
-rw-r--r--devtools/client/webide/content/wifi-auth.xhtml45
34 files changed, 4366 insertions, 0 deletions
diff --git a/devtools/client/webide/content/addons.js b/devtools/client/webide/content/addons.js
new file mode 100644
index 000000000..3948b040f
--- /dev/null
+++ b/devtools/client/webide/content/addons.js
@@ -0,0 +1,135 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const {gDevTools} = require("devtools/client/framework/devtools");
+const {GetAvailableAddons, ForgetAddonsList} = require("devtools/client/webide/modules/addons");
+const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ document.querySelector("#aboutaddons").onclick = function () {
+ let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ if (browserWin && browserWin.BrowserOpenAddonsMgr) {
+ browserWin.BrowserOpenAddonsMgr("addons://list/extension");
+ }
+ };
+ document.querySelector("#close").onclick = CloseUI;
+ GetAvailableAddons().then(BuildUI, (e) => {
+ console.error(e);
+ window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
+ });
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ ForgetAddonsList();
+}, true);
+
+function CloseUI() {
+ window.parent.UI.openProject();
+}
+
+function BuildUI(addons) {
+ BuildItem(addons.adb, "adb");
+ BuildItem(addons.adapters, "adapters");
+ for (let addon of addons.simulators) {
+ BuildItem(addon, "simulator");
+ }
+}
+
+function BuildItem(addon, type) {
+
+ function onAddonUpdate(event, arg) {
+ switch (event) {
+ case "update":
+ progress.removeAttribute("value");
+ li.setAttribute("status", addon.status);
+ status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
+ break;
+ case "failure":
+ window.parent.UI.reportError("error_operationFail", arg);
+ break;
+ case "progress":
+ if (arg == -1) {
+ progress.removeAttribute("value");
+ } else {
+ progress.value = arg;
+ }
+ break;
+ }
+ }
+
+ let events = ["update", "failure", "progress"];
+ for (let e of events) {
+ addon.on(e, onAddonUpdate);
+ }
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ for (let e of events) {
+ addon.off(e, onAddonUpdate);
+ }
+ });
+
+ let li = document.createElement("li");
+ li.setAttribute("status", addon.status);
+
+ let name = document.createElement("span");
+ name.className = "name";
+
+ switch (type) {
+ case "adb":
+ li.setAttribute("addon", type);
+ name.textContent = Strings.GetStringFromName("addons_adb_label");
+ break;
+ case "adapters":
+ li.setAttribute("addon", type);
+ try {
+ name.textContent = Strings.GetStringFromName("addons_adapters_label");
+ } catch (e) {
+ // This code (bug 1081093) will be backported to Aurora, which doesn't
+ // contain this string.
+ name.textContent = "Tools Adapters Add-on";
+ }
+ break;
+ case "simulator":
+ li.setAttribute("addon", "simulator-" + addon.version);
+ let stability = Strings.GetStringFromName("addons_" + addon.stability);
+ name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
+ break;
+ }
+
+ li.appendChild(name);
+
+ let status = document.createElement("span");
+ status.className = "status";
+ status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
+ li.appendChild(status);
+
+ let installButton = document.createElement("button");
+ installButton.className = "install-button";
+ installButton.onclick = () => addon.install();
+ installButton.textContent = Strings.GetStringFromName("addons_install_button");
+ li.appendChild(installButton);
+
+ let uninstallButton = document.createElement("button");
+ uninstallButton.className = "uninstall-button";
+ uninstallButton.onclick = () => addon.uninstall();
+ uninstallButton.textContent = Strings.GetStringFromName("addons_uninstall_button");
+ li.appendChild(uninstallButton);
+
+ let progress = document.createElement("progress");
+ li.appendChild(progress);
+
+ if (type == "adb") {
+ let warning = document.createElement("p");
+ warning.textContent = Strings.GetStringFromName("addons_adb_warning");
+ warning.className = "warning";
+ li.appendChild(warning);
+ }
+
+ document.querySelector("ul").appendChild(li);
+}
diff --git a/devtools/client/webide/content/addons.xhtml b/devtools/client/webide/content/addons.xhtml
new file mode 100644
index 000000000..6f3bc1e7c
--- /dev/null
+++ b/devtools/client/webide/content/addons.xhtml
@@ -0,0 +1,31 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <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">&deck_close;</a>
+ </div>
+
+ <h1>&addons_title;</h1>
+
+ <ul></ul>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/details.js b/devtools/client/webide/content/details.js
new file mode 100644
index 000000000..9097cd8c5
--- /dev/null
+++ b/devtools/client/webide/content/details.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {ProjectBuilding} = require("devtools/client/webide/modules/build");
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ document.addEventListener("visibilitychange", updateUI, true);
+ AppManager.on("app-manager-update", onAppManagerUpdate);
+ updateUI();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ AppManager.off("app-manager-update", onAppManagerUpdate);
+}, true);
+
+function onAppManagerUpdate(event, what, details) {
+ if (what == "project" ||
+ what == "project-validated") {
+ updateUI();
+ }
+}
+
+function resetUI() {
+ document.querySelector("#toolbar").classList.add("hidden");
+ document.querySelector("#type").classList.add("hidden");
+ document.querySelector("#descriptionHeader").classList.add("hidden");
+ document.querySelector("#manifestURLHeader").classList.add("hidden");
+ document.querySelector("#locationHeader").classList.add("hidden");
+
+ document.body.className = "";
+ document.querySelector("#icon").src = "";
+ document.querySelector("h1").textContent = "";
+ document.querySelector("#description").textContent = "";
+ document.querySelector("#type").textContent = "";
+ document.querySelector("#manifestURL").textContent = "";
+ document.querySelector("#location").textContent = "";
+
+ document.querySelector("#prePackageLog").hidden = true;
+
+ document.querySelector("#errorslist").innerHTML = "";
+ document.querySelector("#warningslist").innerHTML = "";
+
+}
+
+function updateUI() {
+ resetUI();
+
+ let project = AppManager.selectedProject;
+ if (!project) {
+ return;
+ }
+
+ if (project.type != "runtimeApp" && project.type != "mainProcess") {
+ document.querySelector("#toolbar").classList.remove("hidden");
+ document.querySelector("#locationHeader").classList.remove("hidden");
+ document.querySelector("#location").textContent = project.location;
+ }
+
+ document.body.className = project.validationStatus;
+ document.querySelector("#icon").src = project.icon;
+ document.querySelector("h1").textContent = project.name;
+
+ let manifest;
+ if (project.type == "runtimeApp") {
+ manifest = project.app.manifest;
+ } else {
+ manifest = project.manifest;
+ }
+
+ if (manifest) {
+ if (manifest.description) {
+ document.querySelector("#descriptionHeader").classList.remove("hidden");
+ document.querySelector("#description").textContent = manifest.description;
+ }
+
+ document.querySelector("#type").classList.remove("hidden");
+
+ if (project.type == "runtimeApp") {
+ let manifestURL = AppManager.getProjectManifestURL(project);
+ document.querySelector("#type").textContent = manifest.type || "web";
+ document.querySelector("#manifestURLHeader").classList.remove("hidden");
+ document.querySelector("#manifestURL").textContent = manifestURL;
+ } else if (project.type == "mainProcess") {
+ document.querySelector("#type").textContent = project.name;
+ } else {
+ document.querySelector("#type").textContent = project.type + " " + (manifest.type || "web");
+ }
+
+ if (project.type == "packaged") {
+ let manifestURL = AppManager.getProjectManifestURL(project);
+ if (manifestURL) {
+ document.querySelector("#manifestURLHeader").classList.remove("hidden");
+ document.querySelector("#manifestURL").textContent = manifestURL;
+ }
+ }
+ }
+
+ if (project.type != "runtimeApp" && project.type != "mainProcess") {
+ ProjectBuilding.hasPrepackage(project).then(hasPrepackage => {
+ document.querySelector("#prePackageLog").hidden = !hasPrepackage;
+ });
+ }
+
+ let errorsNode = document.querySelector("#errorslist");
+ let warningsNode = document.querySelector("#warningslist");
+
+ if (project.errors) {
+ for (let e of project.errors) {
+ let li = document.createElement("li");
+ li.textContent = e;
+ errorsNode.appendChild(li);
+ }
+ }
+
+ if (project.warnings) {
+ for (let w of project.warnings) {
+ let li = document.createElement("li");
+ li.textContent = w;
+ warningsNode.appendChild(li);
+ }
+ }
+
+ AppManager.update("details");
+}
+
+function showPrepackageLog() {
+ window.top.UI.selectDeckPanel("logs");
+}
+
+function removeProject() {
+ AppManager.removeSelectedProject();
+}
diff --git a/devtools/client/webide/content/details.xhtml b/devtools/client/webide/content/details.xhtml
new file mode 100644
index 000000000..a04c37b0c
--- /dev/null
+++ b/devtools/client/webide/content/details.xhtml
@@ -0,0 +1,54 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/details.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/details.js"></script>
+ </head>
+ <body>
+
+ <div id="toolbar">
+ <button onclick="removeProject()">&details_removeProject_button;</button>
+ <p id="validation_status">
+ <span class="valid">&details_valid_header;</span>
+ <span class="warning">&details_warning_header;</span>
+ <span class="error">&details_error_header;</span>
+ </p>
+ </div>
+
+ <header>
+ <img id="icon"></img>
+ <div>
+ <h1></h1>
+ <p id="type"></p>
+ </div>
+ </header>
+
+ <main>
+ <h3 id="descriptionHeader">&details_description;</h3>
+ <p id="description"></p>
+
+ <h3 id="locationHeader">&details_location;</h3>
+ <p id="location"></p>
+
+ <h3 id="manifestURLHeader">&details_manifestURL;</h3>
+ <p id="manifestURL"></p>
+
+ <button id="prePackageLog" onclick="showPrepackageLog()" hidden="true">&details_showPrepackageLog_button;</button>
+ </main>
+
+ <ul class="validation_messages" id="errorslist"></ul>
+ <ul class="validation_messages" id="warningslist"></ul>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/devicepreferences.js b/devtools/client/webide/content/devicepreferences.js
new file mode 100644
index 000000000..14c020f12
--- /dev/null
+++ b/devtools/client/webide/content/devicepreferences.js
@@ -0,0 +1,81 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {Connection} = require("devtools/shared/client/connection-manager");
+const ConfigView = require("devtools/client/webide/modules/config-view");
+
+var configView = new ConfigView(window);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ AppManager.on("app-manager-update", OnAppManagerUpdate);
+ document.getElementById("close").onclick = CloseUI;
+ document.getElementById("device-fields").onchange = UpdateField;
+ document.getElementById("device-fields").onclick = CheckReset;
+ document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchField;
+ document.getElementById("custom-value").onclick = UpdateNewField;
+ document.getElementById("custom-value-type").onchange = ClearNewFields;
+ document.getElementById("add-custom-field").onkeyup = CheckNewFieldSubmit;
+ 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 == "runtime-global-actors") {
+ BuildUI();
+ }
+}
+
+function CheckNewFieldSubmit(event) {
+ configView.checkNewFieldSubmit(event);
+}
+
+function UpdateNewField() {
+ configView.updateNewField();
+}
+
+function ClearNewFields() {
+ configView.clearNewFields();
+}
+
+function CheckReset(event) {
+ configView.checkReset(event);
+}
+
+function UpdateField(event) {
+ configView.updateField(event);
+}
+
+function SearchField(event) {
+ configView.search(event);
+}
+
+var getAllPrefs; // Used by tests
+function BuildUI() {
+ configView.resetTable();
+
+ if (AppManager.connection &&
+ AppManager.connection.status == Connection.Status.CONNECTED &&
+ AppManager.preferenceFront) {
+ configView.front = AppManager.preferenceFront;
+ configView.kind = "Pref";
+ configView.includeTypeName = true;
+
+ getAllPrefs = AppManager.preferenceFront.getAllPrefs()
+ .then(json => configView.generateDisplay(json));
+ } else {
+ CloseUI();
+ }
+}
diff --git a/devtools/client/webide/content/devicepreferences.xhtml b/devtools/client/webide/content/devicepreferences.xhtml
new file mode 100644
index 000000000..dafb6f15f
--- /dev/null
+++ b/devtools/client/webide/content/devicepreferences.xhtml
@@ -0,0 +1,49 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/config-view.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/devicepreferences.js"></script>
+ </head>
+ <body>
+ <header>
+ <div id="controls">
+ <a id="close">&deck_close;</a>
+ </div>
+ <h1>&devicepreference_title;</h1>
+ <div id="search">
+ <input type="text" id="search-bar" placeholder="&devicepreference_search;"/>
+ </div>
+ </header>
+ <table id="device-fields">
+ <tr id="add-custom-field">
+ <td>
+ <select id="custom-value-type">
+ <option value="" selected="selected">&device_typenone;</option>
+ <option value="boolean">&device_typeboolean;</option>
+ <option value="number">&device_typenumber;</option>
+ <option value="string">&device_typestring;</option>
+ </select>
+ <input type="text" id="custom-value-name" placeholder="&devicepreference_newname;"/>
+ </td>
+ <td class="custom-input">
+ <input type="text" id="custom-value-text" placeholder="&devicepreference_newtext;"/>
+ </td>
+ <td>
+ <button id="custom-value" class="new-editable">&devicepreference_addnew;</button>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/devtools/client/webide/content/devicesettings.js b/devtools/client/webide/content/devicesettings.js
new file mode 100644
index 000000000..987df5995
--- /dev/null
+++ b/devtools/client/webide/content/devicesettings.js
@@ -0,0 +1,81 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {Connection} = require("devtools/shared/client/connection-manager");
+const ConfigView = require("devtools/client/webide/modules/config-view");
+
+var configView = new ConfigView(window);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ AppManager.on("app-manager-update", OnAppManagerUpdate);
+ document.getElementById("close").onclick = CloseUI;
+ document.getElementById("device-fields").onchange = UpdateField;
+ document.getElementById("device-fields").onclick = CheckReset;
+ document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchField;
+ document.getElementById("custom-value").onclick = UpdateNewField;
+ document.getElementById("custom-value-type").onchange = ClearNewFields;
+ document.getElementById("add-custom-field").onkeyup = CheckNewFieldSubmit;
+ 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 == "runtime-global-actors") {
+ BuildUI();
+ }
+}
+
+function CheckNewFieldSubmit(event) {
+ configView.checkNewFieldSubmit(event);
+}
+
+function UpdateNewField() {
+ configView.updateNewField();
+}
+
+function ClearNewFields() {
+ configView.clearNewFields();
+}
+
+function CheckReset(event) {
+ configView.checkReset(event);
+}
+
+function UpdateField(event) {
+ configView.updateField(event);
+}
+
+function SearchField(event) {
+ configView.search(event);
+}
+
+var getAllSettings; // Used by tests
+function BuildUI() {
+ configView.resetTable();
+
+ if (AppManager.connection &&
+ AppManager.connection.status == Connection.Status.CONNECTED &&
+ AppManager.settingsFront) {
+ configView.front = AppManager.settingsFront;
+ configView.kind = "Setting";
+ configView.includeTypeName = false;
+
+ getAllSettings = AppManager.settingsFront.getAllSettings()
+ .then(json => configView.generateDisplay(json));
+ } else {
+ CloseUI();
+ }
+}
diff --git a/devtools/client/webide/content/devicesettings.xhtml b/devtools/client/webide/content/devicesettings.xhtml
new file mode 100644
index 000000000..0406c6f07
--- /dev/null
+++ b/devtools/client/webide/content/devicesettings.xhtml
@@ -0,0 +1,50 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/config-view.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/devicesettings.js"></script>
+ </head>
+ <body>
+ <header>
+ <div id="controls">
+ <a id="close">&deck_close;</a>
+ </div>
+ <h1>&devicesetting_title;</h1>
+ <div id="search">
+ <input type="text" id="search-bar" placeholder="&devicesetting_search;"/>
+ </div>
+ </header>
+ <table id="device-fields">
+ <tr id="add-custom-field">
+ <td>
+ <select id="custom-value-type">
+ <option value="" selected="selected">&device_typenone;</option>
+ <option value="boolean">&device_typeboolean;</option>
+ <option value="number">&device_typenumber;</option>
+ <option value="string">&device_typestring;</option>
+ <option value="object">&device_typeobject;</option>
+ </select>
+ <input type="text" id="custom-value-name" placeholder="&devicesetting_newname;"/>
+ </td>
+ <td class="custom-input">
+ <input type="text" id="custom-value-text" placeholder="&devicesetting_newtext;"/>
+ </td>
+ <td>
+ <button id="custom-value" class="new-editable">&devicesetting_addnew;</button>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/devtools/client/webide/content/jar.mn b/devtools/client/webide/content/jar.mn
new file mode 100644
index 000000000..db79fdb51
--- /dev/null
+++ b/devtools/client/webide/content/jar.mn
@@ -0,0 +1,38 @@
+# 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/.
+
+webide.jar:
+% content webide %content/
+ content/webide.xul (webide.xul)
+ 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/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)
+ content/prefs.js (prefs.js)
+ content/prefs.xhtml (prefs.xhtml)
+ content/monitor.xhtml (monitor.xhtml)
+ content/monitor.js (monitor.js)
+ content/devicepreferences.js (devicepreferences.js)
+ content/devicepreferences.xhtml (devicepreferences.xhtml)
+ content/devicesettings.js (devicesettings.js)
+ content/devicesettings.xhtml (devicesettings.xhtml)
+ content/wifi-auth.js (wifi-auth.js)
+ content/wifi-auth.xhtml (wifi-auth.xhtml)
+ content/logs.xhtml (logs.xhtml)
+ content/logs.js (logs.js)
+ content/project-listing.xhtml (project-listing.xhtml)
+ content/project-listing.js (project-listing.js)
+ content/project-panel.js (project-panel.js)
+ content/runtime-panel.js (runtime-panel.js)
+ content/runtime-listing.xhtml (runtime-listing.xhtml)
+ content/runtime-listing.js (runtime-listing.js)
+ content/simulator.js (simulator.js)
+ content/simulator.xhtml (simulator.xhtml)
diff --git a/devtools/client/webide/content/logs.js b/devtools/client/webide/content/logs.js
new file mode 100644
index 000000000..157d83b67
--- /dev/null
+++ b/devtools/client/webide/content/logs.js
@@ -0,0 +1,70 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+
+ Logs.init();
+});
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+
+ Logs.uninit();
+});
+
+const Logs = {
+ init: function () {
+ this.list = document.getElementById("logs");
+
+ Logs.onAppManagerUpdate = Logs.onAppManagerUpdate.bind(this);
+ AppManager.on("app-manager-update", Logs.onAppManagerUpdate);
+
+ document.getElementById("close").onclick = Logs.close.bind(this);
+ },
+
+ uninit: function () {
+ AppManager.off("app-manager-update", Logs.onAppManagerUpdate);
+ },
+
+ onAppManagerUpdate: function (event, what, details) {
+ switch (what) {
+ case "pre-package":
+ this.prePackageLog(details);
+ break;
+ }
+ },
+
+ close: function () {
+ window.parent.UI.openProject();
+ },
+
+ prePackageLog: function (msg, details) {
+ if (msg == "start") {
+ this.clear();
+ } else if (msg == "succeed") {
+ setTimeout(function () {
+ Logs.close();
+ }, 1000);
+ } else if (msg == "failed") {
+ this.log(details);
+ } else {
+ this.log(msg);
+ }
+ },
+
+ clear: function () {
+ this.list.innerHTML = "";
+ },
+
+ log: function (msg) {
+ let line = document.createElement("li");
+ line.textContent = msg;
+ this.list.appendChild(line);
+ }
+};
diff --git a/devtools/client/webide/content/logs.xhtml b/devtools/client/webide/content/logs.xhtml
new file mode 100644
index 000000000..8d003e509
--- /dev/null
+++ b/devtools/client/webide/content/logs.xhtml
@@ -0,0 +1,33 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="resource://devtools/client/themes/common.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/logs.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"></script>
+ <script type="application/javascript;version=1.8" src="logs.js"></script>
+ </head>
+ <body>
+
+ <div id="controls">
+ <a id="close">&deck_close;</a>
+ </div>
+
+ <h1>&logs_title;</h1>
+
+ <ul id="logs" class="devtools-monospace">
+ </ul>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/monitor.js b/devtools/client/webide/content/monitor.js
new file mode 100644
index 000000000..a5d80d460
--- /dev/null
+++ b/devtools/client/webide/content/monitor.js
@@ -0,0 +1,741 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {AppActorFront} = require("devtools/shared/apps/app-actor-front");
+const {Connection} = require("devtools/shared/client/connection-manager");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ window.addEventListener("resize", Monitor.resize);
+ window.addEventListener("unload", Monitor.unload);
+
+ document.querySelector("#close").onclick = () => {
+ window.parent.UI.openProject();
+ };
+
+ Monitor.load();
+});
+
+
+/**
+ * The Monitor is a WebIDE tool used to display any kind of time-based data in
+ * the form of graphs.
+ *
+ * The data can come from a Firefox OS device, simulator, or from a WebSockets
+ * server running locally.
+ *
+ * The format of a data update is typically an object like:
+ *
+ * { graph: 'mygraph', curve: 'mycurve', value: 42, time: 1234 }
+ *
+ * or an array of such objects. For more details on the data format, see the
+ * `Graph.update(data)` method.
+ */
+var Monitor = {
+
+ apps: new Map(),
+ graphs: new Map(),
+ front: null,
+ socket: null,
+ wstimeout: null,
+ b2ginfo: false,
+ b2gtimeout: null,
+
+ /**
+ * Add new data to the graphs, create a new graph if necessary.
+ */
+ update: function (data, fallback) {
+ if (Array.isArray(data)) {
+ data.forEach(d => Monitor.update(d, fallback));
+ return;
+ }
+
+ if (Monitor.b2ginfo && data.graph === "USS") {
+ // If we're polling b2g-info, ignore USS updates from the device's
+ // USSAgents (see Monitor.pollB2GInfo()).
+ return;
+ }
+
+ if (fallback) {
+ for (let key in fallback) {
+ if (!data[key]) {
+ data[key] = fallback[key];
+ }
+ }
+ }
+
+ let graph = Monitor.graphs.get(data.graph);
+ if (!graph) {
+ let element = document.createElement("div");
+ element.classList.add("graph");
+ document.body.appendChild(element);
+
+ graph = new Graph(data.graph, element);
+ Monitor.resize(); // a scrollbar might have dis/reappeared
+ Monitor.graphs.set(data.graph, graph);
+ }
+ graph.update(data);
+ },
+
+ /**
+ * Initialize the Monitor.
+ */
+ load: function () {
+ AppManager.on("app-manager-update", Monitor.onAppManagerUpdate);
+ Monitor.connectToRuntime();
+ Monitor.connectToWebSocket();
+ },
+
+ /**
+ * Clean up the Monitor.
+ */
+ unload: function () {
+ AppManager.off("app-manager-update", Monitor.onAppManagerUpdate);
+ Monitor.disconnectFromRuntime();
+ Monitor.disconnectFromWebSocket();
+ },
+
+ /**
+ * Resize all the graphs.
+ */
+ resize: function () {
+ for (let graph of Monitor.graphs.values()) {
+ graph.resize();
+ }
+ },
+
+ /**
+ * When WebIDE connects to a new runtime, start its data forwarders.
+ */
+ onAppManagerUpdate: function (event, what, details) {
+ switch (what) {
+ case "runtime-global-actors":
+ Monitor.connectToRuntime();
+ break;
+ case "connection":
+ if (AppManager.connection.status == Connection.Status.DISCONNECTED) {
+ Monitor.disconnectFromRuntime();
+ }
+ break;
+ }
+ },
+
+ /**
+ * Use an AppActorFront on a runtime to watch track its apps.
+ */
+ connectToRuntime: function () {
+ Monitor.pollB2GInfo();
+ let client = AppManager.connection && AppManager.connection.client;
+ let resp = AppManager._listTabsResponse;
+ if (client && resp && !Monitor.front) {
+ Monitor.front = new AppActorFront(client, resp);
+ Monitor.front.watchApps(Monitor.onRuntimeAppEvent);
+ }
+ },
+
+ /**
+ * Destroy our AppActorFront.
+ */
+ disconnectFromRuntime: function () {
+ Monitor.unpollB2GInfo();
+ if (Monitor.front) {
+ Monitor.front.unwatchApps(Monitor.onRuntimeAppEvent);
+ Monitor.front = null;
+ }
+ },
+
+ /**
+ * Try connecting to a local websockets server and accept updates from it.
+ */
+ connectToWebSocket: function () {
+ let webSocketURL = Services.prefs.getCharPref("devtools.webide.monitorWebSocketURL");
+ try {
+ Monitor.socket = new WebSocket(webSocketURL);
+ Monitor.socket.onmessage = function (event) {
+ Monitor.update(JSON.parse(event.data));
+ };
+ Monitor.socket.onclose = function () {
+ Monitor.wstimeout = setTimeout(Monitor.connectToWebsocket, 1000);
+ };
+ } catch (e) {
+ Monitor.wstimeout = setTimeout(Monitor.connectToWebsocket, 1000);
+ }
+ },
+
+ /**
+ * Used when cleaning up.
+ */
+ disconnectFromWebSocket: function () {
+ clearTimeout(Monitor.wstimeout);
+ if (Monitor.socket) {
+ Monitor.socket.onclose = () => {};
+ Monitor.socket.close();
+ }
+ },
+
+ /**
+ * When an app starts on the runtime, start a monitor actor for its process.
+ */
+ onRuntimeAppEvent: function (type, app) {
+ if (type !== "appOpen" && type !== "appClose") {
+ return;
+ }
+
+ let client = AppManager.connection.client;
+ app.getForm().then(form => {
+ if (type === "appOpen") {
+ app.monitorClient = new MonitorClient(client, form);
+ app.monitorClient.start();
+ app.monitorClient.on("update", Monitor.onRuntimeUpdate);
+ Monitor.apps.set(form.monitorActor, app);
+ } else {
+ let app = Monitor.apps.get(form.monitorActor);
+ if (app) {
+ app.monitorClient.stop(() => app.monitorClient.destroy());
+ Monitor.apps.delete(form.monitorActor);
+ }
+ }
+ });
+ },
+
+ /**
+ * Accept data updates from the monitor actors of a runtime.
+ */
+ onRuntimeUpdate: function (type, packet) {
+ let fallback = {}, app = Monitor.apps.get(packet.from);
+ if (app) {
+ fallback.curve = app.manifest.name;
+ }
+ Monitor.update(packet.data, fallback);
+ },
+
+ /**
+ * Bug 1047355: If possible, parsing the output of `b2g-info` has several
+ * benefits over bug 1037465's multi-process USSAgent approach, notably:
+ * - Works for older Firefox OS devices (pre-2.1),
+ * - Doesn't need certified-apps debugging,
+ * - Polling time is synchronized for all processes.
+ * TODO: After bug 1043324 lands, consider removing this hack.
+ */
+ pollB2GInfo: function () {
+ if (AppManager.selectedRuntime) {
+ let device = AppManager.selectedRuntime.device;
+ if (device && device.shell) {
+ device.shell("b2g-info").then(s => {
+ let lines = s.split("\n");
+ let line = "";
+
+ // Find the header row to locate NAME and USS, looks like:
+ // ' NAME PID NICE USS PSS RSS VSIZE OOM_ADJ USER '.
+ while (line.indexOf("NAME") < 0) {
+ if (lines.length < 1) {
+ // Something is wrong with this output, don't trust b2g-info.
+ Monitor.unpollB2GInfo();
+ return;
+ }
+ line = lines.shift();
+ }
+ let namelength = line.indexOf("NAME") + "NAME".length;
+ let ussindex = line.slice(namelength).split(/\s+/).indexOf("USS");
+
+ // Get the NAME and USS in each following line, looks like:
+ // 'Homescreen 375 18 12.6 16.3 27.1 67.8 4 app_375'.
+ while (lines.length > 0 && lines[0].length > namelength) {
+ line = lines.shift();
+ let name = line.slice(0, namelength);
+ let uss = line.slice(namelength).split(/\s+/)[ussindex];
+ Monitor.update({
+ curve: name.trim(),
+ value: 1024 * 1024 * parseFloat(uss) // Convert MB to bytes.
+ }, {
+ // Note: We use the fallback object to set the graph name to 'USS'
+ // so that Monitor.update() can ignore USSAgent updates.
+ graph: "USS"
+ });
+ }
+ });
+ }
+ }
+ Monitor.b2ginfo = true;
+ Monitor.b2gtimeout = setTimeout(Monitor.pollB2GInfo, 350);
+ },
+
+ /**
+ * Polling b2g-info doesn't work or is no longer needed.
+ */
+ unpollB2GInfo: function () {
+ clearTimeout(Monitor.b2gtimeout);
+ Monitor.b2ginfo = false;
+ }
+
+};
+
+
+/**
+ * A MonitorClient is used as an actor client of a runtime's monitor actors,
+ * receiving its updates.
+ */
+function MonitorClient(client, form) {
+ this.client = client;
+ this.actor = form.monitorActor;
+ this.events = ["update"];
+
+ EventEmitter.decorate(this);
+ this.client.registerClient(this);
+}
+MonitorClient.prototype.destroy = function () {
+ this.client.unregisterClient(this);
+};
+MonitorClient.prototype.start = function () {
+ this.client.request({
+ to: this.actor,
+ type: "start"
+ });
+};
+MonitorClient.prototype.stop = function (callback) {
+ this.client.request({
+ to: this.actor,
+ type: "stop"
+ }, callback);
+};
+
+
+/**
+ * A Graph populates a container DOM element with an SVG graph and a legend.
+ */
+function Graph(name, element) {
+ this.name = name;
+ this.element = element;
+ this.curves = new Map();
+ this.events = new Map();
+ this.ignored = new Set();
+ this.enabled = true;
+ this.request = null;
+
+ this.x = d3.time.scale();
+ this.y = d3.scale.linear();
+
+ this.xaxis = d3.svg.axis().scale(this.x).orient("bottom");
+ this.yaxis = d3.svg.axis().scale(this.y).orient("left");
+
+ this.xformat = d3.time.format("%I:%M:%S");
+ this.yformat = this.formatter(1);
+ this.yaxis.tickFormat(this.formatter(0));
+
+ this.line = d3.svg.line().interpolate("linear")
+ .x(function (d) { return this.x(d.time); })
+ .y(function (d) { return this.y(d.value); });
+
+ this.color = d3.scale.category10();
+
+ this.svg = d3.select(element).append("svg").append("g")
+ .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
+
+ this.xelement = this.svg.append("g").attr("class", "x axis").call(this.xaxis);
+ this.yelement = this.svg.append("g").attr("class", "y axis").call(this.yaxis);
+
+ // RULERS on axes
+ let xruler = this.xruler = this.svg.select(".x.axis").append("g").attr("class", "x ruler");
+ xruler.append("line").attr("y2", 6);
+ xruler.append("line").attr("stroke-dasharray", "1,1");
+ xruler.append("text").attr("y", 9).attr("dy", ".71em");
+
+ let yruler = this.yruler = this.svg.select(".y.axis").append("g").attr("class", "y ruler");
+ yruler.append("line").attr("x2", -6);
+ yruler.append("line").attr("stroke-dasharray", "1,1");
+ yruler.append("text").attr("x", -9).attr("dy", ".32em");
+
+ let self = this;
+
+ d3.select(element).select("svg")
+ .on("mousemove", function () {
+ let mouse = d3.mouse(this);
+ self.mousex = mouse[0] - self.margin.left,
+ self.mousey = mouse[1] - self.margin.top;
+
+ xruler.attr("transform", "translate(" + self.mousex + ",0)");
+ yruler.attr("transform", "translate(0," + self.mousey + ")");
+ });
+ /* .on('mouseout', function() {
+ self.xruler.attr('transform', 'translate(-500,0)');
+ self.yruler.attr('transform', 'translate(0,-500)');
+ });*/
+ this.mousex = this.mousey = -500;
+
+ let sidebar = d3.select(this.element).append("div").attr("class", "sidebar");
+ let title = sidebar.append("label").attr("class", "graph-title");
+
+ title.append("input")
+ .attr("type", "checkbox")
+ .attr("checked", "true")
+ .on("click", function () { self.toggle(); });
+ title.append("span").text(this.name);
+
+ this.legend = sidebar.append("div").attr("class", "legend");
+
+ this.resize = this.resize.bind(this);
+ this.render = this.render.bind(this);
+ this.averages = this.averages.bind(this);
+
+ setInterval(this.averages, 1000);
+
+ this.resize();
+}
+
+Graph.prototype = {
+
+ /**
+ * These margin are used to properly position the SVG graph items inside the
+ * container element.
+ */
+ margin: {
+ top: 10,
+ right: 150,
+ bottom: 20,
+ left: 50
+ },
+
+ /**
+ * A Graph can be collapsed by the user.
+ */
+ toggle: function () {
+ if (this.enabled) {
+ this.element.classList.add("disabled");
+ this.enabled = false;
+ } else {
+ this.element.classList.remove("disabled");
+ this.enabled = true;
+ }
+ Monitor.resize();
+ },
+
+ /**
+ * If the container element is resized (e.g. because the window was resized or
+ * a scrollbar dis/appeared), the graph needs to be resized as well.
+ */
+ resize: function () {
+ let style = getComputedStyle(this.element),
+ height = parseFloat(style.height) - this.margin.top - this.margin.bottom,
+ width = parseFloat(style.width) - this.margin.left - this.margin.right;
+
+ d3.select(this.element).select("svg")
+ .attr("width", width + this.margin.left)
+ .attr("height", height + this.margin.top + this.margin.bottom);
+
+ this.x.range([0, width]);
+ this.y.range([height, 0]);
+
+ this.xelement.attr("transform", "translate(0," + height + ")");
+ this.xruler.select("line[stroke-dasharray]").attr("y2", -height);
+ this.yruler.select("line[stroke-dasharray]").attr("x2", width);
+ },
+
+ /**
+ * If the domain of the Graph's data changes (on the time axis and/or on the
+ * value axis), the axes' domains need to be updated and the graph items need
+ * to be rescaled in order to represent all the data.
+ */
+ rescale: function () {
+ let gettime = v => { return v.time; },
+ getvalue = v => { return v.value; },
+ ignored = c => { return this.ignored.has(c.id); };
+
+ let xmin = null, xmax = null, ymin = null, ymax = null;
+ for (let curve of this.curves.values()) {
+ if (ignored(curve)) {
+ continue;
+ }
+ if (xmax == null || curve.xmax > xmax) {
+ xmax = curve.xmax;
+ }
+ if (xmin == null || curve.xmin < xmin) {
+ xmin = curve.xmin;
+ }
+ if (ymax == null || curve.ymax > ymax) {
+ ymax = curve.ymax;
+ }
+ if (ymin == null || curve.ymin < ymin) {
+ ymin = curve.ymin;
+ }
+ }
+ for (let event of this.events.values()) {
+ if (ignored(event)) {
+ continue;
+ }
+ if (xmax == null || event.xmax > xmax) {
+ xmax = event.xmax;
+ }
+ if (xmin == null || event.xmin < xmin) {
+ xmin = event.xmin;
+ }
+ }
+
+ let oldxdomain = this.x.domain();
+ if (xmin != null && xmax != null) {
+ this.x.domain([xmin, xmax]);
+ let newxdomain = this.x.domain();
+ if (newxdomain[0] !== oldxdomain[0] || newxdomain[1] !== oldxdomain[1]) {
+ this.xelement.call(this.xaxis);
+ }
+ }
+
+ let oldydomain = this.y.domain();
+ if (ymin != null && ymax != null) {
+ this.y.domain([ymin, ymax]).nice();
+ let newydomain = this.y.domain();
+ if (newydomain[0] !== oldydomain[0] || newydomain[1] !== oldydomain[1]) {
+ this.yelement.call(this.yaxis);
+ }
+ }
+ },
+
+ /**
+ * Add new values to the graph.
+ */
+ update: function (data) {
+ delete data.graph;
+
+ let time = data.time || Date.now();
+ delete data.time;
+
+ let curve = data.curve;
+ delete data.curve;
+
+ // Single curve value, e.g. { curve: 'memory', value: 42, time: 1234 }.
+ if ("value" in data) {
+ this.push(this.curves, curve, [{time: time, value: data.value}]);
+ delete data.value;
+ }
+
+ // Several curve values, e.g. { curve: 'memory', values: [{value: 42, time: 1234}] }.
+ if ("values" in data) {
+ this.push(this.curves, curve, data.values);
+ delete data.values;
+ }
+
+ // Punctual event, e.g. { event: 'gc', time: 1234 },
+ // event with duration, e.g. { event: 'jank', duration: 425, time: 1234 }.
+ if ("event" in data) {
+ this.push(this.events, data.event, [{time: time, value: data.duration}]);
+ delete data.event;
+ delete data.duration;
+ }
+
+ // Remaining keys are curves, e.g. { time: 1234, memory: 42, battery: 13, temperature: 45 }.
+ for (let key in data) {
+ this.push(this.curves, key, [{time: time, value: data[key]}]);
+ }
+
+ // If no render is currently pending, request one.
+ if (this.enabled && !this.request) {
+ this.request = requestAnimationFrame(this.render);
+ }
+ },
+
+ /**
+ * Insert new data into the graph's data structures.
+ */
+ push: function (collection, id, values) {
+
+ // Note: collection is either `this.curves` or `this.events`.
+ let item = collection.get(id);
+ if (!item) {
+ item = { id: id, values: [], xmin: null, xmax: null, ymin: 0, ymax: null, average: 0 };
+ collection.set(id, item);
+ }
+
+ for (let v of values) {
+ let time = new Date(v.time), value = +v.value;
+ // Update the curve/event's domain values.
+ if (item.xmax == null || time > item.xmax) {
+ item.xmax = time;
+ }
+ if (item.xmin == null || time < item.xmin) {
+ item.xmin = time;
+ }
+ if (item.ymax == null || value > item.ymax) {
+ item.ymax = value;
+ }
+ if (item.ymin == null || value < item.ymin) {
+ item.ymin = value;
+ }
+ // Note: A curve's average is not computed here. Call `graph.averages()`.
+ item.values.push({ time: time, value: value });
+ }
+ },
+
+ /**
+ * Render the SVG graph with curves, events, crosshair and legend.
+ */
+ render: function () {
+ this.request = null;
+ this.rescale();
+
+
+ // DATA
+
+ let self = this,
+ getid = d => { return d.id; },
+ gettime = d => { return d.time.getTime(); },
+ getline = d => { return self.line(d.values); },
+ getcolor = d => { return self.color(d.id); },
+ getvalues = d => { return d.values; },
+ ignored = d => { return self.ignored.has(d.id); };
+
+ // Convert our maps to arrays for d3.
+ let curvedata = [...this.curves.values()],
+ eventdata = [...this.events.values()],
+ data = curvedata.concat(eventdata);
+
+
+ // CURVES
+
+ // Map curve data to curve elements.
+ let curves = this.svg.selectAll(".curve").data(curvedata, getid);
+
+ // Create new curves (no element corresponding to the data).
+ curves.enter().append("g").attr("class", "curve").append("path")
+ .style("stroke", getcolor);
+
+ // Delete old curves (elements corresponding to data not present anymore).
+ curves.exit().remove();
+
+ // Update all curves from data.
+ this.svg.selectAll(".curve").select("path")
+ .attr("d", d => { return ignored(d) ? "" : getline(d); });
+
+ let height = parseFloat(getComputedStyle(this.element).height) - this.margin.top - this.margin.bottom;
+
+
+ // EVENTS
+
+ // Map event data to event elements.
+ let events = this.svg.selectAll(".event-slot").data(eventdata, getid);
+
+ // Create new events.
+ events.enter().append("g").attr("class", "event-slot");
+
+ // Remove old events.
+ events.exit().remove();
+
+ // Get all occurences of an event, and map its data to them.
+ let lines = this.svg.selectAll(".event-slot")
+ .style("stroke", d => { return ignored(d) ? "none" : getcolor(d); })
+ .selectAll(".event")
+ .data(getvalues, gettime);
+
+ // Create new event occurrence.
+ lines.enter().append("line").attr("class", "event").attr("y2", height);
+
+ // Delete old event occurrence.
+ lines.exit().remove();
+
+ // Update all event occurrences from data.
+ this.svg.selectAll(".event")
+ .attr("transform", d => { return "translate(" + self.x(d.time) + ",0)"; });
+
+
+ // CROSSHAIR
+
+ // TODO select curves and events, intersect with curves and show values/hovers
+ // e.g. look like http://code.shutterstock.com/rickshaw/examples/lines.html
+
+ // Update crosshair labels on each axis.
+ this.xruler.select("text").text(self.xformat(self.x.invert(self.mousex)));
+ this.yruler.select("text").text(self.yformat(self.y.invert(self.mousey)));
+
+
+ // LEGEND
+
+ // Map data to legend elements.
+ let legends = this.legend.selectAll("label").data(data, getid);
+
+ // Update averages.
+ legends.attr("title", c => { return "Average: " + self.yformat(c.average); });
+
+ // Create new legends.
+ let newlegend = legends.enter().append("label");
+ newlegend.append("input").attr("type", "checkbox").attr("checked", "true").on("click", function (c) {
+ if (ignored(c)) {
+ this.parentElement.classList.remove("disabled");
+ self.ignored.delete(c.id);
+ } else {
+ this.parentElement.classList.add("disabled");
+ self.ignored.add(c.id);
+ }
+ self.update({}); // if no re-render is pending, request one.
+ });
+ newlegend.append("span").attr("class", "legend-color").style("background-color", getcolor);
+ newlegend.append("span").attr("class", "legend-id").text(getid);
+
+ // Delete old legends.
+ legends.exit().remove();
+ },
+
+ /**
+ * Returns a SI value formatter with a given precision.
+ */
+ formatter: function (decimals) {
+ return value => {
+ // Don't use sub-unit SI prefixes (milli, micro, etc.).
+ if (Math.abs(value) < 1) return value.toFixed(decimals);
+ // SI prefix, e.g. 1234567 will give '1.2M' at precision 1.
+ let prefix = d3.formatPrefix(value);
+ return prefix.scale(value).toFixed(decimals) + prefix.symbol;
+ };
+ },
+
+ /**
+ * Compute the average of each time series.
+ */
+ averages: function () {
+ for (let c of this.curves.values()) {
+ let length = c.values.length;
+ if (length > 0) {
+ let total = 0;
+ c.values.forEach(v => total += v.value);
+ c.average = (total / length);
+ }
+ }
+ },
+
+ /**
+ * Bisect a time serie to find the data point immediately left of `time`.
+ */
+ bisectTime: d3.bisector(d => d.time).left,
+
+ /**
+ * Get all curve values at a given time.
+ */
+ valuesAt: function (time) {
+ let values = { time: time };
+
+ for (let id of this.curves.keys()) {
+ let curve = this.curves.get(id);
+
+ // Find the closest value just before `time`.
+ let i = this.bisectTime(curve.values, time);
+ if (i < 0) {
+ // Curve starts after `time`, use first value.
+ values[id] = curve.values[0].value;
+ } else if (i > curve.values.length - 2) {
+ // Curve ends before `time`, use last value.
+ values[id] = curve.values[curve.values.length - 1].value;
+ } else {
+ // Curve has two values around `time`, interpolate.
+ let v1 = curve.values[i],
+ v2 = curve.values[i + 1],
+ delta = (time - v1.time) / (v2.time - v1.time);
+ values[id] = v1.value + (v2.value - v1.time) * delta;
+ }
+ }
+ return values;
+ }
+
+};
diff --git a/devtools/client/webide/content/monitor.xhtml b/devtools/client/webide/content/monitor.xhtml
new file mode 100644
index 000000000..552f3826c
--- /dev/null
+++ b/devtools/client/webide/content/monitor.xhtml
@@ -0,0 +1,31 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/monitor.css" type="text/css"/>
+ <script src="chrome://devtools/content/shared/vendor/d3.js"></script>
+ <script type="application/javascript;version=1.8" src="monitor.js"></script>
+ </head>
+ <body>
+
+ <div id="controls">
+ <a href="https://developer.mozilla.org/docs/Tools/WebIDE/Monitor" target="_blank">&monitor_help;</a>
+ <a id="close">&deck_close;</a>
+ </div>
+
+ <h1>&monitor_title;</h1>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/moz.build b/devtools/client/webide/content/moz.build
new file mode 100644
index 000000000..aac3a838c
--- /dev/null
+++ b/devtools/client/webide/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/devtools/client/webide/content/newapp.js b/devtools/client/webide/content/newapp.js
new file mode 100644
index 000000000..d47bfabec
--- /dev/null
+++ b/devtools/client/webide/content/newapp.js
@@ -0,0 +1,175 @@
+/* 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";
+
+var Cc = Components.classes;
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
+const Services = require("Services");
+const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
+const {AppProjects} = require("devtools/client/webide/modules/app-projects");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {getJSON} = require("devtools/client/shared/getjson");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils", "resource://gre/modules/ZipUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
+
+const TEMPLATES_URL = "devtools.webide.templatesURL";
+
+var gTemplateList = null;
+
+// See bug 989619
+console.log = console.log.bind(console);
+console.warn = console.warn.bind(console);
+console.error = console.error.bind(console);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ let projectNameNode = document.querySelector("#project-name");
+ projectNameNode.addEventListener("input", canValidate, true);
+ getTemplatesJSON();
+}, true);
+
+function getTemplatesJSON() {
+ getJSON(TEMPLATES_URL).then(list => {
+ if (!Array.isArray(list)) {
+ throw new Error("JSON response not an array");
+ }
+ if (list.length == 0) {
+ throw new Error("JSON response is an empty array");
+ }
+ gTemplateList = list;
+ let templatelistNode = document.querySelector("#templatelist");
+ templatelistNode.innerHTML = "";
+ for (let template of list) {
+ let richlistitemNode = document.createElement("richlistitem");
+ let imageNode = document.createElement("image");
+ imageNode.setAttribute("src", template.icon);
+ let labelNode = document.createElement("label");
+ labelNode.setAttribute("value", template.name);
+ let descriptionNode = document.createElement("description");
+ descriptionNode.textContent = template.description;
+ let vboxNode = document.createElement("vbox");
+ vboxNode.setAttribute("flex", "1");
+ richlistitemNode.appendChild(imageNode);
+ vboxNode.appendChild(labelNode);
+ vboxNode.appendChild(descriptionNode);
+ richlistitemNode.appendChild(vboxNode);
+ templatelistNode.appendChild(richlistitemNode);
+ }
+ templatelistNode.selectedIndex = 0;
+
+ /* Chrome mochitest support */
+ let testOptions = window.arguments[0].testOptions;
+ if (testOptions) {
+ templatelistNode.selectedIndex = testOptions.index;
+ document.querySelector("#project-name").value = testOptions.name;
+ doOK();
+ }
+ }, (e) => {
+ failAndBail("Can't download app templates: " + e);
+ });
+}
+
+function failAndBail(msg) {
+ let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+ promptService.alert(window, "error", msg);
+ window.close();
+}
+
+function canValidate() {
+ let projectNameNode = document.querySelector("#project-name");
+ let dialogNode = document.querySelector("dialog");
+ if (projectNameNode.value.length > 0) {
+ dialogNode.removeAttribute("buttondisabledaccept");
+ } else {
+ dialogNode.setAttribute("buttondisabledaccept", "true");
+ }
+}
+
+function doOK() {
+ let projectName = document.querySelector("#project-name").value;
+
+ if (!projectName) {
+ console.error("No project name");
+ return false;
+ }
+
+ if (!gTemplateList) {
+ console.error("No template index");
+ return false;
+ }
+
+ let templatelistNode = document.querySelector("#templatelist");
+ if (templatelistNode.selectedIndex < 0) {
+ console.error("No template selected");
+ return false;
+ }
+
+ let folder;
+
+ /* Chrome mochitest support */
+ let testOptions = window.arguments[0].testOptions;
+ if (testOptions) {
+ folder = testOptions.folder;
+ } else {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, "Select directory where to create app directory", Ci.nsIFilePicker.modeGetFolder);
+ let res = fp.show();
+ if (res == Ci.nsIFilePicker.returnCancel) {
+ console.error("No directory selected");
+ return false;
+ }
+ folder = fp.file;
+ }
+
+ // Create subfolder with fs-friendly name of project
+ let subfolder = projectName.replace(/[\\/:*?"<>|]/g, "").toLowerCase();
+ let win = Services.wm.getMostRecentWindow("devtools:webide");
+ folder.append(subfolder);
+
+ try {
+ folder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ } catch (e) {
+ win.UI.reportError("error_folderCreationFailed");
+ window.close();
+ return false;
+ }
+
+ // Download boilerplate zip
+ let template = gTemplateList[templatelistNode.selectedIndex];
+ let source = template.file;
+ let target = folder.clone();
+ target.append(subfolder + ".zip");
+
+ let bail = (e) => {
+ console.error(e);
+ window.close();
+ };
+
+ Downloads.fetch(source, target).then(() => {
+ ZipUtils.extractFiles(target, folder);
+ target.remove(false);
+ AppProjects.addPackaged(folder).then((project) => {
+ window.arguments[0].location = project.location;
+ AppManager.validateAndUpdateProject(project).then(() => {
+ if (project.manifest) {
+ project.manifest.name = projectName;
+ AppManager.writeManifest(project).then(() => {
+ AppManager.validateAndUpdateProject(project).then(
+ () => {window.close();}, bail);
+ }, bail);
+ } else {
+ bail("Manifest not found");
+ }
+ }, bail);
+ }, bail);
+ }, bail);
+
+ return false;
+}
diff --git a/devtools/client/webide/content/newapp.xul b/devtools/client/webide/content/newapp.xul
new file mode 100644
index 000000000..7ff083519
--- /dev/null
+++ b/devtools/client/webide/content/newapp.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!-- 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 window [
+ <!ENTITY % webideDTD SYSTEM "chrome://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://webide/skin/newapp.css"?>
+
+<dialog id="webide:newapp" title="&newAppWindowTitle;"
+ width="600" height="400"
+ buttons="accept,cancel"
+ ondialogaccept="return doOK();"
+ buttondisabledaccept="true"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="newapp.js"></script>
+ <label class="header-name" value="&newAppHeader;"/>
+
+ <richlistbox id="templatelist" flex="1">
+ <description>&newAppLoadingTemplate;</description>
+ </richlistbox>
+ <vbox>
+ <label class="header-name" control="project-name" value="&newAppProjectName;"/>
+ <textbox id="project-name"/>
+ </vbox>
+
+</dialog>
diff --git a/devtools/client/webide/content/permissionstable.js b/devtools/client/webide/content/permissionstable.js
new file mode 100644
index 000000000..22c74bd0d
--- /dev/null
+++ b/devtools/client/webide/content/permissionstable.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {Connection} = require("devtools/shared/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 == "runtime-global-actors") {
+ BuildUI();
+ }
+}
+
+function generateFields(json) {
+ let table = document.querySelector("table");
+ 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);
+ }
+}
+
+var 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()
+ .then(json => generateFields(json));
+ } else {
+ CloseUI();
+ }
+}
diff --git a/devtools/client/webide/content/permissionstable.xhtml b/devtools/client/webide/content/permissionstable.xhtml
new file mode 100644
index 000000000..361cfece8
--- /dev/null
+++ b/devtools/client/webide/content/permissionstable.xhtml
@@ -0,0 +1,36 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/permissionstable.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>
diff --git a/devtools/client/webide/content/prefs.js b/devtools/client/webide/content/prefs.js
new file mode 100644
index 000000000..75f6233ba
--- /dev/null
+++ b/devtools/client/webide/content/prefs.js
@@ -0,0 +1,108 @@
+/* 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";
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+
+ // Listen to preference changes
+ let inputs = document.querySelectorAll("[data-pref]");
+ for (let i of inputs) {
+ let pref = i.dataset.pref;
+ Services.prefs.addObserver(pref, FillForm, false);
+ i.addEventListener("change", SaveForm, false);
+ }
+
+ // Buttons
+ document.querySelector("#close").onclick = CloseUI;
+ document.querySelector("#restore").onclick = RestoreDefaults;
+ document.querySelector("#manageComponents").onclick = ShowAddons;
+
+ // Initialize the controls
+ FillForm();
+
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ let inputs = document.querySelectorAll("[data-pref]");
+ for (let i of inputs) {
+ let pref = i.dataset.pref;
+ i.removeEventListener("change", SaveForm, false);
+ Services.prefs.removeObserver(pref, FillForm, false);
+ }
+}, true);
+
+function CloseUI() {
+ window.parent.UI.openProject();
+}
+
+function ShowAddons() {
+ window.parent.Cmds.showAddons();
+}
+
+function FillForm() {
+ let inputs = document.querySelectorAll("[data-pref]");
+ for (let i of inputs) {
+ let pref = i.dataset.pref;
+ let val = GetPref(pref);
+ if (i.type == "checkbox") {
+ i.checked = val;
+ } else {
+ i.value = val;
+ }
+ }
+}
+
+function SaveForm(e) {
+ let inputs = document.querySelectorAll("[data-pref]");
+ for (let i of inputs) {
+ let pref = i.dataset.pref;
+ if (i.type == "checkbox") {
+ SetPref(pref, i.checked);
+ } else {
+ SetPref(pref, i.value);
+ }
+ }
+}
+
+function GetPref(name) {
+ let type = Services.prefs.getPrefType(name);
+ switch (type) {
+ case Services.prefs.PREF_STRING:
+ return Services.prefs.getCharPref(name);
+ case Services.prefs.PREF_INT:
+ return Services.prefs.getIntPref(name);
+ case Services.prefs.PREF_BOOL:
+ return Services.prefs.getBoolPref(name);
+ default:
+ throw new Error("Unknown type");
+ }
+}
+
+function SetPref(name, value) {
+ let type = Services.prefs.getPrefType(name);
+ switch (type) {
+ case Services.prefs.PREF_STRING:
+ return Services.prefs.setCharPref(name, value);
+ case Services.prefs.PREF_INT:
+ return Services.prefs.setIntPref(name, value);
+ case Services.prefs.PREF_BOOL:
+ return Services.prefs.setBoolPref(name, value);
+ default:
+ throw new Error("Unknown type");
+ }
+}
+
+function RestoreDefaults() {
+ let inputs = document.querySelectorAll("[data-pref]");
+ for (let i of inputs) {
+ let pref = i.dataset.pref;
+ Services.prefs.clearUserPref(pref);
+ }
+}
diff --git a/devtools/client/webide/content/prefs.xhtml b/devtools/client/webide/content/prefs.xhtml
new file mode 100644
index 000000000..726ca772c
--- /dev/null
+++ b/devtools/client/webide/content/prefs.xhtml
@@ -0,0 +1,112 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/prefs.js"></script>
+ </head>
+ <body>
+
+ <div id="controls">
+ <a id="restore">&prefs_restore;</a>
+ <a id="manageComponents">&prefs_manage_components;</a>
+ <a id="close">&deck_close;</a>
+ </div>
+
+ <h1>&prefs_title;</h1>
+
+ <h2>&prefs_general_title;</h2>
+
+ <ul>
+ <li>
+ <label title="&prefs_options_showeditor_tooltip;">
+ <input type="checkbox" data-pref="devtools.webide.showProjectEditor"/>
+ <span>&prefs_options_showeditor;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_rememberlastproject_tooltip;">
+ <input type="checkbox" data-pref="devtools.webide.restoreLastProject"/>
+ <span>&prefs_options_rememberlastproject;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_autoconnectruntime_tooltip;">
+ <input type="checkbox" data-pref="devtools.webide.autoConnectRuntime"/>
+ <span>&prefs_options_autoconnectruntime;</span>
+ </label>
+ </li>
+ <li>
+ <label class="text-input" title="&prefs_options_templatesurl_tooltip;">
+ <span>&prefs_options_templatesurl;</span>
+ <input data-pref="devtools.webide.templatesURL"/>
+ </label>
+ </li>
+ </ul>
+
+ <h2>&prefs_editor_title;</h2>
+
+ <ul>
+ <li>
+ <label><span>&prefs_options_tabsize;</span>
+ <select data-pref="devtools.editor.tabsize">
+ <option value="2">2</option>
+ <option value="4">4</option>
+ <option value="8">8</option>
+ </select>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_expandtab_tooltip;">
+ <input type="checkbox" data-pref="devtools.editor.expandtab"/>
+ <span>&prefs_options_expandtab;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_detectindentation_tooltip;">
+ <input type="checkbox" data-pref="devtools.editor.detectindentation"/>
+ <span>&prefs_options_detectindentation;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_autocomplete_tooltip;">
+ <input type="checkbox" data-pref="devtools.editor.autocomplete"/>
+ <span>&prefs_options_autocomplete;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_autoclosebrackets_tooltip;">
+ <input type="checkbox" data-pref="devtools.editor.autoclosebrackets"/>
+ <span>&prefs_options_autoclosebrackets;</span>
+ </label>
+ </li>
+ <li>
+ <label title="&prefs_options_autosavefiles_tooltip;">
+ <input type="checkbox" data-pref="devtools.webide.autosaveFiles"/>
+ <span>&prefs_options_autosavefiles;</span>
+ </label>
+ </li>
+ <li>
+ <label><span>&prefs_options_keybindings;</span>
+ <select data-pref="devtools.editor.keymap">
+ <option value="default">&prefs_options_keybindings_default;</option>
+ <option value="vim">Vim</option>
+ <option value="emacs">Emacs</option>
+ <option value="sublime">Sublime</option>
+ </select>
+ </label>
+ </li>
+ </ul>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/project-listing.js b/devtools/client/webide/content/project-listing.js
new file mode 100644
index 000000000..5641f6c0c
--- /dev/null
+++ b/devtools/client/webide/content/project-listing.js
@@ -0,0 +1,42 @@
+/* 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/. */
+
+/* eslint-env browser */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const ProjectList = require("devtools/client/webide/modules/project-list");
+
+var projectList = new ProjectList(window, window.parent);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, true);
+ document.getElementById("new-app").onclick = CreateNewApp;
+ document.getElementById("hosted-app").onclick = ImportHostedApp;
+ document.getElementById("packaged-app").onclick = ImportPackagedApp;
+ document.getElementById("refresh-tabs").onclick = RefreshTabs;
+ projectList.update();
+ projectList.updateCommands();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ projectList.destroy();
+});
+
+function RefreshTabs() {
+ projectList.refreshTabs();
+}
+
+function CreateNewApp() {
+ projectList.newApp();
+}
+
+function ImportHostedApp() {
+ projectList.importHostedApp();
+}
+
+function ImportPackagedApp() {
+ projectList.importPackagedApp();
+}
diff --git a/devtools/client/webide/content/project-listing.xhtml b/devtools/client/webide/content/project-listing.xhtml
new file mode 100644
index 000000000..337befe5d
--- /dev/null
+++ b/devtools/client/webide/content/project-listing.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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/panel-listing.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/project-listing.js"></script>
+ </head>
+ <body>
+ <div id="project-panel">
+ <div id="project-panel-box">
+ <button class="panel-item project-panel-item-newapp" id="new-app">&projectMenu_newApp_label;</button>
+ <button class="panel-item project-panel-item-openpackaged" id="packaged-app">&projectMenu_importPackagedApp_label;</button>
+ <button class="panel-item project-panel-item-openhosted" id="hosted-app">&projectMenu_importHostedApp_label;</button>
+ <label class="panel-header">&projectPanel_myProjects;</label>
+ <div id="project-panel-projects"></div>
+ <label class="panel-header" id="panel-header-runtimeapps" hidden="true">&projectPanel_runtimeApps;</label>
+ <div id="project-panel-runtimeapps"/>
+ <label class="panel-header" id="panel-header-tabs" hidden="true">&projectPanel_tabs;
+ <button class="project-panel-item-refreshtabs refresh-icon" id="refresh-tabs" title="&projectMenu_refreshTabs_label;"></button>
+ </label>
+ <div id="project-panel-tabs"/>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/devtools/client/webide/content/project-panel.js b/devtools/client/webide/content/project-panel.js
new file mode 100644
index 000000000..54eab8251
--- /dev/null
+++ b/devtools/client/webide/content/project-panel.js
@@ -0,0 +1,11 @@
+/* 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/. */
+
+var ProjectPanel = {
+ // TODO: Expand function to save toggle state.
+ toggleSidebar: function () {
+ document.querySelector("#project-listing-panel").setAttribute("sidebar-displayed", true);
+ document.querySelector("#project-listing-splitter").setAttribute("sidebar-displayed", true);
+ }
+};
diff --git a/devtools/client/webide/content/runtime-listing.js b/devtools/client/webide/content/runtime-listing.js
new file mode 100644
index 000000000..0a1a40a2a
--- /dev/null
+++ b/devtools/client/webide/content/runtime-listing.js
@@ -0,0 +1,66 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const RuntimeList = require("devtools/client/webide/modules/runtime-list");
+
+var runtimeList = new RuntimeList(window, window.parent);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, true);
+ document.getElementById("runtime-screenshot").onclick = TakeScreenshot;
+ document.getElementById("runtime-permissions").onclick = ShowPermissionsTable;
+ document.getElementById("runtime-details").onclick = ShowRuntimeDetails;
+ document.getElementById("runtime-disconnect").onclick = DisconnectRuntime;
+ document.getElementById("runtime-preferences").onclick = ShowDevicePreferences;
+ document.getElementById("runtime-settings").onclick = ShowSettings;
+ document.getElementById("runtime-panel-installsimulator").onclick = ShowAddons;
+ document.getElementById("runtime-panel-noadbhelper").onclick = ShowAddons;
+ document.getElementById("runtime-panel-nousbdevice").onclick = ShowTroubleShooting;
+ document.getElementById("refresh-devices").onclick = RefreshScanners;
+ runtimeList.update();
+ runtimeList.updateCommands();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ runtimeList.destroy();
+});
+
+function TakeScreenshot() {
+ runtimeList.takeScreenshot();
+}
+
+function ShowRuntimeDetails() {
+ runtimeList.showRuntimeDetails();
+}
+
+function ShowPermissionsTable() {
+ runtimeList.showPermissionsTable();
+}
+
+function ShowDevicePreferences() {
+ runtimeList.showDevicePreferences();
+}
+
+function ShowSettings() {
+ runtimeList.showSettings();
+}
+
+function RefreshScanners() {
+ runtimeList.refreshScanners();
+}
+
+function DisconnectRuntime() {
+ window.parent.Cmds.disconnectRuntime();
+}
+
+function ShowAddons() {
+ runtimeList.showAddons();
+}
+
+function ShowTroubleShooting() {
+ runtimeList.showTroubleShooting();
+}
diff --git a/devtools/client/webide/content/runtime-listing.xhtml b/devtools/client/webide/content/runtime-listing.xhtml
new file mode 100644
index 000000000..f648fac12
--- /dev/null
+++ b/devtools/client/webide/content/runtime-listing.xhtml
@@ -0,0 +1,45 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/panel-listing.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/runtime-listing.js"></script>
+ </head>
+ <body>
+ <div id="runtime-panel">
+ <div id="runtime-panel-box">
+ <label class="panel-header">&runtimePanel_usb;
+ <button class="runtime-panel-item-refreshdevices refresh-icon" id="refresh-devices" title="&runtimePanel_refreshDevices_label;"></button>
+ </label>
+ <button class="panel-item" id="runtime-panel-nousbdevice">&runtimePanel_nousbdevice;</button>
+ <button class="panel-item" id="runtime-panel-noadbhelper">&runtimePanel_noadbhelper;</button>
+ <div id="runtime-panel-usb"></div>
+ <label class="panel-header" id="runtime-header-wifi">&runtimePanel_wifi;</label>
+ <div id="runtime-panel-wifi"></div>
+ <label class="panel-header">&runtimePanel_simulator;</label>
+ <div id="runtime-panel-simulator"></div>
+ <button class="panel-item" id="runtime-panel-installsimulator">&runtimePanel_installsimulator;</button>
+ <label class="panel-header">&runtimePanel_other;</label>
+ <div id="runtime-panel-other"></div>
+ <div id="runtime-actions">
+ <button class="panel-item" id="runtime-details">&runtimeMenu_showDetails_label;</button>
+ <button class="panel-item" id="runtime-permissions">&runtimeMenu_showPermissionTable_label;</button>
+ <button class="panel-item" id="runtime-preferences">&runtimeMenu_showDevicePrefs_label;</button>
+ <button class="panel-item" id="runtime-settings">&runtimeMenu_showSettings_label;</button>
+ <button class="panel-item" id="runtime-screenshot">&runtimeMenu_takeScreenshot_label;</button>
+ <button class="panel-item" id="runtime-disconnect">&runtimeMenu_disconnect_label;</button>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/devtools/client/webide/content/runtime-panel.js b/devtools/client/webide/content/runtime-panel.js
new file mode 100644
index 000000000..3646fa15c
--- /dev/null
+++ b/devtools/client/webide/content/runtime-panel.js
@@ -0,0 +1,11 @@
+/* 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/. */
+
+var RuntimePanel = {
+ // TODO: Expand function to save toggle state.
+ toggleSidebar: function () {
+ document.querySelector("#runtime-listing-panel").setAttribute("sidebar-displayed", true);
+ document.querySelector("#runtime-listing-splitter").setAttribute("sidebar-displayed", true);
+ }
+};
diff --git a/devtools/client/webide/content/runtimedetails.js b/devtools/client/webide/content/runtimedetails.js
new file mode 100644
index 000000000..dea423e81
--- /dev/null
+++ b/devtools/client/webide/content/runtimedetails.js
@@ -0,0 +1,153 @@
+/* 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/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const {Connection} = require("devtools/shared/client/connection-manager");
+const {RuntimeTypes} = require("devtools/client/webide/modules/runtimes");
+const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
+
+const UNRESTRICTED_HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Running_and_debugging_apps#Unrestricted_app_debugging_%28including_certified_apps_main_process_etc.%29";
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ document.querySelector("#close").onclick = CloseUI;
+ document.querySelector("#devtools-check button").onclick = EnableCertApps;
+ document.querySelector("#adb-check button").onclick = RootADB;
+ document.querySelector("#unrestricted-privileges").onclick = function () {
+ window.parent.UI.openInBrowser(UNRESTRICTED_HELP_URL);
+ };
+ AppManager.on("app-manager-update", OnAppManagerUpdate);
+ BuildUI();
+ CheckLockState();
+}, 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 == "runtime-global-actors") {
+ BuildUI();
+ CheckLockState();
+ }
+}
+
+function generateFields(json) {
+ let table = document.querySelector("table");
+ 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);
+ }
+}
+
+var 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()
+ .then(json => generateFields(json));
+ } else {
+ CloseUI();
+ }
+}
+
+function CheckLockState() {
+ let adbCheckResult = document.querySelector("#adb-check > .yesno");
+ let devtoolsCheckResult = document.querySelector("#devtools-check > .yesno");
+ let flipCertPerfButton = document.querySelector("#devtools-check button");
+ let adbRootButton = document.querySelector("#adb-check button");
+ let flipCertPerfAction = document.querySelector("#devtools-check > .action");
+ let adbRootAction = document.querySelector("#adb-check > .action");
+
+ let sYes = Strings.GetStringFromName("runtimedetails_checkyes");
+ let sNo = Strings.GetStringFromName("runtimedetails_checkno");
+ let sUnknown = Strings.GetStringFromName("runtimedetails_checkunknown");
+ let sNotUSB = Strings.GetStringFromName("runtimedetails_notUSBDevice");
+
+ flipCertPerfButton.setAttribute("disabled", "true");
+ flipCertPerfAction.setAttribute("hidden", "true");
+ adbRootAction.setAttribute("hidden", "true");
+
+ adbCheckResult.textContent = sUnknown;
+ devtoolsCheckResult.textContent = sUnknown;
+
+ if (AppManager.connection &&
+ AppManager.connection.status == Connection.Status.CONNECTED) {
+
+ // ADB check
+ if (AppManager.selectedRuntime.type === RuntimeTypes.USB) {
+ let device = AppManager.selectedRuntime.device;
+ if (device && device.summonRoot) {
+ device.isRoot().then(isRoot => {
+ if (isRoot) {
+ adbCheckResult.textContent = sYes;
+ flipCertPerfButton.removeAttribute("disabled");
+ } else {
+ adbCheckResult.textContent = sNo;
+ adbRootAction.removeAttribute("hidden");
+ }
+ }, e => console.error(e));
+ } else {
+ adbCheckResult.textContent = sUnknown;
+ }
+ } else {
+ adbCheckResult.textContent = sNotUSB;
+ }
+
+ // forbid-certified-apps check
+ try {
+ let prefFront = AppManager.preferenceFront;
+ prefFront.getBoolPref("devtools.debugger.forbid-certified-apps").then(isForbidden => {
+ if (isForbidden) {
+ devtoolsCheckResult.textContent = sNo;
+ flipCertPerfAction.removeAttribute("hidden");
+ } else {
+ devtoolsCheckResult.textContent = sYes;
+ }
+ }, e => console.error(e));
+ } catch (e) {
+ // Exception. pref actor is only accessible if forbird-certified-apps is false
+ devtoolsCheckResult.textContent = sNo;
+ flipCertPerfAction.removeAttribute("hidden");
+ }
+
+ }
+
+}
+
+function EnableCertApps() {
+ let device = AppManager.selectedRuntime.device;
+ // TODO: Remove `network.disable.ipc.security` once bug 1125916 is fixed.
+ device.shell(
+ "stop b2g && " +
+ "cd /data/b2g/mozilla/*.default/ && " +
+ "echo 'user_pref(\"devtools.debugger.forbid-certified-apps\", false);' >> prefs.js && " +
+ "echo 'user_pref(\"dom.apps.developer_mode\", true);' >> prefs.js && " +
+ "echo 'user_pref(\"network.disable.ipc.security\", true);' >> prefs.js && " +
+ "echo 'user_pref(\"dom.webcomponents.enabled\", true);' >> prefs.js && " +
+ "start b2g"
+ );
+}
+
+function RootADB() {
+ let device = AppManager.selectedRuntime.device;
+ device.summonRoot().then(CheckLockState, (e) => console.error(e));
+}
diff --git a/devtools/client/webide/content/runtimedetails.xhtml b/devtools/client/webide/content/runtimedetails.xhtml
new file mode 100644
index 000000000..b2f74728a
--- /dev/null
+++ b/devtools/client/webide/content/runtimedetails.xhtml
@@ -0,0 +1,46 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/runtimedetails.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>
+
+ <div id="devicePrivileges">
+ <p id="adb-check">
+ &runtimedetails_adbIsRoot;<span class="yesno"></span>
+ <div class="action">
+ <button>&runtimedetails_summonADBRoot;</button>
+ <em>&runtimedetails_ADBRootWarning;</em>
+ </div>
+ </p>
+ <p id="devtools-check">
+ <a id="unrestricted-privileges">&runtimedetails_unrestrictedPrivileges;</a><span class="yesno"></span>
+ <div class="action">
+ <button>&runtimedetails_requestPrivileges;</button>
+ <em>&runtimedetails_privilegesWarning;</em>
+ </div>
+ </p>
+ </div>
+
+ <table></table>
+ </body>
+</html>
diff --git a/devtools/client/webide/content/simulator.js b/devtools/client/webide/content/simulator.js
new file mode 100644
index 000000000..ddc1cbed1
--- /dev/null
+++ b/devtools/client/webide/content/simulator.js
@@ -0,0 +1,352 @@
+/* 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/. */
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { getDevices, getDeviceString } = require("devtools/client/shared/devices");
+const { Simulators, Simulator } = require("devtools/client/webide/modules/simulators");
+const Services = require("Services");
+const EventEmitter = require("devtools/shared/event-emitter");
+const promise = require("promise");
+const utils = require("devtools/client/webide/modules/utils");
+
+const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
+
+var SimulatorEditor = {
+
+ // Available Firefox OS Simulator addons (key: `addon.id`).
+ _addons: {},
+
+ // Available device simulation profiles (key: `device.name`).
+ _devices: {},
+
+ // The names of supported simulation options.
+ _deviceOptions: [],
+
+ // The <form> element used to edit Simulator options.
+ _form: null,
+
+ // The Simulator object being edited.
+ _simulator: null,
+
+ // Generate the dynamic form elements.
+ init() {
+ let promises = [];
+
+ // Grab the <form> element.
+ let form = this._form;
+ if (!form) {
+ // This is the first time we run `init()`, bootstrap some things.
+ form = this._form = document.querySelector("#simulator-editor");
+ form.addEventListener("change", this.update.bind(this));
+ Simulators.on("configure", (e, simulator) => { this.edit(simulator); });
+ // Extract the list of device simulation options we'll support.
+ let deviceFields = form.querySelectorAll("*[data-device]");
+ this._deviceOptions = Array.map(deviceFields, field => field.name);
+ }
+
+ // Append a new <option> to a <select> (or <optgroup>) element.
+ function opt(select, value, text) {
+ let option = document.createElement("option");
+ option.value = value;
+ option.textContent = text;
+ select.appendChild(option);
+ }
+
+ // Generate B2G version selector.
+ promises.push(Simulators.findSimulatorAddons().then(addons => {
+ this._addons = {};
+ form.version.innerHTML = "";
+ form.version.classList.remove("custom");
+ addons.forEach(addon => {
+ this._addons[addon.id] = addon;
+ opt(form.version, addon.id, addon.name);
+ });
+ opt(form.version, "custom", "");
+ opt(form.version, "pick", Strings.GetStringFromName("simulator_custom_binary"));
+ }));
+
+ // Generate profile selector.
+ form.profile.innerHTML = "";
+ form.profile.classList.remove("custom");
+ opt(form.profile, "default", Strings.GetStringFromName("simulator_default_profile"));
+ opt(form.profile, "custom", "");
+ opt(form.profile, "pick", Strings.GetStringFromName("simulator_custom_profile"));
+
+ // Generate example devices list.
+ form.device.innerHTML = "";
+ form.device.classList.remove("custom");
+ opt(form.device, "custom", Strings.GetStringFromName("simulator_custom_device"));
+ promises.push(getDevices().then(devices => {
+ devices.TYPES.forEach(type => {
+ let b2gDevices = devices[type].filter(d => d.firefoxOS);
+ if (b2gDevices.length < 1) {
+ return;
+ }
+ let optgroup = document.createElement("optgroup");
+ optgroup.label = getDeviceString(type);
+ b2gDevices.forEach(device => {
+ this._devices[device.name] = device;
+ opt(optgroup, device.name, device.name);
+ });
+ form.device.appendChild(optgroup);
+ });
+ }));
+
+ return promise.all(promises);
+ },
+
+ // Edit the configuration of an existing Simulator, or create a new one.
+ edit(simulator) {
+ // If no Simulator was given to edit, we're creating a new one.
+ if (!simulator) {
+ simulator = new Simulator(); // Default options.
+ Simulators.add(simulator);
+ }
+
+ this._simulator = null;
+
+ return this.init().then(() => {
+ this._simulator = simulator;
+
+ // Update the form fields.
+ this._form.name.value = simulator.name;
+
+ this.updateVersionSelector();
+ this.updateProfileSelector();
+ this.updateDeviceSelector();
+ this.updateDeviceFields();
+
+ // Change visibility of 'TV Simulator Menu'.
+ let tvSimMenu = document.querySelector("#tv_simulator_menu");
+ tvSimMenu.style.visibility = (this._simulator.type === "television") ?
+ "visible" : "hidden";
+
+ // Trigger any listener waiting for this update
+ let change = document.createEvent("HTMLEvents");
+ change.initEvent("change", true, true);
+ this._form.dispatchEvent(change);
+ });
+ },
+
+ // Open the directory of TV Simulator config.
+ showTVConfigDirectory() {
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ profD.append("extensions");
+ profD.append(this._simulator.addon.id);
+ profD.append("profile");
+ profD.append("dummy");
+ let profileDir = profD.path;
+
+ // Show the profile directory.
+ let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+ new nsLocalFile(profileDir).reveal();
+ },
+
+ // Close the configuration panel.
+ close() {
+ this._simulator = null;
+ window.parent.UI.openProject();
+ },
+
+ // Restore the simulator to its default configuration.
+ restoreDefaults() {
+ let simulator = this._simulator;
+ this.version = simulator.addon.id;
+ this.profile = "default";
+ simulator.restoreDefaults();
+ Simulators.emitUpdated();
+ return this.edit(simulator);
+ },
+
+ // Delete this simulator.
+ deleteSimulator() {
+ Simulators.remove(this._simulator);
+ this.close();
+ },
+
+ // Select an available option, or set the "custom" option.
+ updateSelector(selector, value) {
+ selector.value = value;
+ if (selector.selectedIndex == -1) {
+ selector.value = "custom";
+ selector.classList.add("custom");
+ selector[selector.selectedIndex].textContent = value;
+ }
+ },
+
+ // VERSION: Can be an installed `addon.id` or a custom binary path.
+
+ get version() {
+ return this._simulator.options.b2gBinary || this._simulator.addon.id;
+ },
+
+ set version(value) {
+ let form = this._form;
+ let simulator = this._simulator;
+ let oldVer = simulator.version;
+ if (this._addons[value]) {
+ // `value` is a simulator addon ID.
+ simulator.addon = this._addons[value];
+ simulator.options.b2gBinary = null;
+ } else {
+ // `value` is a custom binary path.
+ simulator.options.b2gBinary = value;
+ // TODO (Bug 1146531) Indicate that a custom profile is now required.
+ }
+ // If `form.name` contains the old version, update its last occurrence.
+ if (form.name.value.includes(oldVer) && simulator.version !== oldVer) {
+ let regex = new RegExp("(.*)" + oldVer);
+ let name = form.name.value.replace(regex, "$1" + simulator.version);
+ simulator.options.name = form.name.value = Simulators.uniqueName(name);
+ }
+ },
+
+ updateVersionSelector() {
+ this.updateSelector(this._form.version, this.version);
+ },
+
+ // PROFILE. Can be "default" or a custom profile directory path.
+
+ get profile() {
+ return this._simulator.options.gaiaProfile || "default";
+ },
+
+ set profile(value) {
+ this._simulator.options.gaiaProfile = (value == "default" ? null : value);
+ },
+
+ updateProfileSelector() {
+ this.updateSelector(this._form.profile, this.profile);
+ },
+
+ // DEVICE. Can be an existing `device.name` or "custom".
+
+ get device() {
+ let devices = this._devices;
+ let simulator = this._simulator;
+
+ // Search for the name of a device matching current simulator options.
+ for (let name in devices) {
+ let match = true;
+ for (let option of this._deviceOptions) {
+ if (simulator.options[option] === devices[name][option]) {
+ continue;
+ }
+ match = false;
+ break;
+ }
+ if (match) {
+ return name;
+ }
+ }
+ return "custom";
+ },
+
+ set device(name) {
+ let device = this._devices[name];
+ if (!device) {
+ return;
+ }
+ let form = this._form;
+ let simulator = this._simulator;
+ this._deviceOptions.forEach(option => {
+ simulator.options[option] = form[option].value = device[option] || null;
+ });
+ // TODO (Bug 1146531) Indicate when a custom profile is required (e.g. for
+ // tablet, TV…).
+ },
+
+ updateDeviceSelector() {
+ this.updateSelector(this._form.device, this.device);
+ },
+
+ // Erase any current values, trust only the `simulator.options`.
+ updateDeviceFields() {
+ let form = this._form;
+ let simulator = this._simulator;
+ this._deviceOptions.forEach(option => {
+ form[option].value = simulator.options[option];
+ });
+ },
+
+ // Handle a change in our form's fields.
+ update(event) {
+ let simulator = this._simulator;
+ if (!simulator) {
+ return;
+ }
+ let form = this._form;
+ let input = event.target;
+ switch (input.name) {
+ case "name":
+ simulator.options.name = input.value;
+ break;
+ case "version":
+ switch (input.value) {
+ case "pick":
+ let file = utils.getCustomBinary(window);
+ if (file) {
+ this.version = file.path;
+ }
+ // Whatever happens, don't stay on the "pick" option.
+ this.updateVersionSelector();
+ break;
+ case "custom":
+ this.version = input[input.selectedIndex].textContent;
+ break;
+ default:
+ this.version = input.value;
+ }
+ break;
+ case "profile":
+ switch (input.value) {
+ case "pick":
+ let directory = utils.getCustomProfile(window);
+ if (directory) {
+ this.profile = directory.path;
+ }
+ // Whatever happens, don't stay on the "pick" option.
+ this.updateProfileSelector();
+ break;
+ case "custom":
+ this.profile = input[input.selectedIndex].textContent;
+ break;
+ default:
+ this.profile = input.value;
+ }
+ break;
+ case "device":
+ this.device = input.value;
+ break;
+ default:
+ simulator.options[input.name] = input.value || null;
+ this.updateDeviceSelector();
+ }
+ Simulators.emitUpdated();
+ },
+};
+
+window.addEventListener("load", function onLoad() {
+ document.querySelector("#close").onclick = e => {
+ SimulatorEditor.close();
+ };
+ document.querySelector("#reset").onclick = e => {
+ SimulatorEditor.restoreDefaults();
+ };
+ document.querySelector("#remove").onclick = e => {
+ SimulatorEditor.deleteSimulator();
+ };
+
+ // We just loaded, so we probably missed the first configure request.
+ SimulatorEditor.edit(Simulators._lastConfiguredSimulator);
+
+ document.querySelector("#open-tv-dummy-directory").onclick = e => {
+ SimulatorEditor.showTVConfigDirectory();
+ e.preventDefault();
+ };
+});
diff --git a/devtools/client/webide/content/simulator.xhtml b/devtools/client/webide/content/simulator.xhtml
new file mode 100644
index 000000000..3ab916248
--- /dev/null
+++ b/devtools/client/webide/content/simulator.xhtml
@@ -0,0 +1,99 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/simulator.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/simulator.js"></script>
+ </head>
+ <body>
+
+ <div id="controls">
+ <a id="remove" class="hidden">&simulator_remove;</a>
+ <a id="reset">&simulator_reset;</a>
+ <a id="close">&deck_close;</a>
+ </div>
+
+ <form id="simulator-editor">
+
+ <h1>&simulator_title;</h1>
+
+ <h2>&simulator_software;</h2>
+
+ <ul>
+ <li>
+ <label>
+ <span class="label">&simulator_name;</span>
+ <input type="text" name="name"/>
+ </label>
+ </li>
+ <li>
+ <label>
+ <span class="label">&simulator_version;</span>
+ <select name="version"/>
+ </label>
+ </li>
+ <li>
+ <label>
+ <span class="label">&simulator_profile;</span>
+ <select name="profile"/>
+ </label>
+ </li>
+ </ul>
+
+ <h2>&simulator_hardware;</h2>
+
+ <ul>
+ <li>
+ <label>
+ <span class="label">&simulator_device;</span>
+ <select name="device"/>
+ </label>
+ </li>
+ <li>
+ <label>
+ <span class="label">&simulator_screenSize;</span>
+ <input name="width" data-device="" type="number"/>
+ <span>×</span>
+ <input name="height" data-device="" type="number"/>
+ </label>
+ </li>
+ <li class="hidden">
+ <label>
+ <span class="label">&simulator_pixelRatio;</span>
+ <input name="pixelRatio" data-device="" type="number" step="0.05"/>
+ </label>
+ </li>
+ </ul>
+
+ <!-- This menu is shown when simulator type is television-->
+ <p id="tv_simulator_menu" style="visibility:hidden;">
+ <h2>&simulator_tv_data;</h2>
+
+ <ul>
+ <li>
+ <label>
+ <span class="label">&simulator_tv_data_open;</span>
+ <button id="open-tv-dummy-directory">
+ &simulator_tv_data_open_button;
+ </button>
+ </label>
+ </li>
+ </ul>
+
+ </p>
+
+ </form>
+
+ </body>
+</html>
diff --git a/devtools/client/webide/content/webide.js b/devtools/client/webide/content/webide.js
new file mode 100644
index 000000000..c222332e3
--- /dev/null
+++ b/devtools/client/webide/content/webide.js
@@ -0,0 +1,1157 @@
+/* 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/. */
+
+var Cc = Components.classes;
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
+const {Toolbox} = require("devtools/client/framework/toolbox");
+const Services = require("Services");
+const {AppProjects} = require("devtools/client/webide/modules/app-projects");
+const {Connection} = require("devtools/shared/client/connection-manager");
+const {AppManager} = require("devtools/client/webide/modules/app-manager");
+const EventEmitter = require("devtools/shared/event-emitter");
+const promise = require("promise");
+const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
+const {GetAvailableAddons} = require("devtools/client/webide/modules/addons");
+const {getJSON} = require("devtools/client/shared/getjson");
+const utils = require("devtools/client/webide/modules/utils");
+const Telemetry = require("devtools/client/shared/telemetry");
+const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
+const {showDoorhanger} = require("devtools/client/shared/doorhanger");
+const {Simulators} = require("devtools/client/webide/modules/simulators");
+const {Task} = require("devtools/shared/task");
+
+const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
+
+const HTML = "http://www.w3.org/1999/xhtml";
+const HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting";
+
+const MAX_ZOOM = 1.4;
+const MIN_ZOOM = 0.6;
+
+const MS_PER_DAY = 86400000;
+
+[["AppManager", AppManager],
+ ["AppProjects", AppProjects],
+ ["Connection", Connection]].forEach(([key, value]) => {
+ Object.defineProperty(this, key, {
+ value: value,
+ enumerable: true,
+ writable: false
+ });
+ });
+
+// Download remote resources early
+getJSON("devtools.webide.addonsURL");
+getJSON("devtools.webide.templatesURL");
+getJSON("devtools.devices.url");
+
+// See bug 989619
+console.log = console.log.bind(console);
+console.warn = console.warn.bind(console);
+console.error = console.error.bind(console);
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ UI.init();
+});
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ UI.destroy();
+});
+
+var UI = {
+ init: function () {
+ this._telemetry = new Telemetry();
+ this._telemetry.toolOpened("webide");
+
+ AppManager.init();
+
+ this.appManagerUpdate = this.appManagerUpdate.bind(this);
+ AppManager.on("app-manager-update", this.appManagerUpdate);
+
+ Cmds.showProjectPanel();
+ Cmds.showRuntimePanel();
+
+ this.updateCommands();
+
+ this.onfocus = this.onfocus.bind(this);
+ window.addEventListener("focus", this.onfocus, true);
+
+ AppProjects.load().then(() => {
+ this.autoSelectProject();
+ }, e => {
+ console.error(e);
+ this.reportError("error_appProjectsLoadFailed");
+ });
+
+ // Auto install the ADB Addon Helper and Tools Adapters. Only once.
+ // If the user decides to uninstall any of this addon, we won't install it again.
+ let autoinstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
+ let autoinstallFxdtAdapters = Services.prefs.getBoolPref("devtools.webide.autoinstallFxdtAdapters");
+ if (autoinstallADBHelper) {
+ GetAvailableAddons().then(addons => {
+ addons.adb.install();
+ }, console.error);
+ }
+ if (autoinstallFxdtAdapters) {
+ GetAvailableAddons().then(addons => {
+ addons.adapters.install();
+ }, console.error);
+ }
+ Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
+ Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", false);
+
+ if (Services.prefs.getBoolPref("devtools.webide.widget.autoinstall") &&
+ !Services.prefs.getBoolPref("devtools.webide.widget.enabled")) {
+ Services.prefs.setBoolPref("devtools.webide.widget.enabled", true);
+ gDevToolsBrowser.moveWebIDEWidgetInNavbar();
+ }
+
+ this.setupDeck();
+
+ this.contentViewer = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .contentViewer;
+ this.contentViewer.fullZoom = Services.prefs.getCharPref("devtools.webide.zoom");
+
+ gDevToolsBrowser.isWebIDEInitialized.resolve();
+
+ this.configureSimulator = this.configureSimulator.bind(this);
+ Simulators.on("configure", this.configureSimulator);
+ },
+
+ destroy: function () {
+ window.removeEventListener("focus", this.onfocus, true);
+ AppManager.off("app-manager-update", this.appManagerUpdate);
+ AppManager.destroy();
+ Simulators.off("configure", this.configureSimulator);
+ this.updateConnectionTelemetry();
+ this._telemetry.toolClosed("webide");
+ this._telemetry.toolClosed("webideProjectEditor");
+ this._telemetry.destroy();
+ },
+
+ canCloseProject: function () {
+ if (this.projecteditor) {
+ return this.projecteditor.confirmUnsaved();
+ }
+ return true;
+ },
+
+ onfocus: function () {
+ // Because we can't track the activity in the folder project,
+ // we need to validate the project regularly. Let's assume that
+ // if a modification happened, it happened when the window was
+ // not focused.
+ if (AppManager.selectedProject &&
+ AppManager.selectedProject.type != "mainProcess" &&
+ AppManager.selectedProject.type != "runtimeApp" &&
+ AppManager.selectedProject.type != "tab") {
+ AppManager.validateAndUpdateProject(AppManager.selectedProject);
+ }
+
+ // Hook to display promotional Developer Edition doorhanger. Only displayed once.
+ // Hooked into the `onfocus` event because sometimes does not work
+ // when run at the end of `init`. ¯\(°_o)/¯
+ showDoorhanger({ window, type: "deveditionpromo", anchor: document.querySelector("#deck") });
+ },
+
+ appManagerUpdate: function (event, what, details) {
+ // Got a message from app-manager.js
+ // See AppManager.update() for descriptions of what these events mean.
+ switch (what) {
+ case "runtime-list":
+ this.autoConnectRuntime();
+ break;
+ case "connection":
+ this.updateRuntimeButton();
+ this.updateCommands();
+ this.updateConnectionTelemetry();
+ break;
+ case "before-project":
+ if (!this.canCloseProject()) {
+ details.cancel();
+ }
+ break;
+ case "project":
+ this._updatePromise = Task.spawn(function* () {
+ UI.updateTitle();
+ yield UI.destroyToolbox();
+ UI.updateCommands();
+ UI.openProject();
+ yield UI.autoStartProject();
+ UI.autoOpenToolbox();
+ UI.saveLastSelectedProject();
+ UI.updateRemoveProjectButton();
+ });
+ return;
+ case "project-started":
+ this.updateCommands();
+ UI.autoOpenToolbox();
+ break;
+ case "project-stopped":
+ UI.destroyToolbox();
+ this.updateCommands();
+ break;
+ case "runtime-global-actors":
+ // Check runtime version only on runtime-global-actors,
+ // as we expect to use device actor
+ this.checkRuntimeVersion();
+ this.updateCommands();
+ break;
+ case "runtime-details":
+ this.updateRuntimeButton();
+ break;
+ case "runtime":
+ this.updateRuntimeButton();
+ this.saveLastConnectedRuntime();
+ break;
+ case "project-validated":
+ this.updateTitle();
+ this.updateCommands();
+ this.updateProjectEditorHeader();
+ break;
+ case "install-progress":
+ this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
+ break;
+ case "runtime-targets":
+ this.autoSelectProject();
+ break;
+ case "pre-package":
+ this.prePackageLog(details);
+ break;
+ }
+ this._updatePromise = promise.resolve();
+ },
+
+ configureSimulator: function (event, simulator) {
+ UI.selectDeckPanel("simulator");
+ },
+
+ openInBrowser: function (url) {
+ // Open a URL in a Firefox window
+ let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ if (mainWindow) {
+ mainWindow.openUILinkIn(url, "tab");
+ mainWindow.focus()
+ } else {
+ window.open(url);
+ }
+ },
+
+ updateTitle: function () {
+ let project = AppManager.selectedProject;
+ if (project) {
+ window.document.title = Strings.formatStringFromName("title_app", [project.name], 1);
+ } else {
+ window.document.title = Strings.GetStringFromName("title_noApp");
+ }
+ },
+
+ /** ******** BUSY UI **********/
+
+ _busyTimeout: null,
+ _busyOperationDescription: null,
+ _busyPromise: null,
+
+ updateProgress: function (percent) {
+ let progress = document.querySelector("#action-busy-determined");
+ progress.mode = "determined";
+ progress.value = percent;
+ this.setupBusyTimeout();
+ },
+
+ busy: function () {
+ let win = document.querySelector("window");
+ win.classList.add("busy");
+ win.classList.add("busy-undetermined");
+ this.updateCommands();
+ this.update("busy");
+ },
+
+ unbusy: function () {
+ let win = document.querySelector("window");
+ win.classList.remove("busy");
+ win.classList.remove("busy-determined");
+ win.classList.remove("busy-undetermined");
+ this.updateCommands();
+ this.update("unbusy");
+ this._busyPromise = null;
+ },
+
+ setupBusyTimeout: function () {
+ this.cancelBusyTimeout();
+ this._busyTimeout = setTimeout(() => {
+ this.unbusy();
+ UI.reportError("error_operationTimeout", this._busyOperationDescription);
+ }, Services.prefs.getIntPref("devtools.webide.busyTimeout"));
+ },
+
+ cancelBusyTimeout: function () {
+ clearTimeout(this._busyTimeout);
+ },
+
+ busyWithProgressUntil: function (promise, operationDescription) {
+ let busy = this.busyUntil(promise, operationDescription);
+ let win = document.querySelector("window");
+ let progress = document.querySelector("#action-busy-determined");
+ progress.mode = "undetermined";
+ win.classList.add("busy-determined");
+ win.classList.remove("busy-undetermined");
+ return busy;
+ },
+
+ busyUntil: function (promise, operationDescription) {
+ // Freeze the UI until the promise is resolved. A timeout will unfreeze the
+ // UI, just in case the promise never gets resolved.
+ this._busyPromise = promise;
+ this._busyOperationDescription = operationDescription;
+ this.setupBusyTimeout();
+ this.busy();
+ promise.then(() => {
+ this.cancelBusyTimeout();
+ this.unbusy();
+ }, (e) => {
+ let message;
+ if (e && e.error && e.message) {
+ // Some errors come from fronts that are not based on protocol.js.
+ // Errors are not translated to strings.
+ message = operationDescription + " (" + e.error + "): " + e.message;
+ } else {
+ message = operationDescription + (e ? (": " + e) : "");
+ }
+ this.cancelBusyTimeout();
+ let operationCanceled = e && e.canceled;
+ if (!operationCanceled) {
+ UI.reportError("error_operationFail", message);
+ if (e) {
+ console.error(e);
+ }
+ }
+ this.unbusy();
+ });
+ return promise;
+ },
+
+ reportError: function (l10nProperty, ...l10nArgs) {
+ let text;
+
+ if (l10nArgs.length > 0) {
+ text = Strings.formatStringFromName(l10nProperty, l10nArgs, l10nArgs.length);
+ } else {
+ text = Strings.GetStringFromName(l10nProperty);
+ }
+
+ console.error(text);
+
+ let buttons = [{
+ label: Strings.GetStringFromName("notification_showTroubleShooting_label"),
+ accessKey: Strings.GetStringFromName("notification_showTroubleShooting_accesskey"),
+ callback: function () {
+ Cmds.showTroubleShooting();
+ }
+ }];
+
+ let nbox = document.querySelector("#notificationbox");
+ nbox.removeAllNotifications(true);
+ nbox.appendNotification(text, "webide:errornotification", null,
+ nbox.PRIORITY_WARNING_LOW, buttons);
+ },
+
+ dismissErrorNotification: function () {
+ let nbox = document.querySelector("#notificationbox");
+ nbox.removeAllNotifications(true);
+ },
+
+ /** ******** COMMANDS **********/
+
+ /**
+ * This module emits various events when state changes occur.
+ *
+ * The events this module may emit include:
+ * busy:
+ * The window is currently busy and certain UI functions may be disabled.
+ * unbusy:
+ * The window is not busy and certain UI functions may be re-enabled.
+ */
+ update: function (what, details) {
+ this.emit("webide-update", what, details);
+ },
+
+ updateCommands: function () {
+ // Action commands
+ let playCmd = document.querySelector("#cmd_play");
+ let stopCmd = document.querySelector("#cmd_stop");
+ let debugCmd = document.querySelector("#cmd_toggleToolbox");
+ let playButton = document.querySelector("#action-button-play");
+ let projectPanelCmd = document.querySelector("#cmd_showProjectPanel");
+
+ if (document.querySelector("window").classList.contains("busy")) {
+ playCmd.setAttribute("disabled", "true");
+ stopCmd.setAttribute("disabled", "true");
+ debugCmd.setAttribute("disabled", "true");
+ projectPanelCmd.setAttribute("disabled", "true");
+ return;
+ }
+
+ if (!AppManager.selectedProject || !AppManager.connected) {
+ playCmd.setAttribute("disabled", "true");
+ stopCmd.setAttribute("disabled", "true");
+ debugCmd.setAttribute("disabled", "true");
+ } else {
+ let isProjectRunning = AppManager.isProjectRunning();
+ if (isProjectRunning) {
+ playButton.classList.add("reload");
+ stopCmd.removeAttribute("disabled");
+ debugCmd.removeAttribute("disabled");
+ } else {
+ playButton.classList.remove("reload");
+ stopCmd.setAttribute("disabled", "true");
+ debugCmd.setAttribute("disabled", "true");
+ }
+
+ // If connected and a project is selected
+ if (AppManager.selectedProject.type == "runtimeApp") {
+ playCmd.removeAttribute("disabled");
+ } else if (AppManager.selectedProject.type == "tab") {
+ playCmd.removeAttribute("disabled");
+ stopCmd.setAttribute("disabled", "true");
+ } else if (AppManager.selectedProject.type == "mainProcess") {
+ playCmd.setAttribute("disabled", "true");
+ stopCmd.setAttribute("disabled", "true");
+ } else {
+ if (AppManager.selectedProject.errorsCount == 0 &&
+ AppManager.runtimeCanHandleApps()) {
+ playCmd.removeAttribute("disabled");
+ } else {
+ playCmd.setAttribute("disabled", "true");
+ }
+ }
+ }
+
+ // Runtime commands
+ let monitorCmd = document.querySelector("#cmd_showMonitor");
+ let screenshotCmd = document.querySelector("#cmd_takeScreenshot");
+ let permissionsCmd = document.querySelector("#cmd_showPermissionsTable");
+ let detailsCmd = document.querySelector("#cmd_showRuntimeDetails");
+ let disconnectCmd = document.querySelector("#cmd_disconnectRuntime");
+ let devicePrefsCmd = document.querySelector("#cmd_showDevicePrefs");
+ let settingsCmd = document.querySelector("#cmd_showSettings");
+
+ if (AppManager.connected) {
+ if (AppManager.deviceFront) {
+ monitorCmd.removeAttribute("disabled");
+ detailsCmd.removeAttribute("disabled");
+ permissionsCmd.removeAttribute("disabled");
+ screenshotCmd.removeAttribute("disabled");
+ }
+ if (AppManager.preferenceFront) {
+ devicePrefsCmd.removeAttribute("disabled");
+ }
+ if (AppManager.settingsFront) {
+ settingsCmd.removeAttribute("disabled");
+ }
+ disconnectCmd.removeAttribute("disabled");
+ } else {
+ monitorCmd.setAttribute("disabled", "true");
+ detailsCmd.setAttribute("disabled", "true");
+ permissionsCmd.setAttribute("disabled", "true");
+ screenshotCmd.setAttribute("disabled", "true");
+ disconnectCmd.setAttribute("disabled", "true");
+ devicePrefsCmd.setAttribute("disabled", "true");
+ settingsCmd.setAttribute("disabled", "true");
+ }
+
+ let runtimePanelButton = document.querySelector("#runtime-panel-button");
+
+ if (AppManager.connected) {
+ runtimePanelButton.setAttribute("active", "true");
+ runtimePanelButton.removeAttribute("hidden");
+ } else {
+ runtimePanelButton.removeAttribute("active");
+ runtimePanelButton.setAttribute("hidden", "true");
+ }
+
+ projectPanelCmd.removeAttribute("disabled");
+ },
+
+ updateRemoveProjectButton: function () {
+ // Remove command
+ let removeCmdNode = document.querySelector("#cmd_removeProject");
+ if (AppManager.selectedProject) {
+ removeCmdNode.removeAttribute("disabled");
+ } else {
+ removeCmdNode.setAttribute("disabled", "true");
+ }
+ },
+
+ /** ******** RUNTIME **********/
+
+ get lastConnectedRuntime() {
+ return Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
+ },
+
+ set lastConnectedRuntime(runtime) {
+ Services.prefs.setCharPref("devtools.webide.lastConnectedRuntime", runtime);
+ },
+
+ autoConnectRuntime: function () {
+ // Automatically reconnect to the previously selected runtime,
+ // if available and has an ID and feature is enabled
+ if (AppManager.selectedRuntime ||
+ !Services.prefs.getBoolPref("devtools.webide.autoConnectRuntime") ||
+ !this.lastConnectedRuntime) {
+ return;
+ }
+ let [_, type, id] = this.lastConnectedRuntime.match(/^(\w+):(.+)$/);
+
+ type = type.toLowerCase();
+
+ // Local connection is mapped to AppManager.runtimeList.other array
+ if (type == "local") {
+ type = "other";
+ }
+
+ // We support most runtimes except simulator, that needs to be manually
+ // launched
+ if (type == "usb" || type == "wifi" || type == "other") {
+ for (let runtime of AppManager.runtimeList[type]) {
+ // Some runtimes do not expose an id and don't support autoconnect (like
+ // remote connection)
+ if (runtime.id == id) {
+ // Only want one auto-connect attempt, so clear last runtime value
+ this.lastConnectedRuntime = "";
+ this.connectToRuntime(runtime);
+ }
+ }
+ }
+ },
+
+ connectToRuntime: function (runtime) {
+ let name = runtime.name;
+ let promise = AppManager.connectToRuntime(runtime);
+ promise.then(() => this.initConnectionTelemetry())
+ .catch(() => {
+ // Empty rejection handler to silence uncaught rejection warnings
+ // |busyUntil| will listen for rejections.
+ // Bug 1121100 may find a better way to silence these.
+ });
+ promise = this.busyUntil(promise, "Connecting to " + name);
+ // Stop busy timeout for runtimes that take unknown or long amounts of time
+ // to connect.
+ if (runtime.prolongedConnection) {
+ this.cancelBusyTimeout();
+ }
+ return promise;
+ },
+
+ updateRuntimeButton: function () {
+ let labelNode = document.querySelector("#runtime-panel-button > .panel-button-label");
+ if (!AppManager.selectedRuntime) {
+ labelNode.setAttribute("value", Strings.GetStringFromName("runtimeButton_label"));
+ } else {
+ let name = AppManager.selectedRuntime.name;
+ labelNode.setAttribute("value", name);
+ }
+ },
+
+ saveLastConnectedRuntime: function () {
+ if (AppManager.selectedRuntime &&
+ AppManager.selectedRuntime.id !== undefined) {
+ this.lastConnectedRuntime = AppManager.selectedRuntime.type + ":" +
+ AppManager.selectedRuntime.id;
+ } else {
+ this.lastConnectedRuntime = "";
+ }
+ },
+
+ /** ******** ACTIONS **********/
+
+ _actionsToLog: new Set(),
+
+ /**
+ * For each new connection, track whether play and debug were ever used. Only
+ * one value is collected for each button, even if they are used multiple
+ * times during a connection.
+ */
+ initConnectionTelemetry: function () {
+ this._actionsToLog.add("play");
+ this._actionsToLog.add("debug");
+ },
+
+ /**
+ * Action occurred. Log that it happened, and remove it from the loggable
+ * set.
+ */
+ onAction: function (action) {
+ if (!this._actionsToLog.has(action)) {
+ return;
+ }
+ this.logActionState(action, true);
+ this._actionsToLog.delete(action);
+ },
+
+ /**
+ * Connection status changed or we are shutting down. Record any loggable
+ * actions as having not occurred.
+ */
+ updateConnectionTelemetry: function () {
+ for (let action of this._actionsToLog.values()) {
+ this.logActionState(action, false);
+ }
+ this._actionsToLog.clear();
+ },
+
+ logActionState: function (action, state) {
+ let histogramId = "DEVTOOLS_WEBIDE_CONNECTION_" +
+ action.toUpperCase() + "_USED";
+ this._telemetry.log(histogramId, state);
+ },
+
+ /** ******** PROJECTS **********/
+
+ // ProjectEditor & details screen
+
+ destroyProjectEditor: function () {
+ if (this.projecteditor) {
+ this.projecteditor.destroy();
+ this.projecteditor = null;
+ }
+ },
+
+ /**
+ * Called when selecting or deselecting the project editor panel.
+ */
+ onChangeProjectEditorSelected: function () {
+ if (this.projecteditor) {
+ let panel = document.querySelector("#deck").selectedPanel;
+ if (panel && panel.id == "deck-panel-projecteditor") {
+ this.projecteditor.menuEnabled = true;
+ this._telemetry.toolOpened("webideProjectEditor");
+ } else {
+ this.projecteditor.menuEnabled = false;
+ this._telemetry.toolClosed("webideProjectEditor");
+ }
+ }
+ },
+
+ getProjectEditor: function () {
+ if (this.projecteditor) {
+ return this.projecteditor.loaded;
+ }
+
+ let projecteditorIframe = document.querySelector("#deck-panel-projecteditor");
+ this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe, {
+ menubar: document.querySelector("#main-menubar"),
+ menuindex: 1
+ });
+ this.projecteditor.on("onEditorSave", () => {
+ AppManager.validateAndUpdateProject(AppManager.selectedProject);
+ this._telemetry.actionOccurred("webideProjectEditorSave");
+ });
+ return this.projecteditor.loaded;
+ },
+
+ updateProjectEditorHeader: function () {
+ let project = AppManager.selectedProject;
+ if (!project || !this.projecteditor) {
+ return;
+ }
+ let status = project.validationStatus || "unknown";
+ if (status == "error warning") {
+ status = "error";
+ }
+ this.getProjectEditor().then((projecteditor) => {
+ projecteditor.setProjectToAppPath(project.location, {
+ name: project.name,
+ iconUrl: project.icon,
+ projectOverviewURL: "chrome://webide/content/details.xhtml",
+ validationStatus: status
+ }).then(null, console.error);
+ }, console.error);
+ },
+
+ isProjectEditorEnabled: function () {
+ return Services.prefs.getBoolPref("devtools.webide.showProjectEditor");
+ },
+
+ openProject: function () {
+ let project = AppManager.selectedProject;
+
+ // Nothing to show
+
+ if (!project) {
+ this.resetDeck();
+ return;
+ }
+
+ // Make sure the directory exist before we show Project Editor
+
+ let forceDetailsOnly = false;
+ if (project.type == "packaged") {
+ forceDetailsOnly = !utils.doesFileExist(project.location);
+ }
+
+ // Show only the details screen
+
+ if (project.type != "packaged" ||
+ !this.isProjectEditorEnabled() ||
+ forceDetailsOnly) {
+ this.selectDeckPanel("details");
+ return;
+ }
+
+ // Show ProjectEditor
+
+ this.getProjectEditor().then(() => {
+ this.updateProjectEditorHeader();
+ }, console.error);
+
+ this.selectDeckPanel("projecteditor");
+ },
+
+ autoStartProject: Task.async(function* () {
+ let project = AppManager.selectedProject;
+
+ if (!project) {
+ return;
+ }
+ if (!(project.type == "runtimeApp" ||
+ project.type == "mainProcess" ||
+ project.type == "tab")) {
+ return; // For something that is not an editable app, we're done.
+ }
+
+ // Do not force opening apps that are already running, as they may have
+ // some activity being opened and don't want to dismiss them.
+ if (project.type == "runtimeApp" && !AppManager.isProjectRunning()) {
+ yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
+ }
+ }),
+
+ autoOpenToolbox: Task.async(function* () {
+ let project = AppManager.selectedProject;
+
+ if (!project) {
+ return;
+ }
+ if (!(project.type == "runtimeApp" ||
+ project.type == "mainProcess" ||
+ project.type == "tab")) {
+ return; // For something that is not an editable app, we're done.
+ }
+
+ yield UI.createToolbox();
+ }),
+
+ importAndSelectApp: Task.async(function* (source) {
+ let isPackaged = !!source.path;
+ let project;
+ try {
+ project = yield AppProjects[isPackaged ? "addPackaged" : "addHosted"](source);
+ } catch (e) {
+ if (e === "Already added") {
+ // Select project that's already been added,
+ // and allow it to be revalidated and selected
+ project = AppProjects.get(isPackaged ? source.path : source);
+ } else {
+ throw e;
+ }
+ }
+
+ // Select project
+ AppManager.selectedProject = project;
+
+ this._telemetry.actionOccurred("webideImportProject");
+ }),
+
+ // Remember the last selected project on the runtime
+ saveLastSelectedProject: function () {
+ let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+ if (!shouldRestore) {
+ return;
+ }
+
+ // Ignore unselection of project on runtime disconnection
+ if (!AppManager.connected) {
+ return;
+ }
+
+ let project = "", type = "";
+ let selected = AppManager.selectedProject;
+ if (selected) {
+ if (selected.type == "runtimeApp") {
+ type = "runtimeApp";
+ project = selected.app.manifestURL;
+ } else if (selected.type == "mainProcess") {
+ type = "mainProcess";
+ } else if (selected.type == "packaged" ||
+ selected.type == "hosted") {
+ type = "local";
+ project = selected.location;
+ }
+ }
+ if (type) {
+ Services.prefs.setCharPref("devtools.webide.lastSelectedProject",
+ type + ":" + project);
+ } else {
+ Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
+ }
+ },
+
+ autoSelectProject: function () {
+ if (AppManager.selectedProject) {
+ return;
+ }
+ let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+ if (!shouldRestore) {
+ return;
+ }
+ let pref = Services.prefs.getCharPref("devtools.webide.lastSelectedProject");
+ if (!pref) {
+ return;
+ }
+ let m = pref.match(/^(\w+):(.*)$/);
+ if (!m) {
+ return;
+ }
+ let [_, type, project] = m;
+
+ if (type == "local") {
+ let lastProject = AppProjects.get(project);
+ if (lastProject) {
+ AppManager.selectedProject = lastProject;
+ }
+ }
+
+ // For other project types, we need to be connected to the runtime
+ if (!AppManager.connected) {
+ return;
+ }
+
+ if (type == "mainProcess" && AppManager.isMainProcessDebuggable()) {
+ AppManager.selectedProject = {
+ type: "mainProcess",
+ name: Strings.GetStringFromName("mainProcess_label"),
+ icon: AppManager.DEFAULT_PROJECT_ICON
+ };
+ } else if (type == "runtimeApp") {
+ let app = AppManager.apps.get(project);
+ if (app) {
+ AppManager.selectedProject = {
+ type: "runtimeApp",
+ app: app.manifest,
+ icon: app.iconURL,
+ name: app.manifest.name
+ };
+ }
+ }
+ },
+
+ /** ******** DECK **********/
+
+ setupDeck: function () {
+ let iframes = document.querySelectorAll("#deck > iframe");
+ for (let iframe of iframes) {
+ iframe.tooltip = "aHTMLTooltip";
+ }
+ },
+
+ resetFocus: function () {
+ document.commandDispatcher.focusedElement = document.documentElement;
+ },
+
+ selectDeckPanel: function (id) {
+ let deck = document.querySelector("#deck");
+ if (deck.selectedPanel && deck.selectedPanel.id === "deck-panel-" + id) {
+ // This panel is already displayed.
+ return;
+ }
+ this.resetFocus();
+ let panel = deck.querySelector("#deck-panel-" + id);
+ let lazysrc = panel.getAttribute("lazysrc");
+ if (lazysrc) {
+ panel.removeAttribute("lazysrc");
+ panel.setAttribute("src", lazysrc);
+ }
+ deck.selectedPanel = panel;
+ this.onChangeProjectEditorSelected();
+ },
+
+ resetDeck: function () {
+ this.resetFocus();
+ let deck = document.querySelector("#deck");
+ deck.selectedPanel = null;
+ this.onChangeProjectEditorSelected();
+ },
+
+ buildIDToDate(buildID) {
+ let fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
+ // Date expects 0 - 11 for months
+ return new Date(fields[1], Number.parseInt(fields[2]) - 1, fields[3]);
+ },
+
+ checkRuntimeVersion: Task.async(function* () {
+ if (AppManager.connected && AppManager.deviceFront) {
+ let desc = yield AppManager.deviceFront.getDescription();
+ // Compare device and firefox build IDs
+ // and only compare by day (strip hours/minutes) to prevent
+ // warning against builds of the same day.
+ let deviceID = desc.appbuildid.substr(0, 8);
+ let localID = Services.appinfo.appBuildID.substr(0, 8);
+ let deviceDate = this.buildIDToDate(deviceID);
+ let localDate = this.buildIDToDate(localID);
+ // Allow device to be newer by up to a week. This accommodates those with
+ // local device builds, since their devices will almost always be newer
+ // than the client.
+ if (deviceDate - localDate > 7 * MS_PER_DAY) {
+ this.reportError("error_runtimeVersionTooRecent", deviceID, localID);
+ }
+ }
+ }),
+
+ /** ******** TOOLBOX **********/
+
+ /**
+ * There are many ways to close a toolbox:
+ * * Close button inside the toolbox
+ * * Toggle toolbox wrench in WebIDE
+ * * Disconnect the current runtime gracefully
+ * * Yank cord out of device
+ * * Close or crash the app/tab
+ * We can't know for sure which one was used here, so reset the
+ * |toolboxPromise| since someone must be destroying it to reach here,
+ * and call our own close method.
+ */
+ _onToolboxClosed: function (promise, iframe) {
+ // Only save toolbox size, disable wrench button, workaround focus issue...
+ // if we are closing the last toolbox:
+ // - toolboxPromise is nullified by destroyToolbox and is still null here
+ // if no other toolbox has been opened in between,
+ // - having two distinct promise means we are receiving closed event
+ // for a previous, non-current, toolbox.
+ if (!this.toolboxPromise || this.toolboxPromise === promise) {
+ this.toolboxPromise = null;
+ this.resetFocus();
+ Services.prefs.setIntPref("devtools.toolbox.footer.height", iframe.height);
+
+ let splitter = document.querySelector(".devtools-horizontal-splitter");
+ splitter.setAttribute("hidden", "true");
+ document.querySelector("#action-button-debug").removeAttribute("active");
+ }
+ // We have to destroy the iframe, otherwise, the keybindings of webide don't work
+ // properly anymore.
+ iframe.remove();
+ },
+
+ destroyToolbox: function () {
+ // Only have a live toolbox if |this.toolboxPromise| exists
+ if (this.toolboxPromise) {
+ let toolboxPromise = this.toolboxPromise;
+ this.toolboxPromise = null;
+ return toolboxPromise.then(toolbox => toolbox.destroy());
+ }
+ return promise.resolve();
+ },
+
+ createToolbox: function () {
+ // If |this.toolboxPromise| exists, there is already a live toolbox
+ if (this.toolboxPromise) {
+ return this.toolboxPromise;
+ }
+
+ let iframe = document.createElement("iframe");
+ iframe.id = "toolbox";
+
+ // Compute a uid on the iframe in order to identify toolbox iframe
+ // when receiving toolbox-close event
+ iframe.uid = new Date().getTime();
+
+ let height = Services.prefs.getIntPref("devtools.toolbox.footer.height");
+ iframe.height = height;
+
+ let promise = this.toolboxPromise = AppManager.getTarget().then(target => {
+ return this._showToolbox(target, iframe);
+ }).then(toolbox => {
+ // Destroy the toolbox on WebIDE side before
+ // toolbox.destroy's promise resolves.
+ toolbox.once("destroyed", this._onToolboxClosed.bind(this, promise, iframe));
+ return toolbox;
+ }, console.error);
+
+ return this.busyUntil(this.toolboxPromise, "opening toolbox");
+ },
+
+ _showToolbox: function (target, iframe) {
+ let splitter = document.querySelector(".devtools-horizontal-splitter");
+ splitter.removeAttribute("hidden");
+
+ document.querySelector("notificationbox").insertBefore(iframe, splitter.nextSibling);
+ let host = Toolbox.HostType.CUSTOM;
+ let options = { customIframe: iframe, zoom: false, uid: iframe.uid };
+
+ document.querySelector("#action-button-debug").setAttribute("active", "true");
+
+ return gDevTools.showToolbox(target, null, host, options);
+ },
+
+ prePackageLog: function (msg) {
+ if (msg == "start") {
+ UI.selectDeckPanel("logs");
+ }
+ }
+};
+
+EventEmitter.decorate(UI);
+
+var Cmds = {
+ quit: function () {
+ if (UI.canCloseProject()) {
+ window.close();
+ }
+ },
+
+ showProjectPanel: function () {
+ ProjectPanel.toggleSidebar();
+ return promise.resolve();
+ },
+
+ showRuntimePanel: function () {
+ RuntimeScanners.scan();
+ RuntimePanel.toggleSidebar();
+ },
+
+ disconnectRuntime: function () {
+ let disconnecting = Task.spawn(function* () {
+ yield UI.destroyToolbox();
+ yield AppManager.disconnectRuntime();
+ });
+ return UI.busyUntil(disconnecting, "disconnecting from runtime");
+ },
+
+ takeScreenshot: function () {
+ let url = AppManager.deviceFront.screenshotToDataURL();
+ return UI.busyUntil(url.then(longstr => {
+ return longstr.string().then(dataURL => {
+ longstr.release().then(null, console.error);
+ UI.openInBrowser(dataURL);
+ });
+ }), "taking screenshot");
+ },
+
+ showPermissionsTable: function () {
+ UI.selectDeckPanel("permissionstable");
+ },
+
+ showRuntimeDetails: function () {
+ UI.selectDeckPanel("runtimedetails");
+ },
+
+ showDevicePrefs: function () {
+ UI.selectDeckPanel("devicepreferences");
+ },
+
+ showSettings: function () {
+ UI.selectDeckPanel("devicesettings");
+ },
+
+ showMonitor: function () {
+ UI.selectDeckPanel("monitor");
+ },
+
+ play: Task.async(function* () {
+ let busy;
+ switch (AppManager.selectedProject.type) {
+ case "packaged":
+ let autosave =
+ Services.prefs.getBoolPref("devtools.webide.autosaveFiles");
+ if (autosave && UI.projecteditor) {
+ yield UI.projecteditor.saveAllFiles();
+ }
+ busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(),
+ "installing and running app");
+ break;
+ case "hosted":
+ busy = UI.busyUntil(AppManager.installAndRunProject(),
+ "installing and running app");
+ break;
+ case "runtimeApp":
+ busy = UI.busyUntil(AppManager.launchOrReloadRuntimeApp(), "launching / reloading app");
+ break;
+ case "tab":
+ busy = UI.busyUntil(AppManager.reloadTab(), "reloading tab");
+ break;
+ }
+ if (!busy) {
+ return promise.reject();
+ }
+ UI.onAction("play");
+ return busy;
+ }),
+
+ stop: function () {
+ return UI.busyUntil(AppManager.stopRunningApp(), "stopping app");
+ },
+
+ toggleToolbox: function () {
+ UI.onAction("debug");
+ if (UI.toolboxPromise) {
+ UI.destroyToolbox();
+ return promise.resolve();
+ } else {
+ return UI.createToolbox();
+ }
+ },
+
+ removeProject: function () {
+ AppManager.removeSelectedProject();
+ },
+
+ toggleEditors: function () {
+ let isNowEnabled = !UI.isProjectEditorEnabled();
+ Services.prefs.setBoolPref("devtools.webide.showProjectEditor", isNowEnabled);
+ if (!isNowEnabled) {
+ UI.destroyProjectEditor();
+ }
+ UI.openProject();
+ },
+
+ showTroubleShooting: function () {
+ UI.openInBrowser(HELP_URL);
+ },
+
+ showAddons: function () {
+ UI.selectDeckPanel("addons");
+ },
+
+ showPrefs: function () {
+ UI.selectDeckPanel("prefs");
+ },
+
+ zoomIn: function () {
+ if (UI.contentViewer.fullZoom < MAX_ZOOM) {
+ UI.contentViewer.fullZoom += 0.1;
+ Services.prefs.setCharPref("devtools.webide.zoom", UI.contentViewer.fullZoom);
+ }
+ },
+
+ zoomOut: function () {
+ if (UI.contentViewer.fullZoom > MIN_ZOOM) {
+ UI.contentViewer.fullZoom -= 0.1;
+ Services.prefs.setCharPref("devtools.webide.zoom", UI.contentViewer.fullZoom);
+ }
+ },
+
+ resetZoom: function () {
+ UI.contentViewer.fullZoom = 1;
+ Services.prefs.setCharPref("devtools.webide.zoom", 1);
+ }
+};
diff --git a/devtools/client/webide/content/webide.xul b/devtools/client/webide/content/webide.xul
new file mode 100644
index 000000000..a3e4355b9
--- /dev/null
+++ b/devtools/client/webide/content/webide.xul
@@ -0,0 +1,178 @@
+<?xml version="1.0"?>
+
+<!-- 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 window [
+ <!ENTITY % webideDTD SYSTEM "chrome://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="resource://devtools/client/themes/common.css"?>
+<?xml-stylesheet href="chrome://webide/skin/webide.css"?>
+
+<window id="webide" onclose="return UI.canCloseProject();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&windowTitle;"
+ windowtype="devtools:webide"
+ macanimationtype="document"
+ fullscreenbutton="true"
+ screenX="4" screenY="4"
+ width="800" height="600"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"></script>
+ <script type="application/javascript" src="project-panel.js"></script>
+ <script type="application/javascript" src="runtime-panel.js"></script>
+ <script type="application/javascript" src="webide.js"></script>
+
+ <commandset id="mainCommandSet">
+ <commandset id="editMenuCommands"/>
+ <commandset id="webideCommands">
+ <command id="cmd_quit" oncommand="Cmds.quit()"/>
+ <command id="cmd_newApp" oncommand="Cmds.newApp()" label="&projectMenu_newApp_label;"/>
+ <command id="cmd_importPackagedApp" oncommand="Cmds.importPackagedApp()" label="&projectMenu_importPackagedApp_label;"/>
+ <command id="cmd_importHostedApp" oncommand="Cmds.importHostedApp()" label="&projectMenu_importHostedApp_label;"/>
+ <command id="cmd_showDevicePrefs" label="&runtimeMenu_showDevicePrefs_label;" oncommand="Cmds.showDevicePrefs()"/>
+ <command id="cmd_showSettings" label="&runtimeMenu_showSettings_label;" oncommand="Cmds.showSettings()"/>
+ <command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
+ <command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
+ <command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
+ <command id="cmd_disconnectRuntime" oncommand="Cmds.disconnectRuntime()" label="&runtimeMenu_disconnect_label;"/>
+ <command id="cmd_showMonitor" oncommand="Cmds.showMonitor()" label="&runtimeMenu_showMonitor_label;"/>
+ <command id="cmd_showPermissionsTable" oncommand="Cmds.showPermissionsTable()" label="&runtimeMenu_showPermissionTable_label;"/>
+ <command id="cmd_showRuntimeDetails" oncommand="Cmds.showRuntimeDetails()" label="&runtimeMenu_showDetails_label;"/>
+ <command id="cmd_takeScreenshot" oncommand="Cmds.takeScreenshot()" label="&runtimeMenu_takeScreenshot_label;"/>
+ <command id="cmd_toggleEditor" oncommand="Cmds.toggleEditors()" label="&viewMenu_toggleEditor_label;"/>
+ <command id="cmd_showAddons" oncommand="Cmds.showAddons()"/>
+ <command id="cmd_showPrefs" oncommand="Cmds.showPrefs()"/>
+ <command id="cmd_showTroubleShooting" oncommand="Cmds.showTroubleShooting()"/>
+ <command id="cmd_play" oncommand="Cmds.play()"/>
+ <command id="cmd_stop" oncommand="Cmds.stop()" label="&projectMenu_stop_label;"/>
+ <command id="cmd_toggleToolbox" oncommand="Cmds.toggleToolbox()"/>
+ <command id="cmd_zoomin" label="&viewMenu_zoomin_label;" oncommand="Cmds.zoomIn()"/>
+ <command id="cmd_zoomout" label="&viewMenu_zoomout_label;" oncommand="Cmds.zoomOut()"/>
+ <command id="cmd_resetzoom" label="&viewMenu_resetzoom_label;" oncommand="Cmds.resetZoom()"/>
+ </commandset>
+ </commandset>
+
+ <menubar id="main-menubar">
+ <menu id="menu-project" label="&projectMenu_label;" accesskey="&projectMenu_accesskey;">
+ <menupopup id="menu-project-popup">
+ <menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
+ <menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
+ <menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
+ <menuitem id="menuitem-show_projectPanel" command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
+ <menuseparator/>
+ <menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
+ <menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>
+ <menuitem command="cmd_toggleToolbox" key="key_toggleToolbox" label="&projectMenu_debug_label;" accesskey="&projectMenu_debug_accesskey;"/>
+ <menuseparator/>
+ <menuitem command="cmd_removeProject" accesskey="&projectMenu_remove_accesskey;"/>
+ <menuseparator/>
+ <menuitem command="cmd_showPrefs" label="&projectMenu_showPrefs_label;" accesskey="&projectMenu_showPrefs_accesskey;"/>
+ <menuitem command="cmd_showAddons" label="&projectMenu_manageComponents_label;" accesskey="&projectMenu_manageComponents_accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu-runtime" label="&runtimeMenu_label;" accesskey="&runtimeMenu_accesskey;">
+ <menupopup id="menu-runtime-popup">
+ <menuitem command="cmd_showMonitor" accesskey="&runtimeMenu_showMonitor_accesskey;"/>
+ <menuitem command="cmd_takeScreenshot" accesskey="&runtimeMenu_takeScreenshot_accesskey;"/>
+ <menuitem command="cmd_showPermissionsTable" accesskey="&runtimeMenu_showPermissionTable_accesskey;"/>
+ <menuitem command="cmd_showRuntimeDetails" accesskey="&runtimeMenu_showDetails_accesskey;"/>
+ <menuitem command="cmd_showDevicePrefs" accesskey="&runtimeMenu_showDevicePrefs_accesskey;"/>
+ <menuitem command="cmd_showSettings" accesskey="&runtimeMenu_showSettings_accesskey;"/>
+ <menuseparator/>
+ <menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
+ <menupopup id="menu-ViewPopup">
+ <menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
+ <menuseparator/>
+ <menuitem command="cmd_zoomin" key="key_zoomin" accesskey="&viewMenu_zoomin_accesskey;"/>
+ <menuitem command="cmd_zoomout" key="key_zoomout" accesskey="&viewMenu_zoomout_accesskey;"/>
+ <menuitem command="cmd_resetzoom" key="key_resetzoom" accesskey="&viewMenu_resetzoom_accesskey;"/>
+ </menupopup>
+ </menu>
+
+ </menubar>
+
+ <keyset id="mainKeyset">
+ <key key="&key_quit;" id="key_quit" command="cmd_quit" modifiers="accel"/>
+ <key key="&key_showProjectPanel;" id="key_showProjectPanel" command="cmd_showProjectPanel" modifiers="accel"/>
+ <key key="&key_play;" id="key_play" command="cmd_play" modifiers="accel"/>
+ <key key="&key_toggleEditor;" id="key_toggleEditor" command="cmd_toggleEditor" modifiers="accel"/>
+ <key keycode="&key_toggleToolbox;" id="key_toggleToolbox" command="cmd_toggleToolbox"/>
+ <key key="&key_zoomin;" id="key_zoomin" command="cmd_zoomin" modifiers="accel"/>
+ <key key="&key_zoomin2;" id="key_zoomin2" command="cmd_zoomin" modifiers="accel"/>
+ <key key="&key_zoomout;" id="key_zoomout" command="cmd_zoomout" modifiers="accel"/>
+ <key key="&key_resetzoom;" id="key_resetzoom" command="cmd_resetzoom" modifiers="accel"/>
+ </keyset>
+
+ <tooltip id="aHTMLTooltip" page="true"/>
+
+ <toolbar id="main-toolbar">
+
+ <vbox flex="1">
+ <hbox id="action-buttons-container" class="busy">
+ <toolbarbutton id="action-button-play" class="action-button" command="cmd_play" tooltiptext="&projectMenu_play_label;"/>
+ <toolbarbutton id="action-button-stop" class="action-button" command="cmd_stop" tooltiptext="&projectMenu_stop_label;"/>
+ <toolbarbutton id="action-button-debug" class="action-button" command="cmd_toggleToolbox" tooltiptext="&projectMenu_debug_label;"/>
+ <hbox id="action-busy" align="center">
+ <html:img id="action-busy-undetermined" src="chrome://webide/skin/throbber.svg"/>
+ <progressmeter id="action-busy-determined"/>
+ </hbox>
+ </hbox>
+
+ <hbox id="panel-buttons-container">
+ <spacer flex="1"/>
+ <toolbarbutton id="runtime-panel-button" class="panel-button">
+ <image class="panel-button-image"/>
+ <label class="panel-button-label" value="&runtimeButton_label;"/>
+ </toolbarbutton>
+ </hbox>
+
+ </vbox>
+ </toolbar>
+
+ <notificationbox flex="1" id="notificationbox">
+ <div flex="1" id="deck-panels">
+ <vbox id="project-listing-panel" class="project-listing panel-list" flex="1">
+ <div id="project-listing-wrapper" class="panel-list-wrapper">
+ <iframe id="project-listing-panel-details" flex="1" src="project-listing.xhtml" tooltip="aHTMLTooltip"/>
+ </div>
+ </vbox>
+ <splitter class="devtools-side-splitter" id="project-listing-splitter"/>
+ <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-prefs" flex="1" src="prefs.xhtml"/>
+ <iframe id="deck-panel-permissionstable" flex="1" lazysrc="permissionstable.xhtml"/>
+ <iframe id="deck-panel-runtimedetails" flex="1" lazysrc="runtimedetails.xhtml"/>
+ <iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
+ <iframe id="deck-panel-devicepreferences" flex="1" lazysrc="devicepreferences.xhtml"/>
+ <iframe id="deck-panel-devicesettings" flex="1" lazysrc="devicesettings.xhtml"/>
+ <iframe id="deck-panel-logs" flex="1" src="logs.xhtml"/>
+ <iframe id="deck-panel-simulator" flex="1" lazysrc="simulator.xhtml"/>
+ </deck>
+ <splitter class="devtools-side-splitter" id="runtime-listing-splitter"/>
+ <vbox id="runtime-listing-panel" class="runtime-listing panel-list" flex="1">
+ <div id="runtime-listing-wrapper" class="panel-list-wrapper">
+ <iframe id="runtime-listing-panel-details" flex="1" src="runtime-listing.xhtml" tooltip="aHTMLTooltip"/>
+ </div>
+ </vbox>
+ </div>
+ <splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
+ <!-- toolbox iframe will be inserted here -->
+ </notificationbox>
+
+</window>
diff --git a/devtools/client/webide/content/wifi-auth.js b/devtools/client/webide/content/wifi-auth.js
new file mode 100644
index 000000000..5ae5d824c
--- /dev/null
+++ b/devtools/client/webide/content/wifi-auth.js
@@ -0,0 +1,44 @@
+/* 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";
+
+var Cu = Components.utils;
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const QR = require("devtools/shared/qrcode/index");
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ document.getElementById("close").onclick = () => window.close();
+ document.getElementById("no-scanner").onclick = showToken;
+ document.getElementById("yes-scanner").onclick = hideToken;
+ buildUI();
+});
+
+function buildUI() {
+ let { oob } = window.arguments[0];
+ createQR(oob);
+ createToken(oob);
+}
+
+function createQR(oob) {
+ let oobData = JSON.stringify(oob);
+ let imgData = QR.encodeToDataURI(oobData, "L" /* low quality */);
+ document.querySelector("#qr-code img").src = imgData.src;
+}
+
+function createToken(oob) {
+ let token = oob.sha256.replace(/:/g, "").toLowerCase() + oob.k;
+ document.querySelector("#token pre").textContent = token;
+}
+
+function showToken() {
+ document.querySelector("body").setAttribute("token", "true");
+}
+
+function hideToken() {
+ document.querySelector("body").removeAttribute("token");
+}
diff --git a/devtools/client/webide/content/wifi-auth.xhtml b/devtools/client/webide/content/wifi-auth.xhtml
new file mode 100644
index 000000000..cfeec3c96
--- /dev/null
+++ b/devtools/client/webide/content/wifi-auth.xhtml
@@ -0,0 +1,45 @@
+<?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://devtools/locale/webide.dtd" >
+ %webideDTD;
+]>
+
+<html id="devtools:wifi-auth" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf8"/>
+ <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://webide/skin/wifi-auth.css" type="text/css"/>
+ <script type="application/javascript;version=1.8" src="chrome://webide/content/wifi-auth.js"></script>
+ </head>
+ <body>
+
+ <div id="controls">
+ <a id="close">&deck_close;</a>
+ </div>
+
+ <h3 id="header">&wifi_auth_header;</h3>
+ <div id="scan-request">&wifi_auth_scan_request;</div>
+
+ <div id="qr-code">
+ <div id="qr-code-wrapper">
+ <img/>
+ </div>
+ <a id="no-scanner" class="toggle-scanner">&wifi_auth_no_scanner;</a>
+ <div id="qr-size-note">
+ <h5>&wifi_auth_qr_size_note;</h5>
+ </div>
+ </div>
+
+ <div id="token">
+ <div>&wifi_auth_token_request;</div>
+ <pre id="token-value"/>
+ <a id="yes-scanner" class="toggle-scanner">&wifi_auth_yes_scanner;</a>
+ </div>
+
+ </body>
+</html>