diff --git a/application/basilisk/components/customizableui/CustomizableUI.jsm b/application/basilisk/components/customizableui/CustomizableUI.jsm
index cb92639619..81c49876ab 100644
--- a/application/basilisk/components/customizableui/CustomizableUI.jsm
+++ b/application/basilisk/components/customizableui/CustomizableUI.jsm
@@ -38,6 +38,7 @@ const kPrefCustomizationState = "browser.uiCustomization.state";
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
+const kPrefWebIDEInNavbar = "devtools.webide.widget.inNavbarByDefault";
const kExpectedWindowURL = "chrome://browser/content/browser.xul";
@@ -223,6 +224,10 @@ var CustomizableUIInternal = {
"home-button",
];
+ if (Services.prefs.getBoolPref(kPrefWebIDEInNavbar)) {
+ navbarPlacements.push("webide-button");
+ }
+
// Place this last, when createWidget is called for pocket, it will
// append to the toolbar.
if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
diff --git a/devtools/client/framework/devtools-browser.js b/devtools/client/framework/devtools-browser.js
index 83a888ab99..5943743c41 100644
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -123,6 +123,23 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
win.DeveloperToolbar.show(false).catch(console.error);
}
+ // Enable WebIDE?
+ let webIDEEnabled = Services.prefs.getBoolPref("devtools.webide.enabled");
+ idEls = [
+ "appmenu_webide",
+ "menu_webide"
+ ];
+ idEls.forEach(function (idEl) {
+ toggleMenuItem(idEl, webIDEEnabled);
+ });
+
+ let showWebIDEWidget = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
+ if (webIDEEnabled && showWebIDEWidget) {
+ gDevToolsBrowser.installWebIDEWidget();
+ } else {
+ gDevToolsBrowser.uninstallWebIDEWidget();
+ }
+
// Enable Browser Toolbox?
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
diff --git a/devtools/client/framework/gDevTools.jsm b/devtools/client/framework/gDevTools.jsm
index 6e0dc5e836..d825c0eaa3 100644
--- a/devtools/client/framework/gDevTools.jsm
+++ b/devtools/client/framework/gDevTools.jsm
@@ -126,9 +126,16 @@ let gDevToolsBrowserMethods = [
// Used by browser-sets.inc, command
"openConnectScreen",
+ // Used by browser-sets.inc, command
+ // itself, webide widget
+ "openWebIDE",
+
// Used by browser-sets.inc, command
"openContentProcessToolbox",
+ // Used by webide.js
+ "moveWebIDEWidgetInNavbar",
+
// Used by browser.js
"registerBrowserWindow",
@@ -139,6 +146,10 @@ let gDevToolsBrowserMethods = [
"forgetBrowserWindow"
];
this.gDevToolsBrowser = {
+ // Used by webide.js
+ get isWebIDEInitialized() {
+ return browser.isWebIDEInitialized;
+ },
// Used by a test (should be removed)
get _trackedBrowserWindows() {
return browser._trackedBrowserWindows;
diff --git a/devtools/client/locales/en-US/menus.properties b/devtools/client/locales/en-US/menus.properties
index 7030fe17d2..66e158cbd7 100644
--- a/devtools/client/locales/en-US/menus.properties
+++ b/devtools/client/locales/en-US/menus.properties
@@ -54,6 +54,11 @@ devToolbarMenu.accesskey = v
devToolbarMenu.key = VK_F2
devToolbarMenu.keytext = F2
+webide.label = WebIDE
+webide.accesskey = W
+webide.key = VK_F8
+webide.keytext = F8
+
devToolboxMenuItem.label = Toggle Tools
devToolboxMenuItem.accesskey = T
devToolboxMenuItem.key = I
diff --git a/devtools/client/locales/en-US/webide.dtd b/devtools/client/locales/en-US/webide.dtd
new file mode 100644
index 0000000000..554488f6d0
--- /dev/null
+++ b/devtools/client/locales/en-US/webide.dtd
@@ -0,0 +1,222 @@
+
+
+
+ %brandDTD;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/client/locales/en-US/webide.properties b/devtools/client/locales/en-US/webide.properties
new file mode 100644
index 0000000000..1540949063
--- /dev/null
+++ b/devtools/client/locales/en-US/webide.properties
@@ -0,0 +1,92 @@
+# 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/.
+
+title_noApp=WebIDE
+title_app=WebIDE: %S
+
+runtimeButton_label=Select Runtime
+projectButton_label=Open App
+
+mainProcess_label=Main Process
+
+local_runtime=Local Runtime
+remote_runtime=Remote Runtime
+remote_runtime_promptTitle=Remote Runtime
+remote_runtime_promptMessage=hostname:port
+
+importPackagedApp_title=Select Directory
+importHostedApp_title=Open Hosted App
+importHostedApp_header=Enter Manifest URL
+
+selectCustomBinary_title=Select custom B2G binary
+selectCustomProfile_title=Select custom Gaia profile
+
+notification_showTroubleShooting_label=Troubleshooting
+notification_showTroubleShooting_accesskey=T
+
+# LOCALIZATION NOTE (project_tab_loading): This is shown as a temporary tab
+# title for browser tab projects when the tab is still loading.
+project_tab_loading=Loading…
+
+# These messages appear in a notification box when an error occur.
+
+error_cantInstallNotFullyConnected=Can’t install project. Not fully connected.
+error_cantInstallValidationErrors=Can’t install project. Validation errors.
+error_listRunningApps=Can’t get app list from device
+
+# Variable: name of the operation (in english)
+error_operationTimeout=Operation timed out: %1$S
+error_operationFail=Operation failed: %1$S
+
+# Variable: app name
+error_cantConnectToApp=Can’t connect to app: %1$S
+
+# Variable: error message (in english)
+error_cantFetchAddonsJSON=Can’t fetch the add-on list: %S
+
+error_appProjectsLoadFailed=Unable to load project list. This can occur if you’ve used this profile with a newer version of the browser.
+error_folderCreationFailed=Unable to create project folder in the selected directory.
+
+# Variable: runtime app build ID (looks like this %Y%M%D format) and firefox build ID (same format)
+error_runtimeVersionTooRecent=The connected runtime has a more recent build date (%1$S) than your desktop browser (%2$S) does. This is an unsupported setup and may cause DevTools to fail. Please update the browser.
+
+addons_stable=stable
+addons_unstable=unstable
+# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
+# a given simulator version in the "Manage Simulators" pane. %1$S: Firefox OS
+# version in the simulator, ex. 1.3. %2$S: Simulator stability label, ex.
+# "stable" or "unstable".
+addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
+addons_install_button=install
+addons_uninstall_button=uninstall
+addons_adb_label=ADB Helper Add-on
+addons_adapters_label=Tools Adapters Add-on
+addons_adb_warning=USB devices won’t be detected without this add-on
+addons_status_unknown=?
+addons_status_installed=Installed
+addons_status_uninstalled=Not Installed
+addons_status_preparing=preparing
+addons_status_downloading=downloading
+addons_status_installing=installing
+
+runtimedetails_checkno=no
+runtimedetails_checkyes=yes
+runtimedetails_checkunknown=unknown (requires ADB Helper 0.4.0 or later)
+runtimedetails_notUSBDevice=Not a USB device
+
+# Validation status
+status_tooltip=Validation status: %1$S
+status_valid=VALID
+status_warning=WARNINGS
+status_error=ERRORS
+status_unknown=UNKNOWN
+
+# Device preferences and settings
+device_reset_default=Reset to default
+
+# Simulator options
+simulator_custom_device=Custom
+simulator_custom_binary=Custom B2G binary…
+simulator_custom_profile=Custom Gaia profile…
+simulator_default_profile=Use default
diff --git a/devtools/client/menus.js b/devtools/client/menus.js
index 23f024f04a..dbacb367da 100644
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -86,6 +86,17 @@ exports.menuitems = [
},
checkbox: true
},
+ { id: "menu_webide",
+ l10nKey: "webide",
+ disabled: true,
+ oncommand() {
+ gDevToolsBrowser.openWebIDE();
+ },
+ key: {
+ id: "webide",
+ modifiers: "shift"
+ }
+ },
{ id: "menu_browserToolbox",
l10nKey: "browserToolboxMenu",
disabled: true,
diff --git a/devtools/client/moz.build b/devtools/client/moz.build
index 9699ec7261..b55aa5380e 100644
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -34,6 +34,7 @@ DIRS += [
'themes',
'webaudioeditor',
'webconsole',
+ 'webide',
]
# Shim old theme paths used by DevTools add-ons
diff --git a/devtools/client/preferences/devtools.js b/devtools/client/preferences/devtools.js
index e817b3a18b..7e0f5865d5 100644
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -21,6 +21,9 @@ pref("devtools.loader.hotreload", false);
pref("devtools.toolbar.enabled", true);
pref("devtools.toolbar.visible", false);
+// Enable DevTools WebIDE by default
+pref("devtools.webide.enabled", true);
+
// Toolbox preferences
pref("devtools.toolbox.footer.height", 250);
pref("devtools.toolbox.sidebar.width", 500);
diff --git a/devtools/client/projecteditor/lib/plugins/app-manager/app-project-editor.js b/devtools/client/projecteditor/lib/plugins/app-manager/app-project-editor.js
new file mode 100644
index 0000000000..9a66770b0d
--- /dev/null
+++ b/devtools/client/projecteditor/lib/plugins/app-manager/app-project-editor.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { Cu } = require("chrome");
+const { Class } = require("sdk/core/heritage");
+const promise = require("promise");
+const { ItchEditor } = require("devtools/client/projecteditor/lib/editors");
+
+var AppProjectEditor = Class({
+ extends: ItchEditor,
+
+ hidesToolbar: true,
+
+ initialize: function (host) {
+ ItchEditor.prototype.initialize.apply(this, arguments);
+ this.appended = promise.resolve();
+ this.host = host;
+ this.label = "app-manager";
+ },
+
+ destroy: function () {
+ this.elt.remove();
+ this.elt = null;
+ },
+
+ load: function (resource) {
+ let {appManagerOpts} = this.host.project;
+
+ // Only load the frame the first time it is selected
+ if (!this.iframe || this.iframe.getAttribute("src") !== appManagerOpts.projectOverviewURL) {
+
+ this.elt.textContent = "";
+ let iframe = this.iframe = this.elt.ownerDocument.createElement("iframe");
+ let iframeLoaded = this.iframeLoaded = promise.defer();
+
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ iframeLoaded.resolve();
+ });
+
+ iframe.setAttribute("flex", "1");
+ iframe.setAttribute("src", appManagerOpts.projectOverviewURL);
+ this.elt.appendChild(iframe);
+
+ }
+
+ promise.all([this.iframeLoaded.promise, this.appended]).then(() => {
+ this.emit("load");
+ });
+ }
+});
+
+exports.AppProjectEditor = AppProjectEditor;
diff --git a/devtools/client/projecteditor/lib/plugins/app-manager/moz.build b/devtools/client/projecteditor/lib/plugins/app-manager/moz.build
new file mode 100644
index 0000000000..8aae527250
--- /dev/null
+++ b/devtools/client/projecteditor/lib/plugins/app-manager/moz.build
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+DevToolsModules(
+ 'app-project-editor.js',
+ 'plugin.js',
+)
diff --git a/devtools/client/projecteditor/lib/plugins/app-manager/plugin.js b/devtools/client/projecteditor/lib/plugins/app-manager/plugin.js
new file mode 100644
index 0000000000..82bbab34bd
--- /dev/null
+++ b/devtools/client/projecteditor/lib/plugins/app-manager/plugin.js
@@ -0,0 +1,77 @@
+const { Cu } = require("chrome");
+const { Class } = require("sdk/core/heritage");
+const { EventTarget } = require("sdk/event/target");
+const { emit } = require("sdk/event/core");
+const promise = require("promise");
+var { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
+const { AppProjectEditor } = require("./app-project-editor");
+const OPTION_URL = "chrome://devtools/skin/images/tool-options.svg";
+const Services = require("Services");
+const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
+
+var AppManagerRenderer = Class({
+ extends: Plugin,
+
+ isAppManagerProject: function () {
+ return !!this.host.project.appManagerOpts;
+ },
+ editorForResource: function (resource) {
+ if (!resource.parent && this.isAppManagerProject()) {
+ return AppProjectEditor;
+ }
+ },
+ getUI: function (parent) {
+ let doc = parent.ownerDocument;
+ if (parent.childElementCount == 0) {
+ let image = doc.createElement("image");
+ let optionImage = doc.createElement("image");
+ let flexElement = doc.createElement("div");
+ let nameLabel = doc.createElement("span");
+ let statusElement = doc.createElement("div");
+
+ image.className = "project-image";
+ optionImage.className = "project-options";
+ optionImage.setAttribute("src", OPTION_URL);
+ nameLabel.className = "project-name-label";
+ statusElement.className = "project-status";
+ flexElement.className = "project-flex";
+
+ parent.appendChild(image);
+ parent.appendChild(nameLabel);
+ parent.appendChild(flexElement);
+ parent.appendChild(statusElement);
+ parent.appendChild(optionImage);
+ }
+
+ return {
+ image: parent.querySelector(".project-image"),
+ nameLabel: parent.querySelector(".project-name-label"),
+ statusElement: parent.querySelector(".project-status")
+ };
+ },
+ onAnnotate: function (resource, editor, elt) {
+ if (resource.parent || !this.isAppManagerProject()) {
+ return;
+ }
+
+ let {appManagerOpts} = this.host.project;
+ let doc = elt.ownerDocument;
+
+ let {image, nameLabel, statusElement} = this.getUI(elt);
+ let name = appManagerOpts.name || resource.basename;
+ let url = appManagerOpts.iconUrl || "icon-sample.png";
+ let status = appManagerOpts.validationStatus || "unknown";
+ let tooltip = Strings.formatStringFromName("status_tooltip",
+ [Strings.GetStringFromName("status_" + status)], 1);
+
+ nameLabel.textContent = name;
+ image.setAttribute("src", url);
+ statusElement.setAttribute("status", status);
+ statusElement.setAttribute("tooltiptext", tooltip);
+
+ return true;
+ }
+});
+
+exports.AppManagerRenderer = AppManagerRenderer;
+registerPlugin(AppManagerRenderer);
diff --git a/devtools/client/projecteditor/lib/plugins/moz.build b/devtools/client/projecteditor/lib/plugins/moz.build
index 99d864e136..17bff7ce04 100644
--- a/devtools/client/projecteditor/lib/plugins/moz.build
+++ b/devtools/client/projecteditor/lib/plugins/moz.build
@@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
+ 'app-manager',
'delete',
'dirty',
'image-view',
diff --git a/devtools/client/projecteditor/lib/projecteditor.js b/devtools/client/projecteditor/lib/projecteditor.js
index 27127b0a03..a3ef062496 100644
--- a/devtools/client/projecteditor/lib/projecteditor.js
+++ b/devtools/client/projecteditor/lib/projecteditor.js
@@ -31,6 +31,7 @@ require("devtools/client/projecteditor/lib/plugins/new/new");
require("devtools/client/projecteditor/lib/plugins/rename/rename");
require("devtools/client/projecteditor/lib/plugins/save/save");
require("devtools/client/projecteditor/lib/plugins/image-view/plugin");
+require("devtools/client/projecteditor/lib/plugins/app-manager/plugin");
require("devtools/client/projecteditor/lib/plugins/status-bar/plugin");
// Uncomment to enable logging.
diff --git a/devtools/client/shared/telemetry.js b/devtools/client/shared/telemetry.js
index 547b1c07fe..38a21cef6f 100644
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -177,6 +177,23 @@ Telemetry.prototype = {
histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
},
+ webide: {
+ histogram: "DEVTOOLS_WEBIDE_OPENED_COUNT",
+ timerHistogram: "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS"
+ },
+ webideProjectEditor: {
+ histogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_OPENED_COUNT",
+ timerHistogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_TIME_ACTIVE_SECONDS"
+ },
+ webideProjectEditorSave: {
+ histogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_SAVE_COUNT",
+ },
+ webideNewProject: {
+ histogram: "DEVTOOLS_WEBIDE_NEW_PROJECT_COUNT",
+ },
+ webideImportProject: {
+ histogram: "DEVTOOLS_WEBIDE_IMPORT_PROJECT_COUNT",
+ },
custom: {
histogram: "DEVTOOLS_CUSTOM_OPENED_COUNT",
timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS"
diff --git a/devtools/client/webide/components/moz.build b/devtools/client/webide/components/moz.build
new file mode 100644
index 0000000000..d4047c2956
--- /dev/null
+++ b/devtools/client/webide/components/moz.build
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+EXTRA_COMPONENTS += [
+ 'webideCli.js',
+ 'webideComponents.manifest',
+]
diff --git a/devtools/client/webide/components/webideCli.js b/devtools/client/webide/components/webideCli.js
new file mode 100644
index 0000000000..0f75da2c4a
--- /dev/null
+++ b/devtools/client/webide/components/webideCli.js
@@ -0,0 +1,58 @@
+/* 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 Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+
+/**
+ * Handles --webide command line option.
+ */
+
+function webideCli() { }
+
+webideCli.prototype = {
+ handle: function (cmdLine) {
+ if (!cmdLine.handleFlag("webide", false)) {
+ return;
+ }
+
+ // If --webide is used remotely, we don't want to open
+ // a new tab.
+ //
+ // If --webide is used for a new Firefox instance, we
+ // want to open webide only.
+ cmdLine.preventDefault = true;
+
+ let win = Services.wm.getMostRecentWindow("devtools:webide");
+ if (win) {
+ win.focus();
+ } else {
+ win = Services.ww.openWindow(null,
+ "chrome://webide/content/",
+ "webide",
+ "chrome,centerscreen,resizable,dialog=no",
+ null);
+ }
+
+ if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ // If this is a new Firefox instance, and because we will only start
+ // webide, we need to notify "sessionstore-windows-restored" to trigger
+ // addons registration (for simulators and adb helper).
+ Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
+ }
+ },
+
+ helpInfo: "",
+
+ classID: Components.ID("{79b7b44e-de5e-4e4c-b7a2-044003c615d9}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([webideCli]);
diff --git a/devtools/client/webide/components/webideComponents.manifest b/devtools/client/webide/components/webideComponents.manifest
new file mode 100644
index 0000000000..03af9758cb
--- /dev/null
+++ b/devtools/client/webide/components/webideComponents.manifest
@@ -0,0 +1,4 @@
+# webide components
+component {79b7b44e-de5e-4e4c-b7a2-044003c615d9} webideCli.js
+contract @mozilla.org/browser/webide-clh;1 {79b7b44e-de5e-4e4c-b7a2-044003c615d9}
+category command-line-handler a-webide @mozilla.org/browser/webide-clh;1
diff --git a/devtools/client/webide/content/addons.js b/devtools/client/webide/content/addons.js
new file mode 100644
index 0000000000..3948b040f1
--- /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 0000000000..6f3bc1e7c9
--- /dev/null
+++ b/devtools/client/webide/content/addons.xhtml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+ &addons_title;
+
+
+
+
+
diff --git a/devtools/client/webide/content/details.js b/devtools/client/webide/content/details.js
new file mode 100644
index 0000000000..9097cd8c5f
--- /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 0000000000..a04c37b0cc
--- /dev/null
+++ b/devtools/client/webide/content/details.xhtml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &details_showPrepackageLog_button;
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/devicepreferences.js b/devtools/client/webide/content/devicepreferences.js
new file mode 100644
index 0000000000..14c020f120
--- /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 0000000000..dafb6f15ff
--- /dev/null
+++ b/devtools/client/webide/content/devicepreferences.xhtml
@@ -0,0 +1,49 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/devicesettings.js b/devtools/client/webide/content/devicesettings.js
new file mode 100644
index 0000000000..987df5995b
--- /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 0000000000..0406c6f076
--- /dev/null
+++ b/devtools/client/webide/content/devicesettings.xhtml
@@ -0,0 +1,50 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/jar.mn b/devtools/client/webide/content/jar.mn
new file mode 100644
index 0000000000..db79fdb513
--- /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 0000000000..157d83b676
--- /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 0000000000..8d003e509e
--- /dev/null
+++ b/devtools/client/webide/content/logs.xhtml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &logs_title;
+
+
+
+
+
diff --git a/devtools/client/webide/content/monitor.js b/devtools/client/webide/content/monitor.js
new file mode 100644
index 0000000000..a5d80d460e
--- /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 0000000000..552f3826c1
--- /dev/null
+++ b/devtools/client/webide/content/monitor.xhtml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ &monitor_title;
+
+
+
diff --git a/devtools/client/webide/content/moz.build b/devtools/client/webide/content/moz.build
new file mode 100644
index 0000000000..aac3a838c4
--- /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 0000000000..d47bfabecd
--- /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 0000000000..7ff083519c
--- /dev/null
+++ b/devtools/client/webide/content/newapp.xul
@@ -0,0 +1,33 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+ &newAppLoadingTemplate;
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/permissionstable.js b/devtools/client/webide/content/permissionstable.js
new file mode 100644
index 0000000000..22c74bd0d7
--- /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 0000000000..361cfece88
--- /dev/null
+++ b/devtools/client/webide/content/permissionstable.xhtml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+ &permissionstable_title;
+
+
+
+ &permissionstable_name_header;
+ type:web
+ type:privileged
+ type:certified
+
+
+
+
diff --git a/devtools/client/webide/content/prefs.js b/devtools/client/webide/content/prefs.js
new file mode 100644
index 0000000000..75f6233ba6
--- /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 0000000000..726ca772cb
--- /dev/null
+++ b/devtools/client/webide/content/prefs.xhtml
@@ -0,0 +1,112 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+ &prefs_title;
+
+ &prefs_general_title;
+
+
+
+ &prefs_editor_title;
+
+
+
+
+
diff --git a/devtools/client/webide/content/project-listing.js b/devtools/client/webide/content/project-listing.js
new file mode 100644
index 0000000000..5641f6c0cb
--- /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 0000000000..337befe5d8
--- /dev/null
+++ b/devtools/client/webide/content/project-listing.xhtml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
&projectMenu_newApp_label;
+
&projectMenu_importPackagedApp_label;
+
&projectMenu_importHostedApp_label;
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/project-panel.js b/devtools/client/webide/content/project-panel.js
new file mode 100644
index 0000000000..54eab82518
--- /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 0000000000..0a1a40a2a0
--- /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 0000000000..f648fac12c
--- /dev/null
+++ b/devtools/client/webide/content/runtime-listing.xhtml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
&runtimePanel_nousbdevice;
+
&runtimePanel_noadbhelper;
+
+
+
+
+
+
&runtimePanel_installsimulator;
+
+
+
+ &runtimeMenu_showDetails_label;
+ &runtimeMenu_showPermissionTable_label;
+ &runtimeMenu_showDevicePrefs_label;
+ &runtimeMenu_showSettings_label;
+ &runtimeMenu_takeScreenshot_label;
+ &runtimeMenu_disconnect_label;
+
+
+
+
+
diff --git a/devtools/client/webide/content/runtime-panel.js b/devtools/client/webide/content/runtime-panel.js
new file mode 100644
index 0000000000..3646fa15cc
--- /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 0000000000..dea423e810
--- /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 0000000000..b2f74728ad
--- /dev/null
+++ b/devtools/client/webide/content/runtimedetails.xhtml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ %webideDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+ &runtimedetails_title;
+
+
+
+ &runtimedetails_adbIsRoot;
+
+ &runtimedetails_summonADBRoot;
+ &runtimedetails_ADBRootWarning;
+
+
+
+ &runtimedetails_unrestrictedPrivileges;
+
+ &runtimedetails_requestPrivileges;
+ &runtimedetails_privilegesWarning;
+
+
+
+
+
+
+
diff --git a/devtools/client/webide/content/simulator.js b/devtools/client/webide/content/simulator.js
new file mode 100644
index 0000000000..ddc1cbed12
--- /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