mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 11:57:32 +09:00
Revert "Remove WebIDE devtools component."
This reverts commit 8c1500707f.
# Conflicts:
# application/basilisk/components/customizableui/CustomizableUI.jsm
This commit is contained in:
parent
4ea7de218b
commit
df89b48e30
163 changed files with 13812 additions and 0 deletions
|
|
@ -38,6 +38,7 @@ const kPrefCustomizationState = "browser.uiCustomization.state";
|
||||||
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
|
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
|
||||||
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
|
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
|
||||||
const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
|
const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
|
||||||
|
const kPrefWebIDEInNavbar = "devtools.webide.widget.inNavbarByDefault";
|
||||||
|
|
||||||
const kExpectedWindowURL = "chrome://browser/content/browser.xul";
|
const kExpectedWindowURL = "chrome://browser/content/browser.xul";
|
||||||
|
|
||||||
|
|
@ -223,6 +224,10 @@ var CustomizableUIInternal = {
|
||||||
"home-button",
|
"home-button",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (Services.prefs.getBoolPref(kPrefWebIDEInNavbar)) {
|
||||||
|
navbarPlacements.push("webide-button");
|
||||||
|
}
|
||||||
|
|
||||||
// Place this last, when createWidget is called for pocket, it will
|
// Place this last, when createWidget is called for pocket, it will
|
||||||
// append to the toolbar.
|
// append to the toolbar.
|
||||||
if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
|
if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,23 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
||||||
win.DeveloperToolbar.show(false).catch(console.error);
|
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?
|
// Enable Browser Toolbox?
|
||||||
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
|
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
|
||||||
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
|
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,16 @@ let gDevToolsBrowserMethods = [
|
||||||
// Used by browser-sets.inc, command
|
// Used by browser-sets.inc, command
|
||||||
"openConnectScreen",
|
"openConnectScreen",
|
||||||
|
|
||||||
|
// Used by browser-sets.inc, command
|
||||||
|
// itself, webide widget
|
||||||
|
"openWebIDE",
|
||||||
|
|
||||||
// Used by browser-sets.inc, command
|
// Used by browser-sets.inc, command
|
||||||
"openContentProcessToolbox",
|
"openContentProcessToolbox",
|
||||||
|
|
||||||
|
// Used by webide.js
|
||||||
|
"moveWebIDEWidgetInNavbar",
|
||||||
|
|
||||||
// Used by browser.js
|
// Used by browser.js
|
||||||
"registerBrowserWindow",
|
"registerBrowserWindow",
|
||||||
|
|
||||||
|
|
@ -139,6 +146,10 @@ let gDevToolsBrowserMethods = [
|
||||||
"forgetBrowserWindow"
|
"forgetBrowserWindow"
|
||||||
];
|
];
|
||||||
this.gDevToolsBrowser = {
|
this.gDevToolsBrowser = {
|
||||||
|
// Used by webide.js
|
||||||
|
get isWebIDEInitialized() {
|
||||||
|
return browser.isWebIDEInitialized;
|
||||||
|
},
|
||||||
// Used by a test (should be removed)
|
// Used by a test (should be removed)
|
||||||
get _trackedBrowserWindows() {
|
get _trackedBrowserWindows() {
|
||||||
return browser._trackedBrowserWindows;
|
return browser._trackedBrowserWindows;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@ devToolbarMenu.accesskey = v
|
||||||
devToolbarMenu.key = VK_F2
|
devToolbarMenu.key = VK_F2
|
||||||
devToolbarMenu.keytext = F2
|
devToolbarMenu.keytext = F2
|
||||||
|
|
||||||
|
webide.label = WebIDE
|
||||||
|
webide.accesskey = W
|
||||||
|
webide.key = VK_F8
|
||||||
|
webide.keytext = F8
|
||||||
|
|
||||||
devToolboxMenuItem.label = Toggle Tools
|
devToolboxMenuItem.label = Toggle Tools
|
||||||
devToolboxMenuItem.accesskey = T
|
devToolboxMenuItem.accesskey = T
|
||||||
devToolboxMenuItem.key = I
|
devToolboxMenuItem.key = I
|
||||||
|
|
|
||||||
222
devtools/client/locales/en-US/webide.dtd
Normal file
222
devtools/client/locales/en-US/webide.dtd
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
<!-- 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/. -->
|
||||||
|
|
||||||
|
<!ENTITY % brandDTD
|
||||||
|
SYSTEM "chrome://branding/locale/brand.dtd">
|
||||||
|
%brandDTD;
|
||||||
|
|
||||||
|
<!ENTITY windowTitle "&brandShortName; WebIDE">
|
||||||
|
|
||||||
|
<!ENTITY projectMenu_label "Project">
|
||||||
|
<!ENTITY projectMenu_accesskey "P">
|
||||||
|
<!ENTITY projectMenu_newApp_label "New App…">
|
||||||
|
<!ENTITY projectMenu_newApp_accesskey "N">
|
||||||
|
<!ENTITY projectMenu_importPackagedApp_label "Open Packaged App…">
|
||||||
|
<!ENTITY projectMenu_importPackagedApp_accesskey "P">
|
||||||
|
<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
|
||||||
|
<!ENTITY projectMenu_importHostedApp_accesskey "H">
|
||||||
|
<!ENTITY projectMenu_selectApp_label "Open App…">
|
||||||
|
<!ENTITY projectMenu_selectApp_accesskey "O">
|
||||||
|
<!ENTITY projectMenu_play_label "Install and Run">
|
||||||
|
<!ENTITY projectMenu_play_accesskey "I">
|
||||||
|
<!ENTITY projectMenu_stop_label "Stop App">
|
||||||
|
<!ENTITY projectMenu_stop_accesskey "S">
|
||||||
|
<!ENTITY projectMenu_debug_label "Debug App">
|
||||||
|
<!ENTITY projectMenu_debug_accesskey "D">
|
||||||
|
<!ENTITY projectMenu_remove_label "Remove Project">
|
||||||
|
<!ENTITY projectMenu_remove_accesskey "R">
|
||||||
|
<!ENTITY projectMenu_showPrefs_label "Preferences">
|
||||||
|
<!ENTITY projectMenu_showPrefs_accesskey "e">
|
||||||
|
<!ENTITY projectMenu_manageComponents_label "Manage Extra Components">
|
||||||
|
<!ENTITY projectMenu_manageComponents_accesskey "M">
|
||||||
|
<!ENTITY projectMenu_refreshTabs_label "Refresh Tabs">
|
||||||
|
|
||||||
|
<!ENTITY runtimeMenu_label "Runtime">
|
||||||
|
<!ENTITY runtimeMenu_accesskey "R">
|
||||||
|
<!ENTITY runtimeMenu_disconnect_label "Disconnect">
|
||||||
|
<!ENTITY runtimeMenu_disconnect_accesskey "D">
|
||||||
|
<!ENTITY runtimeMenu_showPermissionTable_label "Permissions Table">
|
||||||
|
<!ENTITY runtimeMenu_showPermissionTable_accesskey "P">
|
||||||
|
<!ENTITY runtimeMenu_takeScreenshot_label "Screenshot">
|
||||||
|
<!ENTITY runtimeMenu_takeScreenshot_accesskey "S">
|
||||||
|
<!ENTITY runtimeMenu_showDetails_label "Runtime Info">
|
||||||
|
<!ENTITY runtimeMenu_showDetails_accesskey "E">
|
||||||
|
<!ENTITY runtimeMenu_showMonitor_label "Monitor">
|
||||||
|
<!ENTITY runtimeMenu_showMonitor_accesskey "M">
|
||||||
|
<!ENTITY runtimeMenu_showDevicePrefs_label "Device Preferences">
|
||||||
|
<!ENTITY runtimeMenu_showDevicePrefs_accesskey "D">
|
||||||
|
<!ENTITY runtimeMenu_showSettings_label "Device Settings">
|
||||||
|
<!ENTITY runtimeMenu_showSettings_accesskey "s">
|
||||||
|
|
||||||
|
<!ENTITY viewMenu_label "View">
|
||||||
|
<!ENTITY viewMenu_accesskey "V">
|
||||||
|
<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
|
||||||
|
<!ENTITY viewMenu_toggleEditor_accesskey "E">
|
||||||
|
<!ENTITY viewMenu_zoomin_label "Zoom In">
|
||||||
|
<!ENTITY viewMenu_zoomin_accesskey "I">
|
||||||
|
<!ENTITY viewMenu_zoomout_label "Zoom Out">
|
||||||
|
<!ENTITY viewMenu_zoomout_accesskey "O">
|
||||||
|
<!ENTITY viewMenu_resetzoom_label "Reset Zoom">
|
||||||
|
<!ENTITY viewMenu_resetzoom_accesskey "R">
|
||||||
|
|
||||||
|
<!ENTITY projectButton_label "Open App">
|
||||||
|
<!ENTITY runtimeButton_label "Select Runtime">
|
||||||
|
|
||||||
|
<!-- We try to repicate browser' bindings: -->
|
||||||
|
<!-- quit app -->
|
||||||
|
<!ENTITY key_quit "W">
|
||||||
|
<!-- open menu -->
|
||||||
|
<!ENTITY key_showProjectPanel "O">
|
||||||
|
<!-- reload app -->
|
||||||
|
<!ENTITY key_play "R">
|
||||||
|
<!-- show toolbox -->
|
||||||
|
<!ENTITY key_toggleToolbox "VK_F12">
|
||||||
|
<!-- toggle sidebar -->
|
||||||
|
<!ENTITY key_toggleEditor "B">
|
||||||
|
<!-- zoom -->
|
||||||
|
<!ENTITY key_zoomin "+">
|
||||||
|
<!ENTITY key_zoomin2 "=">
|
||||||
|
<!ENTITY key_zoomout "-">
|
||||||
|
<!ENTITY key_resetzoom "0">
|
||||||
|
|
||||||
|
<!ENTITY projectPanel_myProjects "My Projects">
|
||||||
|
<!ENTITY projectPanel_runtimeApps "Runtime Apps">
|
||||||
|
<!ENTITY projectPanel_tabs "Tabs">
|
||||||
|
<!ENTITY runtimePanel_usb "USB Devices">
|
||||||
|
<!ENTITY runtimePanel_wifi "Wi-Fi Devices">
|
||||||
|
<!ENTITY runtimePanel_simulator "Simulators">
|
||||||
|
<!ENTITY runtimePanel_other "Other">
|
||||||
|
<!ENTITY runtimePanel_installsimulator "Install Simulator">
|
||||||
|
<!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
|
||||||
|
<!ENTITY runtimePanel_nousbdevice "Can’t see your device?">
|
||||||
|
<!ENTITY runtimePanel_refreshDevices_label "Refresh Devices">
|
||||||
|
|
||||||
|
<!-- Lense -->
|
||||||
|
<!ENTITY details_valid_header "valid">
|
||||||
|
<!ENTITY details_warning_header "warnings">
|
||||||
|
<!ENTITY details_error_header "errors">
|
||||||
|
<!ENTITY details_description "Description">
|
||||||
|
<!ENTITY details_location "Location">
|
||||||
|
<!ENTITY details_manifestURL "App ID">
|
||||||
|
<!ENTITY details_removeProject_button "Remove Project">
|
||||||
|
<!ENTITY details_showPrepackageLog_button "Show Pre-package Log">
|
||||||
|
|
||||||
|
<!-- New App -->
|
||||||
|
<!ENTITY newAppWindowTitle "New App">
|
||||||
|
<!ENTITY newAppHeader "Select template">
|
||||||
|
<!ENTITY newAppLoadingTemplate "Loading templates…">
|
||||||
|
<!ENTITY newAppProjectName "Project Name:">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Decks -->
|
||||||
|
|
||||||
|
<!ENTITY deck_close "Close">
|
||||||
|
|
||||||
|
<!-- Addons -->
|
||||||
|
<!ENTITY addons_title "Extra Components">
|
||||||
|
<!ENTITY addons_aboutaddons "Open Add-ons Manager">
|
||||||
|
|
||||||
|
<!-- Prefs -->
|
||||||
|
<!ENTITY prefs_title "Preferences">
|
||||||
|
<!ENTITY prefs_editor_title "Editor">
|
||||||
|
<!ENTITY prefs_general_title "General">
|
||||||
|
<!ENTITY prefs_restore "Restore Defaults">
|
||||||
|
<!ENTITY prefs_manage_components "Manage Extra Components">
|
||||||
|
<!ENTITY prefs_options_autoconnectruntime "Reconnect to previous runtime">
|
||||||
|
<!ENTITY prefs_options_autoconnectruntime_tooltip "Reconnect to previous runtime when WebIDE starts">
|
||||||
|
<!ENTITY prefs_options_rememberlastproject "Remember last project">
|
||||||
|
<!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
|
||||||
|
<!ENTITY prefs_options_templatesurl "Templates URL">
|
||||||
|
<!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
|
||||||
|
<!ENTITY prefs_options_showeditor "Show editor">
|
||||||
|
<!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
|
||||||
|
<!ENTITY prefs_options_tabsize "Tab size">
|
||||||
|
<!ENTITY prefs_options_expandtab "Soft tabs">
|
||||||
|
<!ENTITY prefs_options_expandtab_tooltip "Use spaces instead of the tab character">
|
||||||
|
<!ENTITY prefs_options_detectindentation "Autoindent">
|
||||||
|
<!ENTITY prefs_options_detectindentation_tooltip "Guess indentation based on source content">
|
||||||
|
<!ENTITY prefs_options_autocomplete "Autocomplete">
|
||||||
|
<!ENTITY prefs_options_autocomplete_tooltip "Enable code autocompletion">
|
||||||
|
<!ENTITY prefs_options_autoclosebrackets "Autoclose brackets">
|
||||||
|
<!ENTITY prefs_options_autoclosebrackets_tooltip "Automatically insert closing brackets">
|
||||||
|
<!ENTITY prefs_options_keybindings "Keybindings">
|
||||||
|
<!ENTITY prefs_options_keybindings_default "Default">
|
||||||
|
<!ENTITY prefs_options_autosavefiles "Autosave files">
|
||||||
|
<!ENTITY prefs_options_autosavefiles_tooltip "Automatically save edited files before running project">
|
||||||
|
|
||||||
|
<!-- Permissions Table -->
|
||||||
|
<!ENTITY permissionstable_title "Permissions Table">
|
||||||
|
<!ENTITY permissionstable_name_header "Name">
|
||||||
|
|
||||||
|
<!-- Runtime Details -->
|
||||||
|
<!ENTITY runtimedetails_title "Runtime Info">
|
||||||
|
<!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
|
||||||
|
<!ENTITY runtimedetails_summonADBRoot "root device">
|
||||||
|
<!ENTITY runtimedetails_ADBRootWarning "(requires unlocked bootloader)">
|
||||||
|
<!ENTITY runtimedetails_unrestrictedPrivileges "Unrestricted DevTools privileges: ">
|
||||||
|
<!ENTITY runtimedetails_requestPrivileges "request higher privileges">
|
||||||
|
<!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
|
||||||
|
|
||||||
|
<!-- Device Preferences and Settings -->
|
||||||
|
<!ENTITY device_typeboolean "Boolean">
|
||||||
|
<!ENTITY device_typenumber "Integer">
|
||||||
|
<!ENTITY device_typestring "String">
|
||||||
|
<!ENTITY device_typeobject "Object">
|
||||||
|
<!ENTITY device_typenone "Select a type">
|
||||||
|
|
||||||
|
<!-- Device Preferences -->
|
||||||
|
<!ENTITY devicepreference_title "Device Preferences">
|
||||||
|
<!ENTITY devicepreference_search "Search preferences">
|
||||||
|
<!ENTITY devicepreference_newname "New preference name">
|
||||||
|
<!ENTITY devicepreference_newtext "Preference value">
|
||||||
|
<!ENTITY devicepreference_addnew "Add new preference">
|
||||||
|
|
||||||
|
<!-- Device Settings -->
|
||||||
|
<!ENTITY devicesetting_title "Device Settings">
|
||||||
|
<!ENTITY devicesetting_search "Search settings">
|
||||||
|
<!ENTITY devicesetting_newname "New setting name">
|
||||||
|
<!ENTITY devicesetting_newtext "Setting value">
|
||||||
|
<!ENTITY devicesetting_addnew "Add new setting">
|
||||||
|
|
||||||
|
<!-- Monitor -->
|
||||||
|
<!ENTITY monitor_title "Monitor">
|
||||||
|
<!ENTITY monitor_help "Help">
|
||||||
|
|
||||||
|
<!-- WiFi Authentication -->
|
||||||
|
<!-- LOCALIZATION NOTE (wifi_auth_header): The header displayed on the dialog
|
||||||
|
that instructs the user to transfer an authentication token to the
|
||||||
|
server. -->
|
||||||
|
<!ENTITY wifi_auth_header "Client Identification">
|
||||||
|
<!-- LOCALIZATION NOTE (wifi_auth_scan_request): Instructions requesting the
|
||||||
|
user to transfer authentication info by scanning a QR code. -->
|
||||||
|
<!ENTITY wifi_auth_scan_request "The endpoint you are connecting to needs more information to authenticate this connection. Please scan the QR code below via the prompt on your other device.">
|
||||||
|
<!-- LOCALIZATION NOTE (wifi_auth_no_scanner): Link text to assist users with
|
||||||
|
devices that can't scan a QR code. -->
|
||||||
|
<!ENTITY wifi_auth_no_scanner "No QR scanner prompt?">
|
||||||
|
<!-- LOCALIZATION NOTE (wifi_auth_yes_scanner): Link text to assist users with
|
||||||
|
devices that can scan a QR code. -->
|
||||||
|
<!ENTITY wifi_auth_yes_scanner "Have a QR scanner prompt?">
|
||||||
|
<!-- LOCALIZATION NOTE (wifi_auth_token_request): Instructions requesting the
|
||||||
|
user to transfer authentication info by transferring a token. -->
|
||||||
|
<!ENTITY wifi_auth_token_request "If your other device asks for a token instead of scanning a QR code, please copy the value below to the other device:">
|
||||||
|
<!ENTITY wifi_auth_qr_size_note "If the QR code appears too small for the connection to be successfully established, try zooming or enlarging the window.">
|
||||||
|
|
||||||
|
<!-- Logs panel -->
|
||||||
|
<!ENTITY logs_title "Pre-packaging Command Logs">
|
||||||
|
|
||||||
|
<!-- Simulator Options -->
|
||||||
|
<!ENTITY simulator_title "Simulator Options">
|
||||||
|
<!ENTITY simulator_remove "Delete Simulator">
|
||||||
|
<!ENTITY simulator_reset "Restore Defaults">
|
||||||
|
<!ENTITY simulator_name "Name">
|
||||||
|
<!ENTITY simulator_software "Software">
|
||||||
|
<!ENTITY simulator_version "Version">
|
||||||
|
<!ENTITY simulator_profile "Profile">
|
||||||
|
<!ENTITY simulator_hardware "Hardware">
|
||||||
|
<!ENTITY simulator_device "Device">
|
||||||
|
<!ENTITY simulator_screenSize "Screen">
|
||||||
|
<!ENTITY simulator_pixelRatio "Pixel Ratio">
|
||||||
|
<!ENTITY simulator_tv_data "TV Simulation">
|
||||||
|
<!ENTITY simulator_tv_data_open "Config Data">
|
||||||
|
<!ENTITY simulator_tv_data_open_button "Open Config Directory…">
|
||||||
92
devtools/client/locales/en-US/webide.properties
Normal file
92
devtools/client/locales/en-US/webide.properties
Normal file
|
|
@ -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
|
||||||
|
|
@ -86,6 +86,17 @@ exports.menuitems = [
|
||||||
},
|
},
|
||||||
checkbox: true
|
checkbox: true
|
||||||
},
|
},
|
||||||
|
{ id: "menu_webide",
|
||||||
|
l10nKey: "webide",
|
||||||
|
disabled: true,
|
||||||
|
oncommand() {
|
||||||
|
gDevToolsBrowser.openWebIDE();
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
id: "webide",
|
||||||
|
modifiers: "shift"
|
||||||
|
}
|
||||||
|
},
|
||||||
{ id: "menu_browserToolbox",
|
{ id: "menu_browserToolbox",
|
||||||
l10nKey: "browserToolboxMenu",
|
l10nKey: "browserToolboxMenu",
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ DIRS += [
|
||||||
'themes',
|
'themes',
|
||||||
'webaudioeditor',
|
'webaudioeditor',
|
||||||
'webconsole',
|
'webconsole',
|
||||||
|
'webide',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Shim old theme paths used by DevTools add-ons
|
# Shim old theme paths used by DevTools add-ons
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ pref("devtools.loader.hotreload", false);
|
||||||
pref("devtools.toolbar.enabled", true);
|
pref("devtools.toolbar.enabled", true);
|
||||||
pref("devtools.toolbar.visible", false);
|
pref("devtools.toolbar.visible", false);
|
||||||
|
|
||||||
|
// Enable DevTools WebIDE by default
|
||||||
|
pref("devtools.webide.enabled", true);
|
||||||
|
|
||||||
// Toolbox preferences
|
// Toolbox preferences
|
||||||
pref("devtools.toolbox.footer.height", 250);
|
pref("devtools.toolbox.footer.height", 250);
|
||||||
pref("devtools.toolbox.sidebar.width", 500);
|
pref("devtools.toolbox.sidebar.width", 500);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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',
|
||||||
|
)
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
DIRS += [
|
DIRS += [
|
||||||
|
'app-manager',
|
||||||
'delete',
|
'delete',
|
||||||
'dirty',
|
'dirty',
|
||||||
'image-view',
|
'image-view',
|
||||||
|
|
|
||||||
|
|
@ -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/rename/rename");
|
||||||
require("devtools/client/projecteditor/lib/plugins/save/save");
|
require("devtools/client/projecteditor/lib/plugins/save/save");
|
||||||
require("devtools/client/projecteditor/lib/plugins/image-view/plugin");
|
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");
|
require("devtools/client/projecteditor/lib/plugins/status-bar/plugin");
|
||||||
|
|
||||||
// Uncomment to enable logging.
|
// Uncomment to enable logging.
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,23 @@ Telemetry.prototype = {
|
||||||
histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
|
histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
|
||||||
timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
|
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: {
|
custom: {
|
||||||
histogram: "DEVTOOLS_CUSTOM_OPENED_COUNT",
|
histogram: "DEVTOOLS_CUSTOM_OPENED_COUNT",
|
||||||
timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS"
|
timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS"
|
||||||
|
|
|
||||||
10
devtools/client/webide/components/moz.build
Normal file
10
devtools/client/webide/components/moz.build
Normal file
|
|
@ -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',
|
||||||
|
]
|
||||||
58
devtools/client/webide/components/webideCli.js
Normal file
58
devtools/client/webide/components/webideCli.js
Normal file
|
|
@ -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]);
|
||||||
|
|
@ -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
|
||||||
135
devtools/client/webide/content/addons.js
Normal file
135
devtools/client/webide/content/addons.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
31
devtools/client/webide/content/addons.xhtml
Normal file
31
devtools/client/webide/content/addons.xhtml
Normal file
|
|
@ -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>
|
||||||
139
devtools/client/webide/content/details.js
Normal file
139
devtools/client/webide/content/details.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
54
devtools/client/webide/content/details.xhtml
Normal file
54
devtools/client/webide/content/details.xhtml
Normal file
|
|
@ -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>
|
||||||
81
devtools/client/webide/content/devicepreferences.js
Normal file
81
devtools/client/webide/content/devicepreferences.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
devtools/client/webide/content/devicepreferences.xhtml
Normal file
49
devtools/client/webide/content/devicepreferences.xhtml
Normal file
|
|
@ -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>
|
||||||
81
devtools/client/webide/content/devicesettings.js
Normal file
81
devtools/client/webide/content/devicesettings.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
devtools/client/webide/content/devicesettings.xhtml
Normal file
50
devtools/client/webide/content/devicesettings.xhtml
Normal file
|
|
@ -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>
|
||||||
38
devtools/client/webide/content/jar.mn
Normal file
38
devtools/client/webide/content/jar.mn
Normal file
|
|
@ -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)
|
||||||
70
devtools/client/webide/content/logs.js
Normal file
70
devtools/client/webide/content/logs.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
33
devtools/client/webide/content/logs.xhtml
Normal file
33
devtools/client/webide/content/logs.xhtml
Normal file
|
|
@ -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>
|
||||||
741
devtools/client/webide/content/monitor.js
Normal file
741
devtools/client/webide/content/monitor.js
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
31
devtools/client/webide/content/monitor.xhtml
Normal file
31
devtools/client/webide/content/monitor.xhtml
Normal file
|
|
@ -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>
|
||||||
7
devtools/client/webide/content/moz.build
Normal file
7
devtools/client/webide/content/moz.build
Normal file
|
|
@ -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']
|
||||||
175
devtools/client/webide/content/newapp.js
Normal file
175
devtools/client/webide/content/newapp.js
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
33
devtools/client/webide/content/newapp.xul
Normal file
33
devtools/client/webide/content/newapp.xul
Normal file
|
|
@ -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>
|
||||||
78
devtools/client/webide/content/permissionstable.js
Normal file
78
devtools/client/webide/content/permissionstable.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
devtools/client/webide/content/permissionstable.xhtml
Normal file
36
devtools/client/webide/content/permissionstable.xhtml
Normal file
|
|
@ -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>
|
||||||
108
devtools/client/webide/content/prefs.js
Normal file
108
devtools/client/webide/content/prefs.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
devtools/client/webide/content/prefs.xhtml
Normal file
112
devtools/client/webide/content/prefs.xhtml
Normal file
|
|
@ -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>
|
||||||
42
devtools/client/webide/content/project-listing.js
Normal file
42
devtools/client/webide/content/project-listing.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
35
devtools/client/webide/content/project-listing.xhtml
Normal file
35
devtools/client/webide/content/project-listing.xhtml
Normal file
|
|
@ -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>
|
||||||
11
devtools/client/webide/content/project-panel.js
Normal file
11
devtools/client/webide/content/project-panel.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
66
devtools/client/webide/content/runtime-listing.js
Normal file
66
devtools/client/webide/content/runtime-listing.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
45
devtools/client/webide/content/runtime-listing.xhtml
Normal file
45
devtools/client/webide/content/runtime-listing.xhtml
Normal file
|
|
@ -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>
|
||||||
11
devtools/client/webide/content/runtime-panel.js
Normal file
11
devtools/client/webide/content/runtime-panel.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
153
devtools/client/webide/content/runtimedetails.js
Normal file
153
devtools/client/webide/content/runtimedetails.js
Normal file
|
|
@ -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));
|
||||||
|
}
|
||||||
46
devtools/client/webide/content/runtimedetails.xhtml
Normal file
46
devtools/client/webide/content/runtimedetails.xhtml
Normal file
|
|
@ -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>
|
||||||
352
devtools/client/webide/content/simulator.js
Normal file
352
devtools/client/webide/content/simulator.js
Normal file
|
|
@ -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();
|
||||||
|
};
|
||||||
|
});
|
||||||
99
devtools/client/webide/content/simulator.xhtml
Normal file
99
devtools/client/webide/content/simulator.xhtml
Normal file
|
|
@ -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>
|
||||||
1157
devtools/client/webide/content/webide.js
Normal file
1157
devtools/client/webide/content/webide.js
Normal file
File diff suppressed because it is too large
Load diff
178
devtools/client/webide/content/webide.xul
Normal file
178
devtools/client/webide/content/webide.xul
Normal file
|
|
@ -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>
|
||||||
44
devtools/client/webide/content/wifi-auth.js
Normal file
44
devtools/client/webide/content/wifi-auth.js
Normal file
|
|
@ -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");
|
||||||
|
}
|
||||||
45
devtools/client/webide/content/wifi-auth.xhtml
Normal file
45
devtools/client/webide/content/wifi-auth.xhtml
Normal file
|
|
@ -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>
|
||||||
197
devtools/client/webide/modules/addons.js
Normal file
197
devtools/client/webide/modules/addons.js
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* 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 promise = require("promise");
|
||||||
|
const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
|
||||||
|
const Services = require("Services");
|
||||||
|
const {getJSON} = require("devtools/client/shared/getjson");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
|
||||||
|
const ADDONS_URL = "devtools.webide.addonsURL";
|
||||||
|
|
||||||
|
var SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
|
||||||
|
var ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
|
||||||
|
var ADAPTERS_LINK = Services.prefs.getCharPref("devtools.webide.adaptersAddonURL");
|
||||||
|
var SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
|
||||||
|
var ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
|
||||||
|
var ADAPTERS_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adaptersAddonID");
|
||||||
|
|
||||||
|
var platform = Services.appShell.hiddenDOMWindow.navigator.platform;
|
||||||
|
var OS = "";
|
||||||
|
if (platform.indexOf("Win") != -1) {
|
||||||
|
OS = "win32";
|
||||||
|
} else if (platform.indexOf("Mac") != -1) {
|
||||||
|
OS = "mac64";
|
||||||
|
} else if (platform.indexOf("Linux") != -1) {
|
||||||
|
if (platform.indexOf("x86_64") != -1) {
|
||||||
|
OS = "linux64";
|
||||||
|
} else {
|
||||||
|
OS = "linux32";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addonsListener = {};
|
||||||
|
addonsListener.onEnabled =
|
||||||
|
addonsListener.onDisabled =
|
||||||
|
addonsListener.onInstalled =
|
||||||
|
addonsListener.onUninstalled = (updatedAddon) => {
|
||||||
|
GetAvailableAddons().then(addons => {
|
||||||
|
for (let a of [...addons.simulators, addons.adb, addons.adapters]) {
|
||||||
|
if (a.addonID == updatedAddon.id) {
|
||||||
|
a.updateInstallStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
AddonManager.addAddonListener(addonsListener);
|
||||||
|
|
||||||
|
var GetAvailableAddons_promise = null;
|
||||||
|
var GetAvailableAddons = exports.GetAvailableAddons = function () {
|
||||||
|
if (!GetAvailableAddons_promise) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
GetAvailableAddons_promise = deferred.promise;
|
||||||
|
let addons = {
|
||||||
|
simulators: [],
|
||||||
|
adb: null
|
||||||
|
};
|
||||||
|
getJSON(ADDONS_URL).then(json => {
|
||||||
|
for (let stability in json) {
|
||||||
|
for (let version of json[stability]) {
|
||||||
|
addons.simulators.push(new SimulatorAddon(stability, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addons.adb = new ADBAddon();
|
||||||
|
addons.adapters = new AdaptersAddon();
|
||||||
|
deferred.resolve(addons);
|
||||||
|
}, e => {
|
||||||
|
GetAvailableAddons_promise = null;
|
||||||
|
deferred.reject(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return GetAvailableAddons_promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.ForgetAddonsList = function () {
|
||||||
|
GetAvailableAddons_promise = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Addon() {}
|
||||||
|
Addon.prototype = {
|
||||||
|
_status: "unknown",
|
||||||
|
set status(value) {
|
||||||
|
if (this._status != value) {
|
||||||
|
this._status = value;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get status() {
|
||||||
|
return this._status;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateInstallStatus: function () {
|
||||||
|
AddonManager.getAddonByID(this.addonID, (addon) => {
|
||||||
|
if (addon && !addon.userDisabled) {
|
||||||
|
this.status = "installed";
|
||||||
|
} else {
|
||||||
|
this.status = "uninstalled";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
install: function () {
|
||||||
|
AddonManager.getAddonByID(this.addonID, (addon) => {
|
||||||
|
if (addon && !addon.userDisabled) {
|
||||||
|
this.status = "installed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.status = "preparing";
|
||||||
|
if (addon && addon.userDisabled) {
|
||||||
|
addon.userDisabled = false;
|
||||||
|
} else {
|
||||||
|
AddonManager.getInstallForURL(this.xpiLink, (install) => {
|
||||||
|
install.addListener(this);
|
||||||
|
install.install();
|
||||||
|
}, "application/x-xpinstall");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall: function () {
|
||||||
|
AddonManager.getAddonByID(this.addonID, (addon) => {
|
||||||
|
addon.uninstall();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
installFailureHandler: function (install, message) {
|
||||||
|
this.status = "uninstalled";
|
||||||
|
this.emit("failure", message);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDownloadStarted: function () {
|
||||||
|
this.status = "downloading";
|
||||||
|
},
|
||||||
|
|
||||||
|
onInstallStarted: function () {
|
||||||
|
this.status = "installing";
|
||||||
|
},
|
||||||
|
|
||||||
|
onDownloadProgress: function (install) {
|
||||||
|
if (install.maxProgress == -1) {
|
||||||
|
this.emit("progress", -1);
|
||||||
|
} else {
|
||||||
|
this.emit("progress", install.progress / install.maxProgress);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInstallEnded: function ({addon}) {
|
||||||
|
addon.userDisabled = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDownloadCancelled: function (install) {
|
||||||
|
this.installFailureHandler(install, "Download cancelled");
|
||||||
|
},
|
||||||
|
onDownloadFailed: function (install) {
|
||||||
|
this.installFailureHandler(install, "Download failed");
|
||||||
|
},
|
||||||
|
onInstallCancelled: function (install) {
|
||||||
|
this.installFailureHandler(install, "Install cancelled");
|
||||||
|
},
|
||||||
|
onInstallFailed: function (install) {
|
||||||
|
this.installFailureHandler(install, "Install failed");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function SimulatorAddon(stability, version) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
this.stability = stability;
|
||||||
|
this.version = version;
|
||||||
|
// This addon uses the string "linux" for "linux32"
|
||||||
|
let fixedOS = OS == "linux32" ? "linux" : OS;
|
||||||
|
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, fixedOS)
|
||||||
|
.replace(/#VERSION#/g, version)
|
||||||
|
.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
|
||||||
|
this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
|
||||||
|
this.updateInstallStatus();
|
||||||
|
}
|
||||||
|
SimulatorAddon.prototype = Object.create(Addon.prototype);
|
||||||
|
|
||||||
|
function ADBAddon() {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
// This addon uses the string "linux" for "linux32"
|
||||||
|
let fixedOS = OS == "linux32" ? "linux" : OS;
|
||||||
|
this.xpiLink = ADB_LINK.replace(/#OS#/g, fixedOS);
|
||||||
|
this.addonID = ADB_ADDON_ID;
|
||||||
|
this.updateInstallStatus();
|
||||||
|
}
|
||||||
|
ADBAddon.prototype = Object.create(Addon.prototype);
|
||||||
|
|
||||||
|
function AdaptersAddon() {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
this.xpiLink = ADAPTERS_LINK.replace(/#OS#/g, OS);
|
||||||
|
this.addonID = ADAPTERS_ADDON_ID;
|
||||||
|
this.updateInstallStatus();
|
||||||
|
}
|
||||||
|
AdaptersAddon.prototype = Object.create(Addon.prototype);
|
||||||
850
devtools/client/webide/modules/app-manager.js
Normal file
850
devtools/client/webide/modules/app-manager.js
Normal file
|
|
@ -0,0 +1,850 @@
|
||||||
|
/* 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 promise = require("promise");
|
||||||
|
const {TargetFactory} = require("devtools/client/framework/target");
|
||||||
|
const Services = require("Services");
|
||||||
|
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
|
||||||
|
const {AppProjects} = require("devtools/client/webide/modules/app-projects");
|
||||||
|
const TabStore = require("devtools/client/webide/modules/tab-store");
|
||||||
|
const {AppValidator} = require("devtools/client/webide/modules/app-validator");
|
||||||
|
const {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
|
||||||
|
const {AppActorFront} = require("devtools/shared/apps/app-actor-front");
|
||||||
|
const {getDeviceFront} = require("devtools/shared/fronts/device");
|
||||||
|
const {getPreferenceFront} = require("devtools/shared/fronts/preference");
|
||||||
|
const {getSettingsFront} = require("devtools/shared/fronts/settings");
|
||||||
|
const {Task} = require("devtools/shared/task");
|
||||||
|
const {RuntimeScanners, RuntimeTypes} = require("devtools/client/webide/modules/runtimes");
|
||||||
|
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||||
|
const Telemetry = require("devtools/client/shared/telemetry");
|
||||||
|
const {ProjectBuilding} = require("./build");
|
||||||
|
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
var AppManager = exports.AppManager = {
|
||||||
|
|
||||||
|
DEFAULT_PROJECT_ICON: "chrome://webide/skin/default-app-icon.png",
|
||||||
|
DEFAULT_PROJECT_NAME: "--",
|
||||||
|
|
||||||
|
_initialized: false,
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
if (this._initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._initialized = true;
|
||||||
|
|
||||||
|
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
|
||||||
|
this.connection = ConnectionManager.createConnection("localhost", port);
|
||||||
|
this.onConnectionChanged = this.onConnectionChanged.bind(this);
|
||||||
|
this.connection.on(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
|
||||||
|
|
||||||
|
this.tabStore = new TabStore(this.connection);
|
||||||
|
this.onTabList = this.onTabList.bind(this);
|
||||||
|
this.onTabNavigate = this.onTabNavigate.bind(this);
|
||||||
|
this.onTabClosed = this.onTabClosed.bind(this);
|
||||||
|
this.tabStore.on("tab-list", this.onTabList);
|
||||||
|
this.tabStore.on("navigate", this.onTabNavigate);
|
||||||
|
this.tabStore.on("closed", this.onTabClosed);
|
||||||
|
|
||||||
|
this._clearRuntimeList();
|
||||||
|
this._rebuildRuntimeList = this._rebuildRuntimeList.bind(this);
|
||||||
|
RuntimeScanners.on("runtime-list-updated", this._rebuildRuntimeList);
|
||||||
|
RuntimeScanners.enable();
|
||||||
|
this._rebuildRuntimeList();
|
||||||
|
|
||||||
|
this.onInstallProgress = this.onInstallProgress.bind(this);
|
||||||
|
|
||||||
|
this._telemetry = new Telemetry();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
if (!this._initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._initialized = false;
|
||||||
|
|
||||||
|
this.selectedProject = null;
|
||||||
|
this.selectedRuntime = null;
|
||||||
|
RuntimeScanners.off("runtime-list-updated", this._rebuildRuntimeList);
|
||||||
|
RuntimeScanners.disable();
|
||||||
|
this.runtimeList = null;
|
||||||
|
this.tabStore.off("tab-list", this.onTabList);
|
||||||
|
this.tabStore.off("navigate", this.onTabNavigate);
|
||||||
|
this.tabStore.off("closed", this.onTabClosed);
|
||||||
|
this.tabStore.destroy();
|
||||||
|
this.tabStore = null;
|
||||||
|
this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
|
||||||
|
this._listTabsResponse = null;
|
||||||
|
this.connection.disconnect();
|
||||||
|
this.connection = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module emits various events when state changes occur. The basic event
|
||||||
|
* naming scheme is that event "X" means "X has changed" or "X is available".
|
||||||
|
* Some names are more detailed to clarify their precise meaning.
|
||||||
|
*
|
||||||
|
* The events this module may emit include:
|
||||||
|
* before-project:
|
||||||
|
* The selected project is about to change. The event includes a special
|
||||||
|
* |cancel| callback that will abort the project change if desired.
|
||||||
|
* connection:
|
||||||
|
* The connection status has changed (connected, disconnected, etc.)
|
||||||
|
* install-progress:
|
||||||
|
* A project being installed to a runtime has made further progress. This
|
||||||
|
* event contains additional details about exactly how far the process is
|
||||||
|
* when such information is available.
|
||||||
|
* project:
|
||||||
|
* The selected project has changed.
|
||||||
|
* project-started:
|
||||||
|
* The selected project started running on the connected runtime.
|
||||||
|
* project-stopped:
|
||||||
|
* The selected project stopped running on the connected runtime.
|
||||||
|
* project-removed:
|
||||||
|
* The selected project was removed from the project list.
|
||||||
|
* project-validated:
|
||||||
|
* The selected project just completed validation. As part of validation,
|
||||||
|
* many pieces of metadata about the project are refreshed, including its
|
||||||
|
* name, manifest details, etc.
|
||||||
|
* runtime:
|
||||||
|
* The selected runtime has changed.
|
||||||
|
* runtime-apps-icons:
|
||||||
|
* The list of URLs for the runtime app icons are available.
|
||||||
|
* runtime-global-actors:
|
||||||
|
* The list of global actors for the entire runtime (but not actors for a
|
||||||
|
* specific tab or app) are now available, so we can test for features
|
||||||
|
* like preferences and settings.
|
||||||
|
* runtime-details:
|
||||||
|
* The selected runtime's details have changed, such as its user-visible
|
||||||
|
* name.
|
||||||
|
* runtime-list:
|
||||||
|
* The list of available runtimes has changed, or any of the user-visible
|
||||||
|
* details (like names) for the non-selected runtimes has changed.
|
||||||
|
* runtime-telemetry:
|
||||||
|
* Detailed runtime telemetry has been recorded. Used by tests.
|
||||||
|
* runtime-targets:
|
||||||
|
* The list of remote runtime targets available from the currently
|
||||||
|
* connected runtime (such as tabs or apps) has changed, or any of the
|
||||||
|
* user-visible details (like names) for the non-selected runtime targets
|
||||||
|
* has changed. This event includes |type| in the details, to distinguish
|
||||||
|
* "apps" and "tabs".
|
||||||
|
*/
|
||||||
|
update: function (what, details) {
|
||||||
|
// Anything we want to forward to the UI
|
||||||
|
this.emit("app-manager-update", what, details);
|
||||||
|
},
|
||||||
|
|
||||||
|
reportError: function (l10nProperty, ...l10nArgs) {
|
||||||
|
let win = Services.wm.getMostRecentWindow("devtools:webide");
|
||||||
|
if (win) {
|
||||||
|
win.UI.reportError(l10nProperty, ...l10nArgs);
|
||||||
|
} else {
|
||||||
|
let text;
|
||||||
|
if (l10nArgs.length > 0) {
|
||||||
|
text = Strings.formatStringFromName(l10nProperty, l10nArgs, l10nArgs.length);
|
||||||
|
} else {
|
||||||
|
text = Strings.GetStringFromName(l10nProperty);
|
||||||
|
}
|
||||||
|
console.error(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onConnectionChanged: function () {
|
||||||
|
console.log("Connection status changed: " + this.connection.status);
|
||||||
|
|
||||||
|
if (this.connection.status == Connection.Status.DISCONNECTED) {
|
||||||
|
this.selectedRuntime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.connected) {
|
||||||
|
if (this._appsFront) {
|
||||||
|
this._appsFront.off("install-progress", this.onInstallProgress);
|
||||||
|
this._appsFront.unwatchApps();
|
||||||
|
this._appsFront = null;
|
||||||
|
}
|
||||||
|
this._listTabsResponse = null;
|
||||||
|
} else {
|
||||||
|
this.connection.client.listTabs((response) => {
|
||||||
|
if (response.webappsActor) {
|
||||||
|
let front = new AppActorFront(this.connection.client,
|
||||||
|
response);
|
||||||
|
front.on("install-progress", this.onInstallProgress);
|
||||||
|
front.watchApps(() => this.checkIfProjectIsRunning())
|
||||||
|
.then(() => {
|
||||||
|
// This can't be done earlier as many operations
|
||||||
|
// in the apps actor require watchApps to be called
|
||||||
|
// first.
|
||||||
|
this._appsFront = front;
|
||||||
|
this._listTabsResponse = response;
|
||||||
|
this._recordRuntimeInfo();
|
||||||
|
this.update("runtime-global-actors");
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.checkIfProjectIsRunning();
|
||||||
|
this.update("runtime-targets", { type: "apps" });
|
||||||
|
front.fetchIcons().then(() => this.update("runtime-apps-icons"));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._listTabsResponse = response;
|
||||||
|
this._recordRuntimeInfo();
|
||||||
|
this.update("runtime-global-actors");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update("connection");
|
||||||
|
},
|
||||||
|
|
||||||
|
get connected() {
|
||||||
|
return this.connection &&
|
||||||
|
this.connection.status == Connection.Status.CONNECTED;
|
||||||
|
},
|
||||||
|
|
||||||
|
get apps() {
|
||||||
|
if (this._appsFront) {
|
||||||
|
return this._appsFront.apps;
|
||||||
|
} else {
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInstallProgress: function (event, details) {
|
||||||
|
this.update("install-progress", details);
|
||||||
|
},
|
||||||
|
|
||||||
|
isProjectRunning: function () {
|
||||||
|
if (this.selectedProject.type == "mainProcess" ||
|
||||||
|
this.selectedProject.type == "tab") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = this._getProjectFront(this.selectedProject);
|
||||||
|
return app && app.running;
|
||||||
|
},
|
||||||
|
|
||||||
|
checkIfProjectIsRunning: function () {
|
||||||
|
if (this.selectedProject) {
|
||||||
|
if (this.isProjectRunning()) {
|
||||||
|
this.update("project-started");
|
||||||
|
} else {
|
||||||
|
this.update("project-stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
listTabs: function () {
|
||||||
|
return this.tabStore.listTabs();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTabList: function () {
|
||||||
|
this.update("runtime-targets", { type: "tabs" });
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Merge this into TabProject as part of project-agnostic work
|
||||||
|
onTabNavigate: function () {
|
||||||
|
this.update("runtime-targets", { type: "tabs" });
|
||||||
|
if (this.selectedProject.type !== "tab") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tab = this.selectedProject.app = this.tabStore.selectedTab;
|
||||||
|
let uri = NetUtil.newURI(tab.url);
|
||||||
|
// Wanted to use nsIFaviconService here, but it only works for visited
|
||||||
|
// tabs, so that's no help for any remote tabs. Maybe some favicon wizard
|
||||||
|
// knows how to get high-res favicons easily, or we could offer actor
|
||||||
|
// support for this (bug 1061654).
|
||||||
|
tab.favicon = uri.prePath + "/favicon.ico";
|
||||||
|
tab.name = tab.title || Strings.GetStringFromName("project_tab_loading");
|
||||||
|
if (uri.scheme.startsWith("http")) {
|
||||||
|
tab.name = uri.host + ": " + tab.name;
|
||||||
|
}
|
||||||
|
this.selectedProject.location = tab.url;
|
||||||
|
this.selectedProject.name = tab.name;
|
||||||
|
this.selectedProject.icon = tab.favicon;
|
||||||
|
this.update("project-validated");
|
||||||
|
},
|
||||||
|
|
||||||
|
onTabClosed: function () {
|
||||||
|
if (this.selectedProject.type !== "tab") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedProject = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
reloadTab: function () {
|
||||||
|
if (this.selectedProject && this.selectedProject.type != "tab") {
|
||||||
|
return promise.reject("tried to reload non-tab project");
|
||||||
|
}
|
||||||
|
return this.getTarget().then(target => {
|
||||||
|
target.activeTab.reload();
|
||||||
|
}, console.error.bind(console));
|
||||||
|
},
|
||||||
|
|
||||||
|
getTarget: function () {
|
||||||
|
if (this.selectedProject.type == "mainProcess") {
|
||||||
|
// Fx >=39 exposes a ChromeActor to debug the main process
|
||||||
|
if (this.connection.client.mainRoot.traits.allowChromeProcess) {
|
||||||
|
return this.connection.client.getProcess()
|
||||||
|
.then(aResponse => {
|
||||||
|
return TargetFactory.forRemoteTab({
|
||||||
|
form: aResponse.form,
|
||||||
|
client: this.connection.client,
|
||||||
|
chrome: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fx <39 exposes tab actors on the root actor
|
||||||
|
return TargetFactory.forRemoteTab({
|
||||||
|
form: this._listTabsResponse,
|
||||||
|
client: this.connection.client,
|
||||||
|
chrome: true,
|
||||||
|
isTabActor: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedProject.type == "tab") {
|
||||||
|
return this.tabStore.getTargetForTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = this._getProjectFront(this.selectedProject);
|
||||||
|
if (!app) {
|
||||||
|
return promise.reject("Can't find app front for selected project");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.spawn(function* () {
|
||||||
|
// Once we asked the app to launch, the app isn't necessary completely loaded.
|
||||||
|
// launch request only ask the app to launch and immediatly returns.
|
||||||
|
// We have to keep trying to get app tab actors required to create its target.
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
try {
|
||||||
|
return yield app.getTarget();
|
||||||
|
} catch (e) {}
|
||||||
|
let deferred = promise.defer();
|
||||||
|
setTimeout(deferred.resolve, 500);
|
||||||
|
yield deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppManager.reportError("error_cantConnectToApp", app.manifest.manifestURL);
|
||||||
|
throw new Error("can't connect to app");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getProjectManifestURL: function (project) {
|
||||||
|
let manifest = null;
|
||||||
|
if (project.type == "runtimeApp") {
|
||||||
|
manifest = project.app.manifestURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.type == "hosted") {
|
||||||
|
manifest = project.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.type == "packaged" && project.packagedAppOrigin) {
|
||||||
|
manifest = "app://" + project.packagedAppOrigin + "/manifest.webapp";
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getProjectFront: function (project) {
|
||||||
|
let manifest = this.getProjectManifestURL(project);
|
||||||
|
if (manifest && this._appsFront) {
|
||||||
|
return this._appsFront.apps.get(manifest);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_selectedProject: null,
|
||||||
|
set selectedProject(project) {
|
||||||
|
// A regular comparison doesn't work as we recreate a new object every time
|
||||||
|
let prev = this._selectedProject;
|
||||||
|
if (!prev && !project) {
|
||||||
|
return;
|
||||||
|
} else if (prev && project && prev.type === project.type) {
|
||||||
|
let type = project.type;
|
||||||
|
if (type === "runtimeApp") {
|
||||||
|
if (prev.app.manifestURL === project.app.manifestURL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (type === "tab") {
|
||||||
|
if (prev.app.actor === project.app.actor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (type === "packaged" || type === "hosted") {
|
||||||
|
if (prev.location === project.location) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (type === "mainProcess") {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error("Unsupported project type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
this.update("before-project", { cancel: () => { cancelled = true; } });
|
||||||
|
if (cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._selectedProject = project;
|
||||||
|
|
||||||
|
// Clear out tab store's selected state, if any
|
||||||
|
this.tabStore.selectedTab = null;
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
if (project.type == "packaged" ||
|
||||||
|
project.type == "hosted") {
|
||||||
|
this.validateAndUpdateProject(project);
|
||||||
|
}
|
||||||
|
if (project.type == "tab") {
|
||||||
|
this.tabStore.selectedTab = project.app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update("project");
|
||||||
|
this.checkIfProjectIsRunning();
|
||||||
|
},
|
||||||
|
get selectedProject() {
|
||||||
|
return this._selectedProject;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeSelectedProject: Task.async(function* () {
|
||||||
|
let location = this.selectedProject.location;
|
||||||
|
AppManager.selectedProject = null;
|
||||||
|
// If the user cancels the removeProject operation, don't remove the project
|
||||||
|
if (AppManager.selectedProject != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield AppProjects.remove(location);
|
||||||
|
AppManager.update("project-removed");
|
||||||
|
}),
|
||||||
|
|
||||||
|
packageProject: Task.async(function* (project) {
|
||||||
|
if (!project) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (project.type == "packaged" ||
|
||||||
|
project.type == "hosted") {
|
||||||
|
yield ProjectBuilding.build({
|
||||||
|
project: project,
|
||||||
|
logger: this.update.bind(this, "pre-package")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
_selectedRuntime: null,
|
||||||
|
set selectedRuntime(value) {
|
||||||
|
this._selectedRuntime = value;
|
||||||
|
if (!value && this.selectedProject &&
|
||||||
|
(this.selectedProject.type == "mainProcess" ||
|
||||||
|
this.selectedProject.type == "runtimeApp" ||
|
||||||
|
this.selectedProject.type == "tab")) {
|
||||||
|
this.selectedProject = null;
|
||||||
|
}
|
||||||
|
this.update("runtime");
|
||||||
|
},
|
||||||
|
|
||||||
|
get selectedRuntime() {
|
||||||
|
return this._selectedRuntime;
|
||||||
|
},
|
||||||
|
|
||||||
|
connectToRuntime: function (runtime) {
|
||||||
|
|
||||||
|
if (this.connected && this.selectedRuntime === runtime) {
|
||||||
|
// Already connected
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
this.disconnectRuntime().then(() => {
|
||||||
|
this.selectedRuntime = runtime;
|
||||||
|
|
||||||
|
let onConnectedOrDisconnected = () => {
|
||||||
|
this.connection.off(Connection.Events.CONNECTED, onConnectedOrDisconnected);
|
||||||
|
this.connection.off(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
|
||||||
|
if (this.connected) {
|
||||||
|
deferred.resolve();
|
||||||
|
} else {
|
||||||
|
deferred.reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
|
||||||
|
this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
|
||||||
|
try {
|
||||||
|
// Reset the connection's state to defaults
|
||||||
|
this.connection.resetOptions();
|
||||||
|
// Only watch for errors here. Final resolution occurs above, once
|
||||||
|
// we've reached the CONNECTED state.
|
||||||
|
this.selectedRuntime.connect(this.connection)
|
||||||
|
.then(null, e => deferred.reject(e));
|
||||||
|
} catch (e) {
|
||||||
|
deferred.reject(e);
|
||||||
|
}
|
||||||
|
}, deferred.reject);
|
||||||
|
|
||||||
|
// Record connection result in telemetry
|
||||||
|
let logResult = result => {
|
||||||
|
this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result);
|
||||||
|
if (runtime.type) {
|
||||||
|
this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type +
|
||||||
|
"_CONNECTION_RESULT", result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deferred.promise.then(() => logResult(true), () => logResult(false));
|
||||||
|
|
||||||
|
// If successful, record connection time in telemetry
|
||||||
|
deferred.promise.then(() => {
|
||||||
|
const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS";
|
||||||
|
this._telemetry.startTimer(timerId);
|
||||||
|
this.connection.once(Connection.Events.STATUS_CHANGED, () => {
|
||||||
|
this._telemetry.stopTimer(timerId);
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// Empty rejection handler to silence uncaught rejection warnings
|
||||||
|
// |connectToRuntime| caller should listen for rejections.
|
||||||
|
// Bug 1121100 may find a better way to silence these.
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
_recordRuntimeInfo: Task.async(function* () {
|
||||||
|
if (!this.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let runtime = this.selectedRuntime;
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE",
|
||||||
|
runtime.type || "UNKNOWN", true);
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID",
|
||||||
|
runtime.id || "unknown", true);
|
||||||
|
if (!this.deviceFront) {
|
||||||
|
this.update("runtime-telemetry");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let d = yield this.deviceFront.getDescription();
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR",
|
||||||
|
d.processor, true);
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS",
|
||||||
|
d.os, true);
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION",
|
||||||
|
d.platformversion, true);
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE",
|
||||||
|
d.apptype, true);
|
||||||
|
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION",
|
||||||
|
d.version, true);
|
||||||
|
this.update("runtime-telemetry");
|
||||||
|
}),
|
||||||
|
|
||||||
|
isMainProcessDebuggable: function () {
|
||||||
|
// Fx <39 exposes chrome tab actors on RootActor
|
||||||
|
// Fx >=39 exposes a dedicated actor via getProcess request
|
||||||
|
return this.connection.client &&
|
||||||
|
this.connection.client.mainRoot &&
|
||||||
|
this.connection.client.mainRoot.traits.allowChromeProcess ||
|
||||||
|
(this._listTabsResponse &&
|
||||||
|
this._listTabsResponse.consoleActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
get deviceFront() {
|
||||||
|
if (!this._listTabsResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getDeviceFront(this.connection.client, this._listTabsResponse);
|
||||||
|
},
|
||||||
|
|
||||||
|
get preferenceFront() {
|
||||||
|
if (!this._listTabsResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPreferenceFront(this.connection.client, this._listTabsResponse);
|
||||||
|
},
|
||||||
|
|
||||||
|
get settingsFront() {
|
||||||
|
if (!this._listTabsResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getSettingsFront(this.connection.client, this._listTabsResponse);
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnectRuntime: function () {
|
||||||
|
if (!this.connected) {
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
let deferred = promise.defer();
|
||||||
|
this.connection.once(Connection.Events.DISCONNECTED, () => deferred.resolve());
|
||||||
|
this.connection.disconnect();
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
launchRuntimeApp: function () {
|
||||||
|
if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
|
||||||
|
return promise.reject("attempting to launch a non-runtime app");
|
||||||
|
}
|
||||||
|
let app = this._getProjectFront(this.selectedProject);
|
||||||
|
return app.launch();
|
||||||
|
},
|
||||||
|
|
||||||
|
launchOrReloadRuntimeApp: function () {
|
||||||
|
if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
|
||||||
|
return promise.reject("attempting to launch / reload a non-runtime app");
|
||||||
|
}
|
||||||
|
let app = this._getProjectFront(this.selectedProject);
|
||||||
|
if (!app.running) {
|
||||||
|
return app.launch();
|
||||||
|
} else {
|
||||||
|
return app.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
runtimeCanHandleApps: function () {
|
||||||
|
return !!this._appsFront;
|
||||||
|
},
|
||||||
|
|
||||||
|
installAndRunProject: function () {
|
||||||
|
let project = this.selectedProject;
|
||||||
|
|
||||||
|
if (!project || (project.type != "packaged" && project.type != "hosted")) {
|
||||||
|
console.error("Can't install project. Unknown type of project.");
|
||||||
|
return promise.reject("Can't install");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._listTabsResponse) {
|
||||||
|
this.reportError("error_cantInstallNotFullyConnected");
|
||||||
|
return promise.reject("Can't install");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._appsFront) {
|
||||||
|
console.error("Runtime doesn't have a webappsActor");
|
||||||
|
return promise.reject("Can't install");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.spawn(function* () {
|
||||||
|
let self = AppManager;
|
||||||
|
|
||||||
|
// Package and validate project
|
||||||
|
yield self.packageProject(project);
|
||||||
|
yield self.validateAndUpdateProject(project);
|
||||||
|
|
||||||
|
if (project.errorsCount > 0) {
|
||||||
|
self.reportError("error_cantInstallValidationErrors");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let installPromise;
|
||||||
|
|
||||||
|
if (project.type != "packaged" && project.type != "hosted") {
|
||||||
|
return promise.reject("Don't know how to install project");
|
||||||
|
}
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (project.type == "packaged") {
|
||||||
|
let packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||||
|
console.log("Installing app from " + packageDir);
|
||||||
|
|
||||||
|
response = yield self._appsFront.installPackaged(packageDir,
|
||||||
|
project.packagedAppOrigin);
|
||||||
|
|
||||||
|
// If the packaged app specified a custom origin override,
|
||||||
|
// we need to update the local project origin
|
||||||
|
project.packagedAppOrigin = response.appId;
|
||||||
|
// And ensure the indexed db on disk is also updated
|
||||||
|
AppProjects.update(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.type == "hosted") {
|
||||||
|
let manifestURLObject = Services.io.newURI(project.location, null, null);
|
||||||
|
let origin = Services.io.newURI(manifestURLObject.prePath, null, null);
|
||||||
|
let appId = origin.host;
|
||||||
|
let metadata = {
|
||||||
|
origin: origin.spec,
|
||||||
|
manifestURL: project.location
|
||||||
|
};
|
||||||
|
response = yield self._appsFront.installHosted(appId,
|
||||||
|
metadata,
|
||||||
|
project.manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addons don't have any document to load (yet?)
|
||||||
|
// So that there is no need to run them, installing is enough
|
||||||
|
if (project.manifest.manifest_version || project.manifest.role === "addon") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {app} = response;
|
||||||
|
if (!app.running) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
self.on("app-manager-update", function onUpdate(event, what) {
|
||||||
|
if (what == "project-started") {
|
||||||
|
self.off("app-manager-update", onUpdate);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
yield app.launch();
|
||||||
|
yield deferred.promise;
|
||||||
|
} else {
|
||||||
|
yield app.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopRunningApp: function () {
|
||||||
|
let app = this._getProjectFront(this.selectedProject);
|
||||||
|
return app.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
/* PROJECT VALIDATION */
|
||||||
|
|
||||||
|
validateAndUpdateProject: function (project) {
|
||||||
|
if (!project) {
|
||||||
|
return promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.spawn(function* () {
|
||||||
|
|
||||||
|
let packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||||
|
let validation = new AppValidator({
|
||||||
|
type: project.type,
|
||||||
|
// Build process may place the manifest in a non-root directory
|
||||||
|
location: packageDir
|
||||||
|
});
|
||||||
|
|
||||||
|
yield validation.validate();
|
||||||
|
|
||||||
|
if (validation.manifest) {
|
||||||
|
let manifest = validation.manifest;
|
||||||
|
let iconPath;
|
||||||
|
if (manifest.icons) {
|
||||||
|
let size = Object.keys(manifest.icons).sort((a, b) => b - a)[0];
|
||||||
|
if (size) {
|
||||||
|
iconPath = manifest.icons[size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!iconPath) {
|
||||||
|
project.icon = AppManager.DEFAULT_PROJECT_ICON;
|
||||||
|
} else {
|
||||||
|
if (project.type == "hosted") {
|
||||||
|
let manifestURL = Services.io.newURI(project.location, null, null);
|
||||||
|
let origin = Services.io.newURI(manifestURL.prePath, null, null);
|
||||||
|
project.icon = Services.io.newURI(iconPath, null, origin).spec;
|
||||||
|
} else if (project.type == "packaged") {
|
||||||
|
let projectFolder = FileUtils.File(packageDir);
|
||||||
|
let folderURI = Services.io.newFileURI(projectFolder).spec;
|
||||||
|
project.icon = folderURI + iconPath.replace(/^\/|\\/, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.manifest = validation.manifest;
|
||||||
|
|
||||||
|
if ("name" in project.manifest) {
|
||||||
|
project.name = project.manifest.name;
|
||||||
|
} else {
|
||||||
|
project.name = AppManager.DEFAULT_PROJECT_NAME;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
project.manifest = null;
|
||||||
|
project.icon = AppManager.DEFAULT_PROJECT_ICON;
|
||||||
|
project.name = AppManager.DEFAULT_PROJECT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
project.validationStatus = "valid";
|
||||||
|
|
||||||
|
if (validation.warnings.length > 0) {
|
||||||
|
project.warningsCount = validation.warnings.length;
|
||||||
|
project.warnings = validation.warnings;
|
||||||
|
project.validationStatus = "warning";
|
||||||
|
} else {
|
||||||
|
project.warnings = "";
|
||||||
|
project.warningsCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validation.errors.length > 0) {
|
||||||
|
project.errorsCount = validation.errors.length;
|
||||||
|
project.errors = validation.errors;
|
||||||
|
project.validationStatus = "error";
|
||||||
|
} else {
|
||||||
|
project.errors = "";
|
||||||
|
project.errorsCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.warningsCount && project.errorsCount) {
|
||||||
|
project.validationStatus = "error warning";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.type === "hosted" && project.location !== validation.manifestURL) {
|
||||||
|
yield AppProjects.updateLocation(project, validation.manifestURL);
|
||||||
|
} else if (AppProjects.get(project.location)) {
|
||||||
|
yield AppProjects.update(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppManager.selectedProject === project) {
|
||||||
|
AppManager.update("project-validated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* RUNTIME LIST */
|
||||||
|
|
||||||
|
_clearRuntimeList: function () {
|
||||||
|
this.runtimeList = {
|
||||||
|
usb: [],
|
||||||
|
wifi: [],
|
||||||
|
simulator: [],
|
||||||
|
other: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_rebuildRuntimeList: function () {
|
||||||
|
let runtimes = RuntimeScanners.listRuntimes();
|
||||||
|
this._clearRuntimeList();
|
||||||
|
|
||||||
|
// Reorganize runtimes by type
|
||||||
|
for (let runtime of runtimes) {
|
||||||
|
switch (runtime.type) {
|
||||||
|
case RuntimeTypes.USB:
|
||||||
|
this.runtimeList.usb.push(runtime);
|
||||||
|
break;
|
||||||
|
case RuntimeTypes.WIFI:
|
||||||
|
this.runtimeList.wifi.push(runtime);
|
||||||
|
break;
|
||||||
|
case RuntimeTypes.SIMULATOR:
|
||||||
|
this.runtimeList.simulator.push(runtime);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.runtimeList.other.push(runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update("runtime-details");
|
||||||
|
this.update("runtime-list");
|
||||||
|
},
|
||||||
|
|
||||||
|
/* MANIFEST UTILS */
|
||||||
|
|
||||||
|
writeManifest: function (project) {
|
||||||
|
if (project.type != "packaged") {
|
||||||
|
return promise.reject("Not a packaged app");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!project.manifest) {
|
||||||
|
project.manifest = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let folder = project.location;
|
||||||
|
let manifestPath = OS.Path.join(folder, "manifest.webapp");
|
||||||
|
let text = JSON.stringify(project.manifest, null, 2);
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
let array = encoder.encode(text);
|
||||||
|
return OS.File.writeAtomic(manifestPath, array, {tmpPath: manifestPath + ".tmp"});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(AppManager);
|
||||||
235
devtools/client/webide/modules/app-projects.js
Normal file
235
devtools/client/webide/modules/app-projects.js
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
/* 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 {Cc, Ci, Cu, Cr} = require("chrome");
|
||||||
|
const promise = require("promise");
|
||||||
|
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||||
|
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IndexedDB wrapper that just save project objects
|
||||||
|
*
|
||||||
|
* The only constraint is that project objects have to have
|
||||||
|
* a unique `location` object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const IDB = {
|
||||||
|
_db: null,
|
||||||
|
databaseName: "AppProjects",
|
||||||
|
|
||||||
|
open: function () {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
let request = indexedDB.open(IDB.databaseName, 5);
|
||||||
|
request.onerror = function (event) {
|
||||||
|
deferred.reject("Unable to open AppProjects indexedDB: " +
|
||||||
|
this.error.name + " - " + this.error.message);
|
||||||
|
};
|
||||||
|
request.onupgradeneeded = function (event) {
|
||||||
|
let db = event.target.result;
|
||||||
|
db.createObjectStore("projects", { keyPath: "location" });
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onsuccess = function () {
|
||||||
|
let db = IDB._db = request.result;
|
||||||
|
let objectStore = db.transaction("projects").objectStore("projects");
|
||||||
|
let projects = [];
|
||||||
|
let toRemove = [];
|
||||||
|
objectStore.openCursor().onsuccess = function (event) {
|
||||||
|
let cursor = event.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
if (cursor.value.location) {
|
||||||
|
|
||||||
|
// We need to make sure this object has a `.location` property.
|
||||||
|
// The UI depends on this property.
|
||||||
|
// This should not be needed as we make sure to register valid
|
||||||
|
// projects, but in the past (before bug 924568), we might have
|
||||||
|
// registered invalid objects.
|
||||||
|
|
||||||
|
|
||||||
|
// We also want to make sure the location is valid.
|
||||||
|
// If the location doesn't exist, we remove the project.
|
||||||
|
|
||||||
|
try {
|
||||||
|
let file = FileUtils.File(cursor.value.location);
|
||||||
|
if (file.exists()) {
|
||||||
|
projects.push(cursor.value);
|
||||||
|
} else {
|
||||||
|
toRemove.push(cursor.value.location);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
|
||||||
|
// A URL
|
||||||
|
projects.push(cursor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
let removePromises = [];
|
||||||
|
for (let location of toRemove) {
|
||||||
|
removePromises.push(IDB.remove(location));
|
||||||
|
}
|
||||||
|
promise.all(removePromises).then(() => {
|
||||||
|
deferred.resolve(projects);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
add: function (project) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
if (!project.location) {
|
||||||
|
// We need to make sure this object has a `.location` property.
|
||||||
|
deferred.reject("Missing location property on project object.");
|
||||||
|
} else {
|
||||||
|
let transaction = IDB._db.transaction(["projects"], "readwrite");
|
||||||
|
let objectStore = transaction.objectStore("projects");
|
||||||
|
let request = objectStore.add(project);
|
||||||
|
request.onerror = function (event) {
|
||||||
|
deferred.reject("Unable to add project to the AppProjects indexedDB: " +
|
||||||
|
this.error.name + " - " + this.error.message);
|
||||||
|
};
|
||||||
|
request.onsuccess = function () {
|
||||||
|
deferred.resolve();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function (project) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
var transaction = IDB._db.transaction(["projects"], "readwrite");
|
||||||
|
var objectStore = transaction.objectStore("projects");
|
||||||
|
var request = objectStore.put(project);
|
||||||
|
request.onerror = function (event) {
|
||||||
|
deferred.reject("Unable to update project to the AppProjects indexedDB: " +
|
||||||
|
this.error.name + " - " + this.error.message);
|
||||||
|
};
|
||||||
|
request.onsuccess = function () {
|
||||||
|
deferred.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function (location) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
let request = IDB._db.transaction(["projects"], "readwrite")
|
||||||
|
.objectStore("projects")
|
||||||
|
.delete(location);
|
||||||
|
request.onsuccess = function (event) {
|
||||||
|
deferred.resolve();
|
||||||
|
};
|
||||||
|
request.onerror = function () {
|
||||||
|
deferred.reject("Unable to delete project to the AppProjects indexedDB: " +
|
||||||
|
this.error.name + " - " + this.error.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var loadDeferred = promise.defer();
|
||||||
|
|
||||||
|
loadDeferred.resolve(IDB.open().then(function (projects) {
|
||||||
|
AppProjects.projects = projects;
|
||||||
|
AppProjects.emit("ready", projects);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const AppProjects = {
|
||||||
|
load: function () {
|
||||||
|
return loadDeferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
addPackaged: function (folder) {
|
||||||
|
let file = FileUtils.File(folder.path);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return promise.reject("path doesn't exist");
|
||||||
|
}
|
||||||
|
let existingProject = this.get(folder.path);
|
||||||
|
if (existingProject) {
|
||||||
|
return promise.reject("Already added");
|
||||||
|
}
|
||||||
|
let project = {
|
||||||
|
type: "packaged",
|
||||||
|
location: folder.path,
|
||||||
|
// We need a unique id, that is the app origin,
|
||||||
|
// in order to identify the app when being installed on the device.
|
||||||
|
// The packaged app local path is a valid id, but only on the client.
|
||||||
|
// This origin will be used to generate the true id of an app:
|
||||||
|
// its manifest URL.
|
||||||
|
// If the app ends up specifying an explicit origin in its manifest,
|
||||||
|
// we will override this random UUID on app install.
|
||||||
|
packagedAppOrigin: generateUUID().toString().slice(1, -1)
|
||||||
|
};
|
||||||
|
return IDB.add(project).then(() => {
|
||||||
|
this.projects.push(project);
|
||||||
|
return project;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addHosted: function (manifestURL) {
|
||||||
|
let existingProject = this.get(manifestURL);
|
||||||
|
if (existingProject) {
|
||||||
|
return promise.reject("Already added");
|
||||||
|
}
|
||||||
|
let project = {
|
||||||
|
type: "hosted",
|
||||||
|
location: manifestURL
|
||||||
|
};
|
||||||
|
return IDB.add(project).then(() => {
|
||||||
|
this.projects.push(project);
|
||||||
|
return project;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function (project) {
|
||||||
|
return IDB.update(project);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLocation: function (project, newLocation) {
|
||||||
|
return IDB.remove(project.location)
|
||||||
|
.then(() => {
|
||||||
|
project.location = newLocation;
|
||||||
|
return IDB.add(project);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function (location) {
|
||||||
|
return IDB.remove(location).then(() => {
|
||||||
|
for (let i = 0; i < this.projects.length; i++) {
|
||||||
|
if (this.projects[i].location == location) {
|
||||||
|
this.projects.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to find project in AppProjects store");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function (location) {
|
||||||
|
for (let i = 0; i < this.projects.length; i++) {
|
||||||
|
if (this.projects[i].location == location) {
|
||||||
|
return this.projects[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
projects: []
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(AppProjects);
|
||||||
|
|
||||||
|
exports.AppProjects = AppProjects;
|
||||||
292
devtools/client/webide/modules/app-validator.js
Normal file
292
devtools/client/webide/modules/app-validator.js
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
/* 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 {Ci, Cu, CC} = require("chrome");
|
||||||
|
const promise = require("promise");
|
||||||
|
|
||||||
|
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||||
|
const Services = require("Services");
|
||||||
|
const {Task} = require("devtools/shared/task");
|
||||||
|
var XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
|
||||||
|
var strings = Services.strings.createBundle("chrome://devtools/locale/app-manager.properties");
|
||||||
|
|
||||||
|
function AppValidator({ type, location }) {
|
||||||
|
this.type = type;
|
||||||
|
this.location = location;
|
||||||
|
this.errors = [];
|
||||||
|
this.warnings = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
AppValidator.prototype.error = function (message) {
|
||||||
|
this.errors.push(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype.warning = function (message) {
|
||||||
|
this.warnings.push(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype._getPackagedManifestFile = function () {
|
||||||
|
let manifestFile = FileUtils.File(this.location);
|
||||||
|
if (!manifestFile.exists()) {
|
||||||
|
this.error(strings.GetStringFromName("validator.nonExistingFolder"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!manifestFile.isDirectory()) {
|
||||||
|
this.error(strings.GetStringFromName("validator.expectProjectFolder"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let appManifestFile = manifestFile.clone();
|
||||||
|
appManifestFile.append("manifest.webapp");
|
||||||
|
|
||||||
|
let jsonManifestFile = manifestFile.clone();
|
||||||
|
jsonManifestFile.append("manifest.json");
|
||||||
|
|
||||||
|
let hasAppManifest = appManifestFile.exists() && appManifestFile.isFile();
|
||||||
|
let hasJsonManifest = jsonManifestFile.exists() && jsonManifestFile.isFile();
|
||||||
|
|
||||||
|
if (!hasAppManifest && !hasJsonManifest) {
|
||||||
|
this.error(strings.GetStringFromName("validator.noManifestFile"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAppManifest ? appManifestFile : jsonManifestFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype._getPackagedManifestURL = function () {
|
||||||
|
let manifestFile = this._getPackagedManifestFile();
|
||||||
|
if (!manifestFile) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Services.io.newFileURI(manifestFile).spec;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.checkManifest = function (manifestURL) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
let error;
|
||||||
|
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
req.overrideMimeType("text/plain");
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.open("GET", manifestURL, true);
|
||||||
|
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
|
||||||
|
} catch (e) {
|
||||||
|
error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1);
|
||||||
|
deferred.reject(error);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
req.onload = function () {
|
||||||
|
let manifest = null;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(req.responseText);
|
||||||
|
} catch (e) {
|
||||||
|
error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
deferred.resolve({manifest, manifestURL});
|
||||||
|
};
|
||||||
|
|
||||||
|
req.onerror = function () {
|
||||||
|
error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2);
|
||||||
|
deferred.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.send(null);
|
||||||
|
} catch (e) {
|
||||||
|
error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.findManifestAtOrigin = function (manifestURL) {
|
||||||
|
let fixedManifest = Services.io.newURI(manifestURL, null, null).prePath + "/manifest.webapp";
|
||||||
|
return AppValidator.checkManifest(fixedManifest);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.findManifestPath = function (manifestURL) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
if (manifestURL.endsWith("manifest.webapp")) {
|
||||||
|
deferred.reject();
|
||||||
|
} else {
|
||||||
|
let fixedManifest = manifestURL + "/manifest.webapp";
|
||||||
|
deferred.resolve(AppValidator.checkManifest(fixedManifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.checkAlternateManifest = function (manifestURL) {
|
||||||
|
return Task.spawn(function* () {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = yield AppValidator.findManifestPath(manifestURL);
|
||||||
|
} catch (e) {
|
||||||
|
result = yield AppValidator.findManifestAtOrigin(manifestURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype._fetchManifest = function (manifestURL) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
this.manifestURL = manifestURL;
|
||||||
|
|
||||||
|
AppValidator.checkManifest(manifestURL)
|
||||||
|
.then(({manifest, manifestURL}) => {
|
||||||
|
deferred.resolve(manifest);
|
||||||
|
}, error => {
|
||||||
|
AppValidator.checkAlternateManifest(manifestURL)
|
||||||
|
.then(({manifest, manifestURL}) => {
|
||||||
|
this.manifestURL = manifestURL;
|
||||||
|
deferred.resolve(manifest);
|
||||||
|
}, () => {
|
||||||
|
this.error(error);
|
||||||
|
deferred.resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype._getManifest = function () {
|
||||||
|
let manifestURL;
|
||||||
|
if (this.type == "packaged") {
|
||||||
|
manifestURL = this._getPackagedManifestURL();
|
||||||
|
if (!manifestURL)
|
||||||
|
return promise.resolve(null);
|
||||||
|
} else if (this.type == "hosted") {
|
||||||
|
manifestURL = this.location;
|
||||||
|
try {
|
||||||
|
Services.io.newURI(manifestURL, null, null);
|
||||||
|
} catch (e) {
|
||||||
|
this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message], 2));
|
||||||
|
return promise.resolve(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1));
|
||||||
|
return promise.resolve(null);
|
||||||
|
}
|
||||||
|
return this._fetchManifest(manifestURL);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype.validateManifest = function (manifest) {
|
||||||
|
if (!manifest.name) {
|
||||||
|
this.error(strings.GetStringFromName("validator.missNameManifestProperty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifest.icons || Object.keys(manifest.icons).length === 0) {
|
||||||
|
this.warning(strings.GetStringFromName("validator.missIconsManifestProperty"));
|
||||||
|
} else if (!manifest.icons["128"]) {
|
||||||
|
this.warning(strings.GetStringFromName("validator.missIconMarketplace2"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype._getOriginURL = function () {
|
||||||
|
if (this.type == "packaged") {
|
||||||
|
let manifestURL = Services.io.newURI(this.manifestURL, null, null);
|
||||||
|
return Services.io.newURI(".", null, manifestURL).spec;
|
||||||
|
} else if (this.type == "hosted") {
|
||||||
|
return Services.io.newURI(this.location, null, null).prePath;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype.validateLaunchPath = function (manifest) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
// The launch_path field has to start with a `/`
|
||||||
|
if (manifest.launch_path && manifest.launch_path[0] !== "/") {
|
||||||
|
this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1));
|
||||||
|
deferred.resolve();
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
let origin = this._getOriginURL();
|
||||||
|
let path;
|
||||||
|
if (this.type == "packaged") {
|
||||||
|
path = "." + (manifest.launch_path || "/index.html");
|
||||||
|
} else if (this.type == "hosted") {
|
||||||
|
path = manifest.launch_path || "/";
|
||||||
|
}
|
||||||
|
let indexURL;
|
||||||
|
try {
|
||||||
|
indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec;
|
||||||
|
} catch (e) {
|
||||||
|
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
|
||||||
|
deferred.resolve();
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
req.overrideMimeType("text/plain");
|
||||||
|
try {
|
||||||
|
req.open("HEAD", indexURL, true);
|
||||||
|
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
|
||||||
|
} catch (e) {
|
||||||
|
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
|
||||||
|
deferred.resolve();
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
req.onload = () => {
|
||||||
|
if (req.status >= 400)
|
||||||
|
this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
|
||||||
|
deferred.resolve();
|
||||||
|
};
|
||||||
|
req.onerror = () => {
|
||||||
|
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
|
||||||
|
deferred.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.send(null);
|
||||||
|
} catch (e) {
|
||||||
|
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype.validateType = function (manifest) {
|
||||||
|
let appType = manifest.type || "web";
|
||||||
|
if (["web", "privileged", "certified"].indexOf(appType) === -1) {
|
||||||
|
this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
|
||||||
|
} else if (this.type == "hosted" &&
|
||||||
|
["certified", "privileged"].indexOf(appType) !== -1) {
|
||||||
|
this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// certified app are not fully supported on the simulator
|
||||||
|
if (appType === "certified") {
|
||||||
|
this.warning(strings.GetStringFromName("validator.noCertifiedSupport"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AppValidator.prototype.validate = function () {
|
||||||
|
this.errors = [];
|
||||||
|
this.warnings = [];
|
||||||
|
return this._getManifest().
|
||||||
|
then((manifest) => {
|
||||||
|
if (manifest) {
|
||||||
|
this.manifest = manifest;
|
||||||
|
|
||||||
|
// Skip validations for add-ons
|
||||||
|
if (manifest.role === "addon" || manifest.manifest_version) {
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.validateManifest(manifest);
|
||||||
|
this.validateType(manifest);
|
||||||
|
return this.validateLaunchPath(manifest);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.AppValidator = AppValidator;
|
||||||
199
devtools/client/webide/modules/build.js
Normal file
199
devtools/client/webide/modules/build.js
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
/* 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, Cc, Ci} = require("chrome");
|
||||||
|
|
||||||
|
const promise = require("promise");
|
||||||
|
const { Task } = require("devtools/shared/task");
|
||||||
|
const { TextDecoder, OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
|
||||||
|
const Subprocess = require("sdk/system/child_process/subprocess");
|
||||||
|
|
||||||
|
const ProjectBuilding = exports.ProjectBuilding = {
|
||||||
|
fetchPackageManifest: Task.async(function* (project) {
|
||||||
|
let manifestPath = OS.Path.join(project.location, "package.json");
|
||||||
|
let exists = yield OS.File.exists(manifestPath);
|
||||||
|
if (!exists) {
|
||||||
|
// No explicit manifest, try to generate one if possible
|
||||||
|
return this.generatePackageManifest(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = yield OS.File.read(manifestPath);
|
||||||
|
data = new TextDecoder().decode(data);
|
||||||
|
let manifest;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Error while reading WebIDE manifest at: '" + manifestPath +
|
||||||
|
"', invalid JSON: " + e.message);
|
||||||
|
}
|
||||||
|
return manifest;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For common frameworks in the community, attempt to detect the build
|
||||||
|
* settings if none are defined. This makes it much easier to get started
|
||||||
|
* with WebIDE. Later on, perhaps an add-on could define such things for
|
||||||
|
* different frameworks.
|
||||||
|
*/
|
||||||
|
generatePackageManifest: Task.async(function* (project) {
|
||||||
|
// Cordova
|
||||||
|
let cordovaConfigPath = OS.Path.join(project.location, "config.xml");
|
||||||
|
let exists = yield OS.File.exists(cordovaConfigPath);
|
||||||
|
if (!exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = yield OS.File.read(cordovaConfigPath);
|
||||||
|
data = new TextDecoder().decode(data);
|
||||||
|
if (data.contains("cordova.apache.org")) {
|
||||||
|
return {
|
||||||
|
"webide": {
|
||||||
|
"prepackage": "cordova prepare",
|
||||||
|
"packageDir": "./platforms/firefoxos/www"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
hasPrepackage: Task.async(function* (project) {
|
||||||
|
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||||
|
return manifest && manifest.webide && "prepackage" in manifest.webide;
|
||||||
|
}),
|
||||||
|
|
||||||
|
// If the app depends on some build step, run it before pushing the app
|
||||||
|
build: Task.async(function* ({ project, logger }) {
|
||||||
|
if (!(yield this.hasPrepackage(project))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||||
|
|
||||||
|
logger("start");
|
||||||
|
try {
|
||||||
|
yield this._build(project, manifest, logger);
|
||||||
|
logger("succeed");
|
||||||
|
} catch (e) {
|
||||||
|
logger("failed", e);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
_build: Task.async(function* (project, manifest, logger) {
|
||||||
|
// Look for `webide` property
|
||||||
|
manifest = manifest.webide;
|
||||||
|
|
||||||
|
let command, cwd, args = [], env = [];
|
||||||
|
|
||||||
|
// Copy frequently used env vars
|
||||||
|
let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||||
|
["HOME", "PATH"].forEach(key => {
|
||||||
|
let value = envService.get(key);
|
||||||
|
if (value) {
|
||||||
|
env.push(key + "=" + value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof (manifest.prepackage) === "string") {
|
||||||
|
command = manifest.prepackage.replace(/%project%/g, project.location);
|
||||||
|
} else if (manifest.prepackage.command) {
|
||||||
|
command = manifest.prepackage.command;
|
||||||
|
|
||||||
|
args = manifest.prepackage.args || [];
|
||||||
|
args = args.map(a => a.replace(/%project%/g, project.location));
|
||||||
|
|
||||||
|
env = env.concat(manifest.prepackage.env || []);
|
||||||
|
env = env.map(a => a.replace(/%project%/g, project.location));
|
||||||
|
|
||||||
|
if (manifest.prepackage.cwd) {
|
||||||
|
// Normalize path for Windows support (converts / to \)
|
||||||
|
let path = OS.Path.normalize(manifest.prepackage.cwd);
|
||||||
|
// Note that Path.join also support absolute path and argument.
|
||||||
|
// So that if cwd is absolute, it will return cwd.
|
||||||
|
let rel = OS.Path.join(project.location, path);
|
||||||
|
let exists = yield OS.File.exists(rel);
|
||||||
|
if (exists) {
|
||||||
|
cwd = rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("pre-package manifest is invalid, missing or invalid " +
|
||||||
|
"`prepackage` attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cwd) {
|
||||||
|
cwd = project.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger("Running pre-package hook '" + command + "' " +
|
||||||
|
args.join(" ") +
|
||||||
|
" with ENV=[" + env.join(", ") + "]" +
|
||||||
|
" at " + cwd);
|
||||||
|
|
||||||
|
// Run the command through a shell command in order to support non absolute
|
||||||
|
// paths.
|
||||||
|
// On Windows `ComSpec` env variable is going to refer to cmd.exe,
|
||||||
|
// Otherwise, on Linux and Mac, SHELL env variable should refer to
|
||||||
|
// the user chosen shell program.
|
||||||
|
// (We do not check for OS, as on windows, with cygwin, ComSpec isn't set)
|
||||||
|
let shell = envService.get("ComSpec") || envService.get("SHELL");
|
||||||
|
args.unshift(command);
|
||||||
|
|
||||||
|
// For cmd.exe, we have to pass the `/C` option,
|
||||||
|
// but for unix shells we need -c.
|
||||||
|
// That to interpret next argument as a shell command.
|
||||||
|
if (envService.exists("ComSpec")) {
|
||||||
|
args.unshift("/C");
|
||||||
|
} else {
|
||||||
|
args.unshift("-c");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subprocess changes CWD, we have to save and restore it.
|
||||||
|
let originalCwd = yield OS.File.getCurrentDirectory();
|
||||||
|
try {
|
||||||
|
let defer = promise.defer();
|
||||||
|
Subprocess.call({
|
||||||
|
command: shell,
|
||||||
|
arguments: args,
|
||||||
|
environment: env,
|
||||||
|
workdir: cwd,
|
||||||
|
|
||||||
|
stdout: data =>
|
||||||
|
logger(data),
|
||||||
|
stderr: data =>
|
||||||
|
logger(data),
|
||||||
|
|
||||||
|
done: result => {
|
||||||
|
logger("Terminated with error code: " + result.exitCode);
|
||||||
|
if (result.exitCode == 0) {
|
||||||
|
defer.resolve();
|
||||||
|
} else {
|
||||||
|
defer.reject("pre-package command failed with error code " + result.exitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
defer.promise.then(() => {
|
||||||
|
OS.File.setCurrentDirectory(originalCwd);
|
||||||
|
});
|
||||||
|
yield defer.promise;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Unable to run pre-package command '" + command + "' " +
|
||||||
|
args.join(" ") + ":\n" + (e.message || e));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
getPackageDir: Task.async(function* (project) {
|
||||||
|
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||||
|
if (!manifest || !manifest.webide || !manifest.webide.packageDir) {
|
||||||
|
return project.location;
|
||||||
|
}
|
||||||
|
manifest = manifest.webide;
|
||||||
|
|
||||||
|
let packageDir = OS.Path.join(project.location, manifest.packageDir);
|
||||||
|
// On Windows, replace / by \\
|
||||||
|
packageDir = OS.Path.normalize(packageDir);
|
||||||
|
let exists = yield OS.File.exists(packageDir);
|
||||||
|
if (exists) {
|
||||||
|
return packageDir;
|
||||||
|
}
|
||||||
|
throw new Error("Unable to resolve application package directory: '" + manifest.packageDir + "'");
|
||||||
|
})
|
||||||
|
};
|
||||||
373
devtools/client/webide/modules/config-view.js
Normal file
373
devtools/client/webide/modules/config-view.js
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
/* 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 EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const Services = require("Services");
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
var ConfigView;
|
||||||
|
|
||||||
|
module.exports = ConfigView = function (window) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
this._doc = window.document;
|
||||||
|
this._keys = [];
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigView.prototype = {
|
||||||
|
_renderByType: function (input, name, value, customType) {
|
||||||
|
value = customType || typeof value;
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case "boolean":
|
||||||
|
input.setAttribute("data-type", "boolean");
|
||||||
|
input.setAttribute("type", "checkbox");
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
input.setAttribute("data-type", "number");
|
||||||
|
input.setAttribute("type", "number");
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
input.setAttribute("data-type", "object");
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
input.setAttribute("data-type", "string");
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
set front(front) {
|
||||||
|
this._front = front;
|
||||||
|
},
|
||||||
|
|
||||||
|
set keys(keys) {
|
||||||
|
this._keys = keys;
|
||||||
|
},
|
||||||
|
|
||||||
|
get keys() {
|
||||||
|
return this._keys;
|
||||||
|
},
|
||||||
|
|
||||||
|
set kind(kind) {
|
||||||
|
this._kind = kind;
|
||||||
|
},
|
||||||
|
|
||||||
|
set includeTypeName(include) {
|
||||||
|
this._includeTypeName = include;
|
||||||
|
},
|
||||||
|
|
||||||
|
search: function (event) {
|
||||||
|
if (event.target.value.length) {
|
||||||
|
let stringMatch = new RegExp(event.target.value, "i");
|
||||||
|
|
||||||
|
for (let i = 0; i < this._keys.length; i++) {
|
||||||
|
let key = this._keys[i];
|
||||||
|
let row = this._doc.getElementById("row-" + key);
|
||||||
|
if (key.match(stringMatch)) {
|
||||||
|
row.classList.remove("hide");
|
||||||
|
} else if (row) {
|
||||||
|
row.classList.add("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var trs = this._doc.getElementById("device-fields").querySelectorAll("tr");
|
||||||
|
|
||||||
|
for (let i = 0; i < trs.length; i++) {
|
||||||
|
trs[i].classList.remove("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateDisplay: function (json) {
|
||||||
|
let deviceItems = Object.keys(json);
|
||||||
|
deviceItems.sort();
|
||||||
|
this.keys = deviceItems;
|
||||||
|
for (let i = 0; i < this.keys.length; i++) {
|
||||||
|
let key = this.keys[i];
|
||||||
|
this.generateField(key, json[key].value, json[key].hasUserValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateField: function (name, value, hasUserValue, customType, newRow) {
|
||||||
|
let table = this._doc.querySelector("table");
|
||||||
|
let sResetDefault = Strings.GetStringFromName("device_reset_default");
|
||||||
|
|
||||||
|
if (this._keys.indexOf(name) === -1) {
|
||||||
|
this._keys.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = this._doc.createElement("input");
|
||||||
|
let tr = this._doc.createElement("tr");
|
||||||
|
tr.setAttribute("id", "row-" + name);
|
||||||
|
tr.classList.add("edit-row");
|
||||||
|
let td = this._doc.createElement("td");
|
||||||
|
td.classList.add("field-name");
|
||||||
|
td.textContent = name;
|
||||||
|
tr.appendChild(td);
|
||||||
|
td = this._doc.createElement("td");
|
||||||
|
input.classList.add("editable");
|
||||||
|
input.setAttribute("id", name);
|
||||||
|
input = this._renderByType(input, name, value, customType);
|
||||||
|
|
||||||
|
if (customType === "boolean" || input.type === "checkbox") {
|
||||||
|
input.checked = value;
|
||||||
|
} else {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(this._includeTypeName || isNaN(parseInt(value, 10)))) {
|
||||||
|
input.type = "number";
|
||||||
|
}
|
||||||
|
|
||||||
|
td.appendChild(input);
|
||||||
|
tr.appendChild(td);
|
||||||
|
td = this._doc.createElement("td");
|
||||||
|
td.setAttribute("id", "td-" + name);
|
||||||
|
|
||||||
|
let button = this._doc.createElement("button");
|
||||||
|
button.setAttribute("data-id", name);
|
||||||
|
button.setAttribute("id", "btn-" + name);
|
||||||
|
button.classList.add("reset");
|
||||||
|
button.textContent = sResetDefault;
|
||||||
|
td.appendChild(button);
|
||||||
|
|
||||||
|
if (!hasUserValue) {
|
||||||
|
button.classList.add("hide");
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.appendChild(td);
|
||||||
|
|
||||||
|
// If this is a new field, add it to the top of the table.
|
||||||
|
if (newRow) {
|
||||||
|
let existing = table.querySelector("#" + name);
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
table.insertBefore(tr, newRow);
|
||||||
|
} else {
|
||||||
|
existing.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
table.appendChild(tr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resetTable: function () {
|
||||||
|
let table = this._doc.querySelector("table");
|
||||||
|
let trs = table.querySelectorAll("tr:not(#add-custom-field)");
|
||||||
|
|
||||||
|
for (var i = 0; i < trs.length; i++) {
|
||||||
|
table.removeChild(trs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getCallType: function (type, name) {
|
||||||
|
let frontName = "get";
|
||||||
|
|
||||||
|
if (this._includeTypeName) {
|
||||||
|
frontName += type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._front[frontName + this._kind](name);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setCallType: function (type, name, value) {
|
||||||
|
let frontName = "set";
|
||||||
|
|
||||||
|
if (this._includeTypeName) {
|
||||||
|
frontName += type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._front[frontName + this._kind](name, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
_saveByType: function (options) {
|
||||||
|
let fieldName = options.id;
|
||||||
|
let inputType = options.type;
|
||||||
|
let value = options.value;
|
||||||
|
let input = this._doc.getElementById(fieldName);
|
||||||
|
|
||||||
|
switch (inputType) {
|
||||||
|
case "boolean":
|
||||||
|
this._setCallType("Bool", fieldName, input.checked);
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
this._setCallType("Int", fieldName, value);
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
try {
|
||||||
|
value = JSON.parse(value);
|
||||||
|
} catch (e) {}
|
||||||
|
this._setCallType("Object", fieldName, value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._setCallType("Char", fieldName, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateField: function (event) {
|
||||||
|
if (event.target) {
|
||||||
|
let inputType = event.target.getAttribute("data-type");
|
||||||
|
let inputValue = event.target.checked || event.target.value;
|
||||||
|
|
||||||
|
if (event.target.nodeName == "input" &&
|
||||||
|
event.target.validity.valid &&
|
||||||
|
event.target.classList.contains("editable")) {
|
||||||
|
let id = event.target.id;
|
||||||
|
if (inputType === "boolean") {
|
||||||
|
if (event.target.checked) {
|
||||||
|
inputValue = true;
|
||||||
|
} else {
|
||||||
|
inputValue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._saveByType({
|
||||||
|
id: id,
|
||||||
|
type: inputType,
|
||||||
|
value: inputValue
|
||||||
|
});
|
||||||
|
this._doc.getElementById("btn-" + id).classList.remove("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_resetToDefault: function (name, input, button) {
|
||||||
|
this._front["clearUser" + this._kind](name);
|
||||||
|
let dataType = input.getAttribute("data-type");
|
||||||
|
let tr = this._doc.getElementById("row-" + name);
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case "boolean":
|
||||||
|
this._defaultField = this._getCallType("Bool", name);
|
||||||
|
this._defaultField.then(boolean => {
|
||||||
|
input.checked = boolean;
|
||||||
|
}, () => {
|
||||||
|
input.checked = false;
|
||||||
|
tr.parentNode.removeChild(tr);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
this._defaultField = this._getCallType("Int", name);
|
||||||
|
this._defaultField.then(number => {
|
||||||
|
input.value = number;
|
||||||
|
}, () => {
|
||||||
|
tr.parentNode.removeChild(tr);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
this._defaultField = this._getCallType("Object", name);
|
||||||
|
this._defaultField.then(object => {
|
||||||
|
input.value = JSON.stringify(object);
|
||||||
|
}, () => {
|
||||||
|
tr.parentNode.removeChild(tr);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._defaultField = this._getCallType("Char", name);
|
||||||
|
this._defaultField.then(string => {
|
||||||
|
input.value = string;
|
||||||
|
}, () => {
|
||||||
|
tr.parentNode.removeChild(tr);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.classList.add("hide");
|
||||||
|
},
|
||||||
|
|
||||||
|
checkReset: function (event) {
|
||||||
|
if (event.target.classList.contains("reset")) {
|
||||||
|
let btnId = event.target.getAttribute("data-id");
|
||||||
|
let input = this._doc.getElementById(btnId);
|
||||||
|
this._resetToDefault(btnId, input, event.target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFieldType: function () {
|
||||||
|
let table = this._doc.querySelector("table");
|
||||||
|
let customValueType = table.querySelector("#custom-value-type").value;
|
||||||
|
let customTextEl = table.querySelector("#custom-value-text");
|
||||||
|
let customText = customTextEl.value;
|
||||||
|
|
||||||
|
if (customValueType.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (customValueType) {
|
||||||
|
case "boolean":
|
||||||
|
customTextEl.type = "checkbox";
|
||||||
|
customText = customTextEl.checked;
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
customText = parseInt(customText, 10) || 0;
|
||||||
|
customTextEl.type = "number";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
customTextEl.type = "text";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return customValueType;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearNewFields: function () {
|
||||||
|
let table = this._doc.querySelector("table");
|
||||||
|
let customTextEl = table.querySelector("#custom-value-text");
|
||||||
|
if (customTextEl.checked) {
|
||||||
|
customTextEl.checked = false;
|
||||||
|
} else {
|
||||||
|
customTextEl.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateFieldType();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNewField: function () {
|
||||||
|
let table = this._doc.querySelector("table");
|
||||||
|
let customValueType = this.updateFieldType();
|
||||||
|
|
||||||
|
if (!customValueType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let customRow = table.querySelector("tr:nth-of-type(2)");
|
||||||
|
let customTextEl = table.querySelector("#custom-value-text");
|
||||||
|
let customTextNameEl = table.querySelector("#custom-value-name");
|
||||||
|
|
||||||
|
if (customTextEl.validity.valid) {
|
||||||
|
let customText = customTextEl.value;
|
||||||
|
|
||||||
|
if (customValueType === "boolean") {
|
||||||
|
customText = customTextEl.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
let customTextName = customTextNameEl.value.replace(/[^A-Za-z0-9\.\-_]/gi, "");
|
||||||
|
this.generateField(customTextName, customText, true, customValueType, customRow);
|
||||||
|
this._saveByType({
|
||||||
|
id: customTextName,
|
||||||
|
type: customValueType,
|
||||||
|
value: customText
|
||||||
|
});
|
||||||
|
customTextNameEl.value = "";
|
||||||
|
this.clearNewFields();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkNewFieldSubmit: function (event) {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
this._doc.getElementById("custom-value").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
21
devtools/client/webide/modules/moz.build
Normal file
21
devtools/client/webide/modules/moz.build
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- 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(
|
||||||
|
'addons.js',
|
||||||
|
'app-manager.js',
|
||||||
|
'app-projects.js',
|
||||||
|
'app-validator.js',
|
||||||
|
'build.js',
|
||||||
|
'config-view.js',
|
||||||
|
'project-list.js',
|
||||||
|
'runtime-list.js',
|
||||||
|
'runtimes.js',
|
||||||
|
'simulator-process.js',
|
||||||
|
'simulators.js',
|
||||||
|
'tab-store.js',
|
||||||
|
'utils.js'
|
||||||
|
)
|
||||||
375
devtools/client/webide/modules/project-list.js
Normal file
375
devtools/client/webide/modules/project-list.js
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
/* 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 Services = require("Services");
|
||||||
|
const {AppProjects} = require("devtools/client/webide/modules/app-projects");
|
||||||
|
const {AppManager} = require("devtools/client/webide/modules/app-manager");
|
||||||
|
const promise = require("promise");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const {Task} = require("devtools/shared/task");
|
||||||
|
const utils = require("devtools/client/webide/modules/utils");
|
||||||
|
const Telemetry = require("devtools/client/shared/telemetry");
|
||||||
|
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
var ProjectList;
|
||||||
|
|
||||||
|
module.exports = ProjectList = function (win, parentWindow) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
this._doc = win.document;
|
||||||
|
this._UI = parentWindow.UI;
|
||||||
|
this._parentWindow = parentWindow;
|
||||||
|
this._telemetry = new Telemetry();
|
||||||
|
this._panelNodeEl = "div";
|
||||||
|
|
||||||
|
this.onWebIDEUpdate = this.onWebIDEUpdate.bind(this);
|
||||||
|
this._UI.on("webide-update", this.onWebIDEUpdate);
|
||||||
|
|
||||||
|
AppManager.init();
|
||||||
|
this.appManagerUpdate = this.appManagerUpdate.bind(this);
|
||||||
|
AppManager.on("app-manager-update", this.appManagerUpdate);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProjectList.prototype = {
|
||||||
|
get doc() {
|
||||||
|
return this._doc;
|
||||||
|
},
|
||||||
|
|
||||||
|
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 "project-removed":
|
||||||
|
case "runtime-apps-icons":
|
||||||
|
case "runtime-targets":
|
||||||
|
case "connection":
|
||||||
|
this.update(details);
|
||||||
|
break;
|
||||||
|
case "project":
|
||||||
|
this.updateCommands();
|
||||||
|
this.update(details);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onWebIDEUpdate: function (event, what, details) {
|
||||||
|
if (what == "busy" || what == "unbusy") {
|
||||||
|
this.updateCommands();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testOptions: { chrome mochitest support
|
||||||
|
* folder: nsIFile, where to store the app
|
||||||
|
* index: Number, index of the app in the template list
|
||||||
|
* name: String name of the app
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
newApp: function (testOptions) {
|
||||||
|
let parentWindow = this._parentWindow;
|
||||||
|
let self = this;
|
||||||
|
return this._UI.busyUntil(Task.spawn(function* () {
|
||||||
|
// Open newapp.xul, which will feed ret.location
|
||||||
|
let ret = {location: null, testOptions: testOptions};
|
||||||
|
parentWindow.openDialog("chrome://webide/content/newapp.xul", "newapp", "chrome,modal", ret);
|
||||||
|
if (!ret.location)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Retrieve added project
|
||||||
|
let project = AppProjects.get(ret.location);
|
||||||
|
|
||||||
|
// Select project
|
||||||
|
AppManager.selectedProject = project;
|
||||||
|
|
||||||
|
self._telemetry.actionOccurred("webideNewProject");
|
||||||
|
}), "creating new app");
|
||||||
|
},
|
||||||
|
|
||||||
|
importPackagedApp: function (location) {
|
||||||
|
let parentWindow = this._parentWindow;
|
||||||
|
let UI = this._UI;
|
||||||
|
return UI.busyUntil(Task.spawn(function* () {
|
||||||
|
let directory = utils.getPackagedDirectory(parentWindow, location);
|
||||||
|
|
||||||
|
if (!directory) {
|
||||||
|
// User cancelled directory selection
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield UI.importAndSelectApp(directory);
|
||||||
|
}), "importing packaged app");
|
||||||
|
},
|
||||||
|
|
||||||
|
importHostedApp: function (location) {
|
||||||
|
let parentWindow = this._parentWindow;
|
||||||
|
let UI = this._UI;
|
||||||
|
return UI.busyUntil(Task.spawn(function* () {
|
||||||
|
let url = utils.getHostedURL(parentWindow, location);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield UI.importAndSelectApp(url);
|
||||||
|
}), "importing hosted app");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* opts: {
|
||||||
|
* panel: Object, currenl project panel node
|
||||||
|
* name: String, name of the project
|
||||||
|
* icon: String path of the project icon
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
_renderProjectItem: function (opts) {
|
||||||
|
let span = opts.panel.querySelector("span") || this._doc.createElement("span");
|
||||||
|
span.textContent = opts.name;
|
||||||
|
let icon = opts.panel.querySelector("img") || this._doc.createElement("img");
|
||||||
|
icon.className = "project-image";
|
||||||
|
icon.setAttribute("src", opts.icon);
|
||||||
|
opts.panel.appendChild(icon);
|
||||||
|
opts.panel.appendChild(span);
|
||||||
|
opts.panel.setAttribute("title", opts.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshTabs: function () {
|
||||||
|
if (AppManager.connected) {
|
||||||
|
return AppManager.listTabs().then(() => {
|
||||||
|
this.updateTabs();
|
||||||
|
}).catch(console.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTabs: function () {
|
||||||
|
let tabsHeaderNode = this._doc.querySelector("#panel-header-tabs");
|
||||||
|
let tabsNode = this._doc.querySelector("#project-panel-tabs");
|
||||||
|
|
||||||
|
while (tabsNode.hasChildNodes()) {
|
||||||
|
tabsNode.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AppManager.connected) {
|
||||||
|
tabsHeaderNode.setAttribute("hidden", "true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tabs = AppManager.tabStore.tabs;
|
||||||
|
|
||||||
|
tabsHeaderNode.removeAttribute("hidden");
|
||||||
|
|
||||||
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
|
let tab = tabs[i];
|
||||||
|
let URL = this._parentWindow.URL;
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(tab.url);
|
||||||
|
} catch (e) {
|
||||||
|
// Don't try to handle invalid URLs, especially from Valence.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Wanted to use nsIFaviconService here, but it only works for visited
|
||||||
|
// tabs, so that's no help for any remote tabs. Maybe some favicon wizard
|
||||||
|
// knows how to get high-res favicons easily, or we could offer actor
|
||||||
|
// support for this (bug 1061654).
|
||||||
|
if (url.origin) {
|
||||||
|
tab.favicon = url.origin + "/favicon.ico";
|
||||||
|
}
|
||||||
|
tab.name = tab.title || Strings.GetStringFromName("project_tab_loading");
|
||||||
|
if (url.protocol.startsWith("http")) {
|
||||||
|
tab.name = url.hostname + ": " + tab.name;
|
||||||
|
}
|
||||||
|
let panelItemNode = this._doc.createElement(this._panelNodeEl);
|
||||||
|
panelItemNode.className = "panel-item";
|
||||||
|
tabsNode.appendChild(panelItemNode);
|
||||||
|
this._renderProjectItem({
|
||||||
|
panel: panelItemNode,
|
||||||
|
name: tab.name,
|
||||||
|
icon: tab.favicon || AppManager.DEFAULT_PROJECT_ICON
|
||||||
|
});
|
||||||
|
panelItemNode.addEventListener("click", () => {
|
||||||
|
AppManager.selectedProject = {
|
||||||
|
type: "tab",
|
||||||
|
app: tab,
|
||||||
|
icon: tab.favicon || AppManager.DEFAULT_PROJECT_ICON,
|
||||||
|
location: tab.url,
|
||||||
|
name: tab.name
|
||||||
|
};
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateApps: function () {
|
||||||
|
let doc = this._doc;
|
||||||
|
let runtimeappsHeaderNode = doc.querySelector("#panel-header-runtimeapps");
|
||||||
|
let sortedApps = [];
|
||||||
|
for (let [manifestURL, app] of AppManager.apps) {
|
||||||
|
sortedApps.push(app);
|
||||||
|
}
|
||||||
|
sortedApps = sortedApps.sort((a, b) => {
|
||||||
|
return a.manifest.name > b.manifest.name;
|
||||||
|
});
|
||||||
|
let mainProcess = AppManager.isMainProcessDebuggable();
|
||||||
|
if (AppManager.connected && (sortedApps.length > 0 || mainProcess)) {
|
||||||
|
runtimeappsHeaderNode.removeAttribute("hidden");
|
||||||
|
} else {
|
||||||
|
runtimeappsHeaderNode.setAttribute("hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
let runtimeAppsNode = doc.querySelector("#project-panel-runtimeapps");
|
||||||
|
while (runtimeAppsNode.hasChildNodes()) {
|
||||||
|
runtimeAppsNode.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainProcess) {
|
||||||
|
let panelItemNode = doc.createElement(this._panelNodeEl);
|
||||||
|
panelItemNode.className = "panel-item";
|
||||||
|
this._renderProjectItem({
|
||||||
|
panel: panelItemNode,
|
||||||
|
name: Strings.GetStringFromName("mainProcess_label"),
|
||||||
|
icon: AppManager.DEFAULT_PROJECT_ICON
|
||||||
|
});
|
||||||
|
runtimeAppsNode.appendChild(panelItemNode);
|
||||||
|
panelItemNode.addEventListener("click", () => {
|
||||||
|
AppManager.selectedProject = {
|
||||||
|
type: "mainProcess",
|
||||||
|
name: Strings.GetStringFromName("mainProcess_label"),
|
||||||
|
icon: AppManager.DEFAULT_PROJECT_ICON
|
||||||
|
};
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedApps.length; i++) {
|
||||||
|
let app = sortedApps[i];
|
||||||
|
let panelItemNode = doc.createElement(this._panelNodeEl);
|
||||||
|
panelItemNode.className = "panel-item";
|
||||||
|
this._renderProjectItem({
|
||||||
|
panel: panelItemNode,
|
||||||
|
name: app.manifest.name,
|
||||||
|
icon: app.iconURL || AppManager.DEFAULT_PROJECT_ICON
|
||||||
|
});
|
||||||
|
runtimeAppsNode.appendChild(panelItemNode);
|
||||||
|
panelItemNode.addEventListener("click", () => {
|
||||||
|
AppManager.selectedProject = {
|
||||||
|
type: "runtimeApp",
|
||||||
|
app: app.manifest,
|
||||||
|
icon: app.iconURL || AppManager.DEFAULT_PROJECT_ICON,
|
||||||
|
name: app.manifest.name
|
||||||
|
};
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCommands: function () {
|
||||||
|
let doc = this._doc;
|
||||||
|
let newAppCmd;
|
||||||
|
let packagedAppCmd;
|
||||||
|
let hostedAppCmd;
|
||||||
|
|
||||||
|
newAppCmd = doc.querySelector("#new-app");
|
||||||
|
packagedAppCmd = doc.querySelector("#packaged-app");
|
||||||
|
hostedAppCmd = doc.querySelector("#hosted-app");
|
||||||
|
|
||||||
|
if (!newAppCmd || !packagedAppCmd || !hostedAppCmd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._parentWindow.document.querySelector("window").classList.contains("busy")) {
|
||||||
|
newAppCmd.setAttribute("disabled", "true");
|
||||||
|
packagedAppCmd.setAttribute("disabled", "true");
|
||||||
|
hostedAppCmd.setAttribute("disabled", "true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newAppCmd.removeAttribute("disabled");
|
||||||
|
packagedAppCmd.removeAttribute("disabled");
|
||||||
|
hostedAppCmd.removeAttribute("disabled");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger an update of the project and remote runtime list.
|
||||||
|
* @param options object (optional)
|
||||||
|
* An |options| object containing a type of |apps| or |tabs| will limit
|
||||||
|
* what is updated to only those sections.
|
||||||
|
*/
|
||||||
|
update: function (options) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
if (options && options.type === "apps") {
|
||||||
|
return this.updateApps();
|
||||||
|
} else if (options && options.type === "tabs") {
|
||||||
|
return this.updateTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = this._doc;
|
||||||
|
let projectsNode = doc.querySelector("#project-panel-projects");
|
||||||
|
|
||||||
|
while (projectsNode.hasChildNodes()) {
|
||||||
|
projectsNode.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProjects.load().then(() => {
|
||||||
|
let projects = AppProjects.projects;
|
||||||
|
for (let i = 0; i < projects.length; i++) {
|
||||||
|
let project = projects[i];
|
||||||
|
let panelItemNode = doc.createElement(this._panelNodeEl);
|
||||||
|
panelItemNode.className = "panel-item";
|
||||||
|
projectsNode.appendChild(panelItemNode);
|
||||||
|
if (!project.validationStatus) {
|
||||||
|
// The result of the validation process (storing names, icons, …) is not stored in
|
||||||
|
// the IndexedDB database when App Manager v1 is used.
|
||||||
|
// We need to run the validation again and update the name and icon of the app.
|
||||||
|
AppManager.validateAndUpdateProject(project).then(() => {
|
||||||
|
this._renderProjectItem({
|
||||||
|
panel: panelItemNode,
|
||||||
|
name: project.name,
|
||||||
|
icon: project.icon
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._renderProjectItem({
|
||||||
|
panel: panelItemNode,
|
||||||
|
name: project.name || AppManager.DEFAULT_PROJECT_NAME,
|
||||||
|
icon: project.icon || AppManager.DEFAULT_PROJECT_ICON
|
||||||
|
});
|
||||||
|
}
|
||||||
|
panelItemNode.addEventListener("click", () => {
|
||||||
|
AppManager.selectedProject = project;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
deferred.resolve();
|
||||||
|
}, deferred.reject);
|
||||||
|
|
||||||
|
// List remote apps and the main process, if they exist
|
||||||
|
this.updateApps();
|
||||||
|
|
||||||
|
// Build the tab list right now, so it's fast...
|
||||||
|
this.updateTabs();
|
||||||
|
|
||||||
|
// But re-list them and rebuild, in case any tabs navigated since the last
|
||||||
|
// time they were listed.
|
||||||
|
if (AppManager.connected) {
|
||||||
|
AppManager.listTabs().then(() => {
|
||||||
|
this.updateTabs();
|
||||||
|
}).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
this._doc = null;
|
||||||
|
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||||
|
this._UI.off("webide-update", this.onWebIDEUpdate);
|
||||||
|
this._UI = null;
|
||||||
|
this._parentWindow = null;
|
||||||
|
this._panelNodeEl = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
207
devtools/client/webide/modules/runtime-list.js
Normal file
207
devtools/client/webide/modules/runtime-list.js
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
/* 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 Services = require("Services");
|
||||||
|
const {AppManager} = require("devtools/client/webide/modules/app-manager");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const {RuntimeScanners, WiFiScanner} = require("devtools/client/webide/modules/runtimes");
|
||||||
|
const {Devices} = require("resource://devtools/shared/apps/Devices.jsm");
|
||||||
|
const {Task} = require("devtools/shared/task");
|
||||||
|
const utils = require("devtools/client/webide/modules/utils");
|
||||||
|
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
var RuntimeList;
|
||||||
|
|
||||||
|
module.exports = RuntimeList = function (window, parentWindow) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
this._doc = window.document;
|
||||||
|
this._UI = parentWindow.UI;
|
||||||
|
this._Cmds = parentWindow.Cmds;
|
||||||
|
this._parentWindow = parentWindow;
|
||||||
|
this._panelNodeEl = "button";
|
||||||
|
this._panelBoxEl = "div";
|
||||||
|
|
||||||
|
this.onWebIDEUpdate = this.onWebIDEUpdate.bind(this);
|
||||||
|
this._UI.on("webide-update", this.onWebIDEUpdate);
|
||||||
|
|
||||||
|
AppManager.init();
|
||||||
|
this.appManagerUpdate = this.appManagerUpdate.bind(this);
|
||||||
|
AppManager.on("app-manager-update", this.appManagerUpdate);
|
||||||
|
};
|
||||||
|
|
||||||
|
RuntimeList.prototype = {
|
||||||
|
get doc() {
|
||||||
|
return this._doc;
|
||||||
|
},
|
||||||
|
|
||||||
|
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.update();
|
||||||
|
break;
|
||||||
|
case "connection":
|
||||||
|
case "runtime-global-actors":
|
||||||
|
this.updateCommands();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onWebIDEUpdate: function (event, what, details) {
|
||||||
|
if (what == "busy" || what == "unbusy") {
|
||||||
|
this.updateCommands();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
takeScreenshot: function () {
|
||||||
|
this._Cmds.takeScreenshot();
|
||||||
|
},
|
||||||
|
|
||||||
|
showRuntimeDetails: function () {
|
||||||
|
this._Cmds.showRuntimeDetails();
|
||||||
|
},
|
||||||
|
|
||||||
|
showPermissionsTable: function () {
|
||||||
|
this._Cmds.showPermissionsTable();
|
||||||
|
},
|
||||||
|
|
||||||
|
showDevicePreferences: function () {
|
||||||
|
this._Cmds.showDevicePrefs();
|
||||||
|
},
|
||||||
|
|
||||||
|
showSettings: function () {
|
||||||
|
this._Cmds.showSettings();
|
||||||
|
},
|
||||||
|
|
||||||
|
showTroubleShooting: function () {
|
||||||
|
this._Cmds.showTroubleShooting();
|
||||||
|
},
|
||||||
|
|
||||||
|
showAddons: function () {
|
||||||
|
this._Cmds.showAddons();
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshScanners: function () {
|
||||||
|
RuntimeScanners.scan();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCommands: function () {
|
||||||
|
let doc = this._doc;
|
||||||
|
|
||||||
|
// Runtime commands
|
||||||
|
let screenshotCmd = doc.querySelector("#runtime-screenshot");
|
||||||
|
let permissionsCmd = doc.querySelector("#runtime-permissions");
|
||||||
|
let detailsCmd = doc.querySelector("#runtime-details");
|
||||||
|
let disconnectCmd = doc.querySelector("#runtime-disconnect");
|
||||||
|
let devicePrefsCmd = doc.querySelector("#runtime-preferences");
|
||||||
|
let settingsCmd = doc.querySelector("#runtime-settings");
|
||||||
|
|
||||||
|
if (AppManager.connected) {
|
||||||
|
if (AppManager.deviceFront) {
|
||||||
|
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 {
|
||||||
|
detailsCmd.setAttribute("disabled", "true");
|
||||||
|
permissionsCmd.setAttribute("disabled", "true");
|
||||||
|
screenshotCmd.setAttribute("disabled", "true");
|
||||||
|
disconnectCmd.setAttribute("disabled", "true");
|
||||||
|
devicePrefsCmd.setAttribute("disabled", "true");
|
||||||
|
settingsCmd.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function () {
|
||||||
|
let doc = this._doc;
|
||||||
|
let wifiHeaderNode = doc.querySelector("#runtime-header-wifi");
|
||||||
|
|
||||||
|
if (WiFiScanner.allowed) {
|
||||||
|
wifiHeaderNode.removeAttribute("hidden");
|
||||||
|
} else {
|
||||||
|
wifiHeaderNode.setAttribute("hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
let usbListNode = doc.querySelector("#runtime-panel-usb");
|
||||||
|
let wifiListNode = doc.querySelector("#runtime-panel-wifi");
|
||||||
|
let simulatorListNode = doc.querySelector("#runtime-panel-simulator");
|
||||||
|
let otherListNode = doc.querySelector("#runtime-panel-other");
|
||||||
|
let noHelperNode = doc.querySelector("#runtime-panel-noadbhelper");
|
||||||
|
let noUSBNode = doc.querySelector("#runtime-panel-nousbdevice");
|
||||||
|
|
||||||
|
if (Devices.helperAddonInstalled) {
|
||||||
|
noHelperNode.setAttribute("hidden", "true");
|
||||||
|
} else {
|
||||||
|
noHelperNode.removeAttribute("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
let runtimeList = AppManager.runtimeList;
|
||||||
|
|
||||||
|
if (!runtimeList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtimeList.usb.length === 0 && Devices.helperAddonInstalled) {
|
||||||
|
noUSBNode.removeAttribute("hidden");
|
||||||
|
} else {
|
||||||
|
noUSBNode.setAttribute("hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [type, parent] of [
|
||||||
|
["usb", usbListNode],
|
||||||
|
["wifi", wifiListNode],
|
||||||
|
["simulator", simulatorListNode],
|
||||||
|
["other", otherListNode],
|
||||||
|
]) {
|
||||||
|
while (parent.hasChildNodes()) {
|
||||||
|
parent.firstChild.remove();
|
||||||
|
}
|
||||||
|
for (let runtime of runtimeList[type]) {
|
||||||
|
let r = runtime;
|
||||||
|
let panelItemNode = doc.createElement(this._panelBoxEl);
|
||||||
|
panelItemNode.className = "panel-item-complex";
|
||||||
|
|
||||||
|
let connectButton = doc.createElement(this._panelNodeEl);
|
||||||
|
connectButton.className = "panel-item runtime-panel-item-" + type;
|
||||||
|
connectButton.textContent = r.name;
|
||||||
|
|
||||||
|
connectButton.addEventListener("click", () => {
|
||||||
|
this._UI.dismissErrorNotification();
|
||||||
|
this._UI.connectToRuntime(r);
|
||||||
|
}, true);
|
||||||
|
panelItemNode.appendChild(connectButton);
|
||||||
|
|
||||||
|
if (r.configure) {
|
||||||
|
let configButton = doc.createElement(this._panelNodeEl);
|
||||||
|
configButton.className = "configure-button";
|
||||||
|
configButton.addEventListener("click", r.configure.bind(r), true);
|
||||||
|
panelItemNode.appendChild(configButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appendChild(panelItemNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
this._doc = null;
|
||||||
|
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||||
|
this._UI.off("webide-update", this.onWebIDEUpdate);
|
||||||
|
this._UI = null;
|
||||||
|
this._Cmds = null;
|
||||||
|
this._parentWindow = null;
|
||||||
|
this._panelNodeEl = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
673
devtools/client/webide/modules/runtimes.js
Normal file
673
devtools/client/webide/modules/runtimes.js
Normal file
|
|
@ -0,0 +1,673 @@
|
||||||
|
/* 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} = require("chrome");
|
||||||
|
const Services = require("Services");
|
||||||
|
const {Devices} = require("resource://devtools/shared/apps/Devices.jsm");
|
||||||
|
const {Connection} = require("devtools/shared/client/connection-manager");
|
||||||
|
const {DebuggerServer} = require("devtools/server/main");
|
||||||
|
const {Simulators} = require("devtools/client/webide/modules/simulators");
|
||||||
|
const discovery = require("devtools/shared/discovery/discovery");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const promise = require("promise");
|
||||||
|
loader.lazyRequireGetter(this, "AuthenticationResult",
|
||||||
|
"devtools/shared/security/auth", true);
|
||||||
|
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||||
|
"devtools/shared/DevToolsUtils");
|
||||||
|
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime and Scanner API
|
||||||
|
*
|
||||||
|
* |RuntimeScanners| maintains a set of |Scanner| objects that produce one or
|
||||||
|
* more |Runtime|s to connect to. Add-ons can extend the set of known runtimes
|
||||||
|
* by registering additional |Scanner|s that emit them.
|
||||||
|
*
|
||||||
|
* Each |Scanner| must support the following API:
|
||||||
|
*
|
||||||
|
* enable()
|
||||||
|
* Bind any event handlers and start any background work the scanner needs to
|
||||||
|
* maintain an updated set of |Runtime|s.
|
||||||
|
* Called when the first consumer (such as WebIDE) actively interested in
|
||||||
|
* maintaining the |Runtime| list enables the registry.
|
||||||
|
* disable()
|
||||||
|
* Unbind any event handlers and stop any background work the scanner needs to
|
||||||
|
* maintain an updated set of |Runtime|s.
|
||||||
|
* Called when the last consumer (such as WebIDE) actively interested in
|
||||||
|
* maintaining the |Runtime| list disables the registry.
|
||||||
|
* emits "runtime-list-updated"
|
||||||
|
* If the set of runtimes a |Scanner| manages has changed, it must emit this
|
||||||
|
* event to notify consumers of changes.
|
||||||
|
* scan()
|
||||||
|
* Actively refreshes the list of runtimes the scanner knows about. If your
|
||||||
|
* scanner uses an active scanning approach (as opposed to listening for
|
||||||
|
* events when changes occur), the bulk of the work would be done here.
|
||||||
|
* @return Promise
|
||||||
|
* Should be resolved when scanning is complete. If scanning has no
|
||||||
|
* well-defined end point, you can resolve immediately, as long as
|
||||||
|
* update event is emitted later when changes are noticed.
|
||||||
|
* listRuntimes()
|
||||||
|
* Return the current list of runtimes known to the |Scanner| instance.
|
||||||
|
* @return Iterable
|
||||||
|
*
|
||||||
|
* Each |Runtime| must support the following API:
|
||||||
|
*
|
||||||
|
* |type| field
|
||||||
|
* The |type| must be one of the values from the |RuntimeTypes| object. This
|
||||||
|
* is used for Telemetry and to support displaying sets of |Runtime|s
|
||||||
|
* categorized by type.
|
||||||
|
* |id| field
|
||||||
|
* An identifier that is unique in the set of all runtimes with the same
|
||||||
|
* |type|. WebIDE tries to save the last used runtime via type + id, and
|
||||||
|
* tries to locate it again in the next session, so this value should attempt
|
||||||
|
* to be stable across Firefox sessions.
|
||||||
|
* |name| field
|
||||||
|
* A user-visible label to identify the runtime that will be displayed in a
|
||||||
|
* runtime list.
|
||||||
|
* |prolongedConnection| field
|
||||||
|
* A boolean value which should be |true| if the connection process is
|
||||||
|
* expected to take a unknown or large amount of time. A UI may use this as a
|
||||||
|
* hint to skip timeouts or other time-based code paths.
|
||||||
|
* connect()
|
||||||
|
* Configure the passed |connection| object with any settings need to
|
||||||
|
* successfully connect to the runtime, and call the |connection|'s connect()
|
||||||
|
* method.
|
||||||
|
* @param Connection connection
|
||||||
|
* A |Connection| object from the DevTools |ConnectionManager|.
|
||||||
|
* @return Promise
|
||||||
|
* Resolved once you've called the |connection|'s connect() method.
|
||||||
|
* configure() OPTIONAL
|
||||||
|
* Show a configuration screen if the runtime is configurable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* SCANNER REGISTRY */
|
||||||
|
|
||||||
|
var RuntimeScanners = {
|
||||||
|
|
||||||
|
_enabledCount: 0,
|
||||||
|
_scanners: new Set(),
|
||||||
|
|
||||||
|
get enabled() {
|
||||||
|
return !!this._enabledCount;
|
||||||
|
},
|
||||||
|
|
||||||
|
add(scanner) {
|
||||||
|
if (this.enabled) {
|
||||||
|
// Enable any scanner added while globally enabled
|
||||||
|
this._enableScanner(scanner);
|
||||||
|
}
|
||||||
|
this._scanners.add(scanner);
|
||||||
|
this._emitUpdated();
|
||||||
|
},
|
||||||
|
|
||||||
|
remove(scanner) {
|
||||||
|
this._scanners.delete(scanner);
|
||||||
|
if (this.enabled) {
|
||||||
|
// Disable any scanner removed while globally enabled
|
||||||
|
this._disableScanner(scanner);
|
||||||
|
}
|
||||||
|
this._emitUpdated();
|
||||||
|
},
|
||||||
|
|
||||||
|
has(scanner) {
|
||||||
|
return this._scanners.has(scanner);
|
||||||
|
},
|
||||||
|
|
||||||
|
scan() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._scanPromise) {
|
||||||
|
return this._scanPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
for (let scanner of this._scanners) {
|
||||||
|
promises.push(scanner.scan());
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scanPromise = promise.all(promises);
|
||||||
|
|
||||||
|
// Reset pending promise
|
||||||
|
this._scanPromise.then(() => {
|
||||||
|
this._scanPromise = null;
|
||||||
|
}, () => {
|
||||||
|
this._scanPromise = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._scanPromise;
|
||||||
|
},
|
||||||
|
|
||||||
|
listRuntimes: function* () {
|
||||||
|
for (let scanner of this._scanners) {
|
||||||
|
for (let runtime of scanner.listRuntimes()) {
|
||||||
|
yield runtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_emitUpdated() {
|
||||||
|
this.emit("runtime-list-updated");
|
||||||
|
},
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
if (this._enabledCount++ !== 0) {
|
||||||
|
// Already enabled scanners during a previous call
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._emitUpdated = this._emitUpdated.bind(this);
|
||||||
|
for (let scanner of this._scanners) {
|
||||||
|
this._enableScanner(scanner);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_enableScanner(scanner) {
|
||||||
|
scanner.enable();
|
||||||
|
scanner.on("runtime-list-updated", this._emitUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
if (--this._enabledCount !== 0) {
|
||||||
|
// Already disabled scanners during a previous call
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let scanner of this._scanners) {
|
||||||
|
this._disableScanner(scanner);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_disableScanner(scanner) {
|
||||||
|
scanner.off("runtime-list-updated", this._emitUpdated);
|
||||||
|
scanner.disable();
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(RuntimeScanners);
|
||||||
|
|
||||||
|
exports.RuntimeScanners = RuntimeScanners;
|
||||||
|
|
||||||
|
/* SCANNERS */
|
||||||
|
|
||||||
|
var SimulatorScanner = {
|
||||||
|
|
||||||
|
_runtimes: [],
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this._updateRuntimes = this._updateRuntimes.bind(this);
|
||||||
|
Simulators.on("updated", this._updateRuntimes);
|
||||||
|
this._updateRuntimes();
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
Simulators.off("updated", this._updateRuntimes);
|
||||||
|
},
|
||||||
|
|
||||||
|
_emitUpdated() {
|
||||||
|
this.emit("runtime-list-updated");
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateRuntimes() {
|
||||||
|
Simulators.findSimulators().then(simulators => {
|
||||||
|
this._runtimes = [];
|
||||||
|
for (let simulator of simulators) {
|
||||||
|
this._runtimes.push(new SimulatorRuntime(simulator));
|
||||||
|
}
|
||||||
|
this._emitUpdated();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
scan() {
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
listRuntimes: function () {
|
||||||
|
return this._runtimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(SimulatorScanner);
|
||||||
|
RuntimeScanners.add(SimulatorScanner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove this comaptibility layer in the future (bug 1085393)
|
||||||
|
* This runtime exists to support the ADB Helper add-on below version 0.7.0.
|
||||||
|
*
|
||||||
|
* This scanner will list all ADB devices as runtimes, even if they may or may
|
||||||
|
* not actually connect (since the |DeprecatedUSBRuntime| assumes a Firefox OS
|
||||||
|
* device).
|
||||||
|
*/
|
||||||
|
var DeprecatedAdbScanner = {
|
||||||
|
|
||||||
|
_runtimes: [],
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this._updateRuntimes = this._updateRuntimes.bind(this);
|
||||||
|
Devices.on("register", this._updateRuntimes);
|
||||||
|
Devices.on("unregister", this._updateRuntimes);
|
||||||
|
Devices.on("addon-status-updated", this._updateRuntimes);
|
||||||
|
this._updateRuntimes();
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
Devices.off("register", this._updateRuntimes);
|
||||||
|
Devices.off("unregister", this._updateRuntimes);
|
||||||
|
Devices.off("addon-status-updated", this._updateRuntimes);
|
||||||
|
},
|
||||||
|
|
||||||
|
_emitUpdated() {
|
||||||
|
this.emit("runtime-list-updated");
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateRuntimes() {
|
||||||
|
this._runtimes = [];
|
||||||
|
for (let id of Devices.available()) {
|
||||||
|
let runtime = new DeprecatedUSBRuntime(id);
|
||||||
|
this._runtimes.push(runtime);
|
||||||
|
runtime.updateNameFromADB().then(() => {
|
||||||
|
this._emitUpdated();
|
||||||
|
}, () => {});
|
||||||
|
}
|
||||||
|
this._emitUpdated();
|
||||||
|
},
|
||||||
|
|
||||||
|
scan() {
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
listRuntimes: function () {
|
||||||
|
return this._runtimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(DeprecatedAdbScanner);
|
||||||
|
RuntimeScanners.add(DeprecatedAdbScanner);
|
||||||
|
|
||||||
|
// ADB Helper 0.7.0 and later will replace this scanner on startup
|
||||||
|
exports.DeprecatedAdbScanner = DeprecatedAdbScanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a lazy ADB scanner shim which only tells the ADB Helper to start and
|
||||||
|
* stop as needed. The real scanner that lists devices lives in ADB Helper.
|
||||||
|
* ADB Helper 0.8.0 and later wait until these signals are received before
|
||||||
|
* starting ADB polling. For earlier versions, they have no effect.
|
||||||
|
*/
|
||||||
|
var LazyAdbScanner = {
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
Devices.emit("adb-start-polling");
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
Devices.emit("adb-stop-polling");
|
||||||
|
},
|
||||||
|
|
||||||
|
scan() {
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
listRuntimes: function () {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(LazyAdbScanner);
|
||||||
|
RuntimeScanners.add(LazyAdbScanner);
|
||||||
|
|
||||||
|
var WiFiScanner = {
|
||||||
|
|
||||||
|
_runtimes: [],
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.updateRegistration();
|
||||||
|
Services.prefs.addObserver(this.ALLOWED_PREF, this, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this._updateRuntimes = this._updateRuntimes.bind(this);
|
||||||
|
discovery.on("devtools-device-added", this._updateRuntimes);
|
||||||
|
discovery.on("devtools-device-updated", this._updateRuntimes);
|
||||||
|
discovery.on("devtools-device-removed", this._updateRuntimes);
|
||||||
|
this._updateRuntimes();
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
discovery.off("devtools-device-added", this._updateRuntimes);
|
||||||
|
discovery.off("devtools-device-updated", this._updateRuntimes);
|
||||||
|
discovery.off("devtools-device-removed", this._updateRuntimes);
|
||||||
|
},
|
||||||
|
|
||||||
|
_emitUpdated() {
|
||||||
|
this.emit("runtime-list-updated");
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateRuntimes() {
|
||||||
|
this._runtimes = [];
|
||||||
|
for (let device of discovery.getRemoteDevicesWithService("devtools")) {
|
||||||
|
this._runtimes.push(new WiFiRuntime(device));
|
||||||
|
}
|
||||||
|
this._emitUpdated();
|
||||||
|
},
|
||||||
|
|
||||||
|
scan() {
|
||||||
|
discovery.scan();
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
listRuntimes: function () {
|
||||||
|
return this._runtimes;
|
||||||
|
},
|
||||||
|
|
||||||
|
ALLOWED_PREF: "devtools.remote.wifi.scan",
|
||||||
|
|
||||||
|
get allowed() {
|
||||||
|
return Services.prefs.getBoolPref(this.ALLOWED_PREF);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRegistration() {
|
||||||
|
if (this.allowed) {
|
||||||
|
RuntimeScanners.add(WiFiScanner);
|
||||||
|
} else {
|
||||||
|
RuntimeScanners.remove(WiFiScanner);
|
||||||
|
}
|
||||||
|
this._emitUpdated();
|
||||||
|
},
|
||||||
|
|
||||||
|
observe(subject, topic, data) {
|
||||||
|
if (data !== WiFiScanner.ALLOWED_PREF) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WiFiScanner.updateRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(WiFiScanner);
|
||||||
|
WiFiScanner.init();
|
||||||
|
|
||||||
|
exports.WiFiScanner = WiFiScanner;
|
||||||
|
|
||||||
|
var StaticScanner = {
|
||||||
|
enable() {},
|
||||||
|
disable() {},
|
||||||
|
scan() { return promise.resolve(); },
|
||||||
|
listRuntimes() {
|
||||||
|
let runtimes = [gRemoteRuntime];
|
||||||
|
if (Services.prefs.getBoolPref("devtools.webide.enableLocalRuntime")) {
|
||||||
|
runtimes.push(gLocalRuntime);
|
||||||
|
}
|
||||||
|
return runtimes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(StaticScanner);
|
||||||
|
RuntimeScanners.add(StaticScanner);
|
||||||
|
|
||||||
|
/* RUNTIMES */
|
||||||
|
|
||||||
|
// These type strings are used for logging events to Telemetry.
|
||||||
|
// You must update Histograms.json if new types are added.
|
||||||
|
var RuntimeTypes = exports.RuntimeTypes = {
|
||||||
|
USB: "USB",
|
||||||
|
WIFI: "WIFI",
|
||||||
|
SIMULATOR: "SIMULATOR",
|
||||||
|
REMOTE: "REMOTE",
|
||||||
|
LOCAL: "LOCAL",
|
||||||
|
OTHER: "OTHER"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove this comaptibility layer in the future (bug 1085393)
|
||||||
|
* This runtime exists to support the ADB Helper add-on below version 0.7.0.
|
||||||
|
*
|
||||||
|
* This runtime assumes it is connecting to a Firefox OS device.
|
||||||
|
*/
|
||||||
|
function DeprecatedUSBRuntime(id) {
|
||||||
|
this._id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeprecatedUSBRuntime.prototype = {
|
||||||
|
type: RuntimeTypes.USB,
|
||||||
|
get device() {
|
||||||
|
return Devices.getByName(this._id);
|
||||||
|
},
|
||||||
|
connect: function (connection) {
|
||||||
|
if (!this.device) {
|
||||||
|
return promise.reject(new Error("Can't find device: " + this.name));
|
||||||
|
}
|
||||||
|
return this.device.connect().then((port) => {
|
||||||
|
connection.host = "localhost";
|
||||||
|
connection.port = port;
|
||||||
|
connection.connect();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return this._productModel || this._id;
|
||||||
|
},
|
||||||
|
updateNameFromADB: function () {
|
||||||
|
if (this._productModel) {
|
||||||
|
return promise.reject();
|
||||||
|
}
|
||||||
|
let deferred = promise.defer();
|
||||||
|
if (this.device && this.device.shell) {
|
||||||
|
this.device.shell("getprop ro.product.model").then(stdout => {
|
||||||
|
this._productModel = stdout;
|
||||||
|
deferred.resolve();
|
||||||
|
}, () => {});
|
||||||
|
} else {
|
||||||
|
this._productModel = null;
|
||||||
|
deferred.reject();
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// For testing use only
|
||||||
|
exports._DeprecatedUSBRuntime = DeprecatedUSBRuntime;
|
||||||
|
|
||||||
|
function WiFiRuntime(deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiRuntime.prototype = {
|
||||||
|
type: RuntimeTypes.WIFI,
|
||||||
|
// Mark runtime as taking a long time to connect
|
||||||
|
prolongedConnection: true,
|
||||||
|
connect: function (connection) {
|
||||||
|
let service = discovery.getRemoteService("devtools", this.deviceName);
|
||||||
|
if (!service) {
|
||||||
|
return promise.reject(new Error("Can't find device: " + this.name));
|
||||||
|
}
|
||||||
|
connection.advertisement = service;
|
||||||
|
connection.authenticator.sendOOB = this.sendOOB;
|
||||||
|
// Disable the default connection timeout, since QR scanning can take an
|
||||||
|
// unknown amount of time. This prevents spurious errors (even after
|
||||||
|
// eventual success) from being shown.
|
||||||
|
connection.timeoutDelay = 0;
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
get id() {
|
||||||
|
return this.deviceName;
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return this.deviceName;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During OOB_CERT authentication, a notification dialog like this is used to
|
||||||
|
* to display a token which the user must transfer through some mechanism to the
|
||||||
|
* server to authenticate the devices.
|
||||||
|
*
|
||||||
|
* This implementation presents the token as text for the user to transfer
|
||||||
|
* manually. For a mobile device, you should override this implementation with
|
||||||
|
* something more convenient, such as displaying a QR code.
|
||||||
|
*
|
||||||
|
* This method receives an object containing:
|
||||||
|
* @param host string
|
||||||
|
* The host name or IP address of the debugger server.
|
||||||
|
* @param port number
|
||||||
|
* The port number of the debugger server.
|
||||||
|
* @param cert object (optional)
|
||||||
|
* The server's cert details.
|
||||||
|
* @param authResult AuthenticationResult
|
||||||
|
* Authentication result sent from the server.
|
||||||
|
* @param oob object (optional)
|
||||||
|
* The token data to be transferred during OOB_CERT step 8:
|
||||||
|
* * sha256: hash(ClientCert)
|
||||||
|
* * k : K(random 128-bit number)
|
||||||
|
* @return object containing:
|
||||||
|
* * close: Function to hide the notification
|
||||||
|
*/
|
||||||
|
sendOOB(session) {
|
||||||
|
const WINDOW_ID = "devtools:wifi-auth";
|
||||||
|
let { authResult } = session;
|
||||||
|
// Only show in the PENDING state
|
||||||
|
if (authResult != AuthenticationResult.PENDING) {
|
||||||
|
throw new Error("Expected PENDING result, got " + authResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for the window our prompt opens, so we can close it programatically
|
||||||
|
let promptWindow;
|
||||||
|
let windowListener = {
|
||||||
|
onOpenWindow(xulWindow) {
|
||||||
|
let win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindow);
|
||||||
|
win.addEventListener("load", function listener() {
|
||||||
|
win.removeEventListener("load", listener, false);
|
||||||
|
if (win.document.documentElement.getAttribute("id") != WINDOW_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Found the window
|
||||||
|
promptWindow = win;
|
||||||
|
Services.wm.removeListener(windowListener);
|
||||||
|
}, false);
|
||||||
|
},
|
||||||
|
onCloseWindow() {},
|
||||||
|
onWindowTitleChange() {}
|
||||||
|
};
|
||||||
|
Services.wm.addListener(windowListener);
|
||||||
|
|
||||||
|
// |openDialog| is typically a blocking API, so |executeSoon| to get around this
|
||||||
|
DevToolsUtils.executeSoon(() => {
|
||||||
|
// Height determines the size of the QR code. Force a minimum size to
|
||||||
|
// improve scanability.
|
||||||
|
const MIN_HEIGHT = 600;
|
||||||
|
let win = Services.wm.getMostRecentWindow("devtools:webide");
|
||||||
|
let width = win.outerWidth * 0.8;
|
||||||
|
let height = Math.max(win.outerHeight * 0.5, MIN_HEIGHT);
|
||||||
|
win.openDialog("chrome://webide/content/wifi-auth.xhtml",
|
||||||
|
WINDOW_ID,
|
||||||
|
"modal=yes,width=" + width + ",height=" + height, session);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
close() {
|
||||||
|
if (!promptWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promptWindow.close();
|
||||||
|
promptWindow = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For testing use only
|
||||||
|
exports._WiFiRuntime = WiFiRuntime;
|
||||||
|
|
||||||
|
function SimulatorRuntime(simulator) {
|
||||||
|
this.simulator = simulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimulatorRuntime.prototype = {
|
||||||
|
type: RuntimeTypes.SIMULATOR,
|
||||||
|
connect: function (connection) {
|
||||||
|
return this.simulator.launch().then(port => {
|
||||||
|
connection.host = "localhost";
|
||||||
|
connection.port = port;
|
||||||
|
connection.keepConnecting = true;
|
||||||
|
connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
|
||||||
|
connection.connect();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
configure() {
|
||||||
|
Simulators.emit("configure", this.simulator);
|
||||||
|
},
|
||||||
|
get id() {
|
||||||
|
return this.simulator.id;
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return this.simulator.name;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// For testing use only
|
||||||
|
exports._SimulatorRuntime = SimulatorRuntime;
|
||||||
|
|
||||||
|
var gLocalRuntime = {
|
||||||
|
type: RuntimeTypes.LOCAL,
|
||||||
|
connect: function (connection) {
|
||||||
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init();
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
DebuggerServer.allowChromeProcess = true;
|
||||||
|
connection.host = null; // Force Pipe transport
|
||||||
|
connection.port = null;
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
get id() {
|
||||||
|
return "local";
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return Strings.GetStringFromName("local_runtime");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// For testing use only
|
||||||
|
exports._gLocalRuntime = gLocalRuntime;
|
||||||
|
|
||||||
|
var gRemoteRuntime = {
|
||||||
|
type: RuntimeTypes.REMOTE,
|
||||||
|
connect: function (connection) {
|
||||||
|
let win = Services.wm.getMostRecentWindow("devtools:webide");
|
||||||
|
if (!win) {
|
||||||
|
return promise.reject(new Error("No WebIDE window found"));
|
||||||
|
}
|
||||||
|
let ret = {value: connection.host + ":" + connection.port};
|
||||||
|
let title = Strings.GetStringFromName("remote_runtime_promptTitle");
|
||||||
|
let message = Strings.GetStringFromName("remote_runtime_promptMessage");
|
||||||
|
let ok = Services.prompt.prompt(win, title, message, ret, null, {});
|
||||||
|
let [host, port] = ret.value.split(":");
|
||||||
|
if (!ok) {
|
||||||
|
return promise.reject({canceled: true});
|
||||||
|
}
|
||||||
|
if (!host || !port) {
|
||||||
|
return promise.reject(new Error("Invalid host or port"));
|
||||||
|
}
|
||||||
|
connection.host = host;
|
||||||
|
connection.port = port;
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return Strings.GetStringFromName("remote_runtime");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// For testing use only
|
||||||
|
exports._gRemoteRuntime = gRemoteRuntime;
|
||||||
325
devtools/client/webide/modules/simulator-process.js
Normal file
325
devtools/client/webide/modules/simulator-process.js
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
/* 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 { Cc, Ci, Cu } = require("chrome");
|
||||||
|
|
||||||
|
const Environment = require("sdk/system/environment").env;
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const promise = require("promise");
|
||||||
|
const Subprocess = require("sdk/system/child_process/subprocess");
|
||||||
|
const Services = require("Services");
|
||||||
|
|
||||||
|
loader.lazyGetter(this, "OS", () => {
|
||||||
|
const Runtime = require("sdk/system/runtime");
|
||||||
|
switch (Runtime.OS) {
|
||||||
|
case "Darwin":
|
||||||
|
return "mac64";
|
||||||
|
case "Linux":
|
||||||
|
if (Runtime.XPCOMABI.indexOf("x86_64") === 0) {
|
||||||
|
return "linux64";
|
||||||
|
} else {
|
||||||
|
return "linux32";
|
||||||
|
}
|
||||||
|
case "WINNT":
|
||||||
|
return "win32";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function SimulatorProcess() {}
|
||||||
|
SimulatorProcess.prototype = {
|
||||||
|
|
||||||
|
// Check if B2G is running.
|
||||||
|
get isRunning() {
|
||||||
|
return !!this.process;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Start the process and connect the debugger client.
|
||||||
|
run() {
|
||||||
|
|
||||||
|
// Resolve B2G binary.
|
||||||
|
let b2g = this.b2gBinary;
|
||||||
|
if (!b2g || !b2g.exists()) {
|
||||||
|
throw Error("B2G executable not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Gaia profile exists.
|
||||||
|
let gaia = this.gaiaProfile;
|
||||||
|
if (!gaia || !gaia.exists()) {
|
||||||
|
throw Error("Gaia profile directory not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.once("stdout", function () {
|
||||||
|
if (OS == "mac64") {
|
||||||
|
console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
|
||||||
|
// Escape double quotes and escape characters for use in AppleScript.
|
||||||
|
let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
|
||||||
|
|
||||||
|
Subprocess.call({
|
||||||
|
command: "/usr/bin/osascript",
|
||||||
|
arguments: ["-e", 'tell application "' + path + '" to activate'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let logHandler = (e, data) => this.log(e, data.trim());
|
||||||
|
this.on("stdout", logHandler);
|
||||||
|
this.on("stderr", logHandler);
|
||||||
|
this.once("exit", () => {
|
||||||
|
this.off("stdout", logHandler);
|
||||||
|
this.off("stderr", logHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
let environment;
|
||||||
|
if (OS.indexOf("linux") > -1) {
|
||||||
|
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
|
||||||
|
["DISPLAY", "XAUTHORITY"].forEach(key => {
|
||||||
|
if (key in Environment) {
|
||||||
|
environment.push(key + "=" + Environment[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn a B2G instance.
|
||||||
|
this.process = Subprocess.call({
|
||||||
|
command: b2g,
|
||||||
|
arguments: this.args,
|
||||||
|
environment: environment,
|
||||||
|
stdout: data => this.emit("stdout", data),
|
||||||
|
stderr: data => this.emit("stderr", data),
|
||||||
|
// On B2G instance exit, reset tracked process, remote debugger port and
|
||||||
|
// shuttingDown flag, then finally emit an exit event.
|
||||||
|
done: result => {
|
||||||
|
console.log("B2G terminated with " + result.exitCode);
|
||||||
|
this.process = null;
|
||||||
|
this.emit("exit", result.exitCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Request a B2G instance kill.
|
||||||
|
kill() {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
if (this.process) {
|
||||||
|
this.once("exit", (e, exitCode) => {
|
||||||
|
this.shuttingDown = false;
|
||||||
|
deferred.resolve(exitCode);
|
||||||
|
});
|
||||||
|
if (!this.shuttingDown) {
|
||||||
|
this.shuttingDown = true;
|
||||||
|
this.emit("kill", null);
|
||||||
|
this.process.kill();
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
} else {
|
||||||
|
return promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Maybe log output messages.
|
||||||
|
log(level, message) {
|
||||||
|
if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (level === "stderr" || level === "error") {
|
||||||
|
console.error(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Compute B2G CLI arguments.
|
||||||
|
get args() {
|
||||||
|
let args = [];
|
||||||
|
|
||||||
|
// Gaia profile.
|
||||||
|
args.push("-profile", this.gaiaProfile.path);
|
||||||
|
|
||||||
|
// Debugger server port.
|
||||||
|
let port = parseInt(this.options.port);
|
||||||
|
args.push("-start-debugger-server", "" + port);
|
||||||
|
|
||||||
|
// Screen size.
|
||||||
|
let width = parseInt(this.options.width);
|
||||||
|
let height = parseInt(this.options.height);
|
||||||
|
if (width && height) {
|
||||||
|
args.push("-screen", width + "x" + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore eventual zombie instances of b2g that are left over.
|
||||||
|
args.push("-no-remote");
|
||||||
|
|
||||||
|
// If we are running a simulator based on Mulet,
|
||||||
|
// we have to override the default chrome URL
|
||||||
|
// in order to prevent the Browser UI to appear.
|
||||||
|
if (this.b2gBinary.leafName.includes("firefox")) {
|
||||||
|
args.push("-chrome", "chrome://b2g/content/shell.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.decorate(SimulatorProcess.prototype);
|
||||||
|
|
||||||
|
|
||||||
|
function CustomSimulatorProcess(options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
var CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
|
||||||
|
|
||||||
|
// Compute B2G binary file handle.
|
||||||
|
Object.defineProperty(CSPp, "b2gBinary", {
|
||||||
|
get: function () {
|
||||||
|
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||||
|
file.initWithPath(this.options.b2gBinary);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compute Gaia profile file handle.
|
||||||
|
Object.defineProperty(CSPp, "gaiaProfile", {
|
||||||
|
get: function () {
|
||||||
|
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||||
|
file.initWithPath(this.options.gaiaProfile);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.CustomSimulatorProcess = CustomSimulatorProcess;
|
||||||
|
|
||||||
|
|
||||||
|
function AddonSimulatorProcess(addon, options) {
|
||||||
|
this.addon = addon;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
|
||||||
|
|
||||||
|
// Compute B2G binary file handle.
|
||||||
|
Object.defineProperty(ASPp, "b2gBinary", {
|
||||||
|
get: function () {
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
let pref = "extensions." + this.addon.id + ".customRuntime";
|
||||||
|
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
file.append("b2g");
|
||||||
|
let binaries = {
|
||||||
|
win32: "b2g-bin.exe",
|
||||||
|
mac64: "B2G.app/Contents/MacOS/b2g-bin",
|
||||||
|
linux32: "b2g-bin",
|
||||||
|
linux64: "b2g-bin",
|
||||||
|
};
|
||||||
|
binaries[OS].split("/").forEach(node => file.append(node));
|
||||||
|
}
|
||||||
|
// If the binary doesn't exists, it may be because of a simulator
|
||||||
|
// based on mulet, which has a different binary name.
|
||||||
|
if (!file.exists()) {
|
||||||
|
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
file.append("firefox");
|
||||||
|
let binaries = {
|
||||||
|
win32: "firefox.exe",
|
||||||
|
mac64: "FirefoxNightly.app/Contents/MacOS/firefox-bin",
|
||||||
|
linux32: "firefox-bin",
|
||||||
|
linux64: "firefox-bin",
|
||||||
|
};
|
||||||
|
binaries[OS].split("/").forEach(node => file.append(node));
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compute Gaia profile file handle.
|
||||||
|
Object.defineProperty(ASPp, "gaiaProfile", {
|
||||||
|
get: function () {
|
||||||
|
let file;
|
||||||
|
|
||||||
|
// Custom profile from simulator configuration.
|
||||||
|
if (this.options.gaiaProfile) {
|
||||||
|
file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||||
|
file.initWithPath(this.options.gaiaProfile);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom profile from addon prefs.
|
||||||
|
try {
|
||||||
|
let pref = "extensions." + this.addon.id + ".gaiaProfile";
|
||||||
|
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
|
||||||
|
return file;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Default profile from addon.
|
||||||
|
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
file.append("profile");
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.AddonSimulatorProcess = AddonSimulatorProcess;
|
||||||
|
|
||||||
|
|
||||||
|
function OldAddonSimulatorProcess(addon, options) {
|
||||||
|
this.addon = addon;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
var OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
|
||||||
|
|
||||||
|
// Compute B2G binary file handle.
|
||||||
|
Object.defineProperty(OASPp, "b2gBinary", {
|
||||||
|
get: function () {
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
let pref = "extensions." + this.addon.id + ".customRuntime";
|
||||||
|
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
|
||||||
|
file.append("resources");
|
||||||
|
file.append("fxos_" + version + "_simulator");
|
||||||
|
file.append("data");
|
||||||
|
file.append(OS == "linux32" ? "linux" : OS);
|
||||||
|
let binaries = {
|
||||||
|
win32: "b2g/b2g-bin.exe",
|
||||||
|
mac64: "B2G.app/Contents/MacOS/b2g-bin",
|
||||||
|
linux32: "b2g/b2g-bin",
|
||||||
|
linux64: "b2g/b2g-bin",
|
||||||
|
};
|
||||||
|
binaries[OS].split("/").forEach(node => file.append(node));
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compute B2G CLI arguments.
|
||||||
|
Object.defineProperty(OASPp, "args", {
|
||||||
|
get: function () {
|
||||||
|
let args = [];
|
||||||
|
|
||||||
|
// Gaia profile.
|
||||||
|
args.push("-profile", this.gaiaProfile.path);
|
||||||
|
|
||||||
|
// Debugger server port.
|
||||||
|
let port = parseInt(this.options.port);
|
||||||
|
args.push("-dbgport", "" + port);
|
||||||
|
|
||||||
|
// Ignore eventual zombie instances of b2g that are left over.
|
||||||
|
args.push("-no-remote");
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;
|
||||||
368
devtools/client/webide/modules/simulators.js
Normal file
368
devtools/client/webide/modules/simulators.js
Normal file
|
|
@ -0,0 +1,368 @@
|
||||||
|
/* 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 { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
|
||||||
|
const { Task } = require("devtools/shared/task");
|
||||||
|
loader.lazyRequireGetter(this, "ConnectionManager", "devtools/shared/client/connection-manager", true);
|
||||||
|
loader.lazyRequireGetter(this, "AddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
|
||||||
|
loader.lazyRequireGetter(this, "OldAddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
|
||||||
|
loader.lazyRequireGetter(this, "CustomSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
|
||||||
|
const asyncStorage = require("devtools/shared/async-storage");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const promise = require("promise");
|
||||||
|
const Services = require("Services");
|
||||||
|
|
||||||
|
const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
|
||||||
|
const LocaleCompare = (a, b) => {
|
||||||
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
var Simulators = {
|
||||||
|
|
||||||
|
// The list of simulator configurations.
|
||||||
|
_simulators: [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a previously saved list of configurations (only once).
|
||||||
|
*
|
||||||
|
* @return Promise.
|
||||||
|
*/
|
||||||
|
_load() {
|
||||||
|
if (this._loadingPromise) {
|
||||||
|
return this._loadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadingPromise = Task.spawn(function* () {
|
||||||
|
let jobs = [];
|
||||||
|
|
||||||
|
let value = yield asyncStorage.getItem("simulators");
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(options => {
|
||||||
|
let simulator = new Simulator(options);
|
||||||
|
Simulators.add(simulator, true);
|
||||||
|
|
||||||
|
// If the simulator had a reference to an addon, fix it.
|
||||||
|
if (options.addonID) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
AddonManager.getAddonByID(options.addonID, addon => {
|
||||||
|
simulator.addon = addon;
|
||||||
|
delete simulator.options.addonID;
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
jobs.push(deferred.promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
yield promise.all(jobs);
|
||||||
|
yield Simulators._addUnusedAddons();
|
||||||
|
Simulators.emitUpdated();
|
||||||
|
return Simulators._simulators;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._loadingPromise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add default simulators to the list for each new (unused) addon.
|
||||||
|
*
|
||||||
|
* @return Promise.
|
||||||
|
*/
|
||||||
|
_addUnusedAddons: Task.async(function* () {
|
||||||
|
let jobs = [];
|
||||||
|
|
||||||
|
let addons = yield Simulators.findSimulatorAddons();
|
||||||
|
addons.forEach(addon => {
|
||||||
|
jobs.push(Simulators.addIfUnusedAddon(addon, true));
|
||||||
|
});
|
||||||
|
|
||||||
|
yield promise.all(jobs);
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current list of configurations.
|
||||||
|
*
|
||||||
|
* @return Promise.
|
||||||
|
*/
|
||||||
|
_save: Task.async(function* () {
|
||||||
|
yield this._load();
|
||||||
|
|
||||||
|
let value = Simulators._simulators.map(simulator => {
|
||||||
|
let options = JSON.parse(JSON.stringify(simulator.options));
|
||||||
|
if (simulator.addon != null) {
|
||||||
|
options.addonID = simulator.addon.id;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
yield asyncStorage.setItem("simulators", value);
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all available simulators.
|
||||||
|
*
|
||||||
|
* @return Promised simulator list.
|
||||||
|
*/
|
||||||
|
findSimulators: Task.async(function* () {
|
||||||
|
yield this._load();
|
||||||
|
return Simulators._simulators;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all installed simulator addons.
|
||||||
|
*
|
||||||
|
* @return Promised addon list.
|
||||||
|
*/
|
||||||
|
findSimulatorAddons() {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
AddonManager.getAllAddons(all => {
|
||||||
|
let addons = [];
|
||||||
|
for (let addon of all) {
|
||||||
|
if (Simulators.isSimulatorAddon(addon)) {
|
||||||
|
addons.push(addon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort simulator addons by name.
|
||||||
|
addons.sort(LocaleCompare);
|
||||||
|
deferred.resolve(addons);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new simulator for `addon` if no other simulator uses it.
|
||||||
|
*/
|
||||||
|
addIfUnusedAddon(addon, silently = false) {
|
||||||
|
let simulators = this._simulators;
|
||||||
|
let matching = simulators.filter(s => s.addon && s.addon.id == addon.id);
|
||||||
|
if (matching.length > 0) {
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
let options = {};
|
||||||
|
options.name = addon.name.replace(" Simulator", "");
|
||||||
|
// Some addons specify a simulator type at the end of their version string,
|
||||||
|
// e.g. "2_5_tv".
|
||||||
|
let type = this.simulatorAddonVersion(addon).split("_")[2];
|
||||||
|
if (type) {
|
||||||
|
// "tv" is shorthand for type "television".
|
||||||
|
options.type = (type === "tv" ? "television" : type);
|
||||||
|
}
|
||||||
|
return this.add(new Simulator(options, addon), silently);
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO (Bug 1146521) Maybe find a better way to deal with removed addons?
|
||||||
|
removeIfUsingAddon(addon) {
|
||||||
|
let simulators = this._simulators;
|
||||||
|
let remaining = simulators.filter(s => !s.addon || s.addon.id != addon.id);
|
||||||
|
this._simulators = remaining;
|
||||||
|
if (remaining.length !== simulators.length) {
|
||||||
|
this.emitUpdated();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new simulator to the list. Caution: `simulator.name` may be modified.
|
||||||
|
*
|
||||||
|
* @return Promise to added simulator.
|
||||||
|
*/
|
||||||
|
add(simulator, silently = false) {
|
||||||
|
let simulators = this._simulators;
|
||||||
|
let uniqueName = this.uniqueName(simulator.options.name);
|
||||||
|
simulator.options.name = uniqueName;
|
||||||
|
simulators.push(simulator);
|
||||||
|
if (!silently) {
|
||||||
|
this.emitUpdated();
|
||||||
|
}
|
||||||
|
return promise.resolve(simulator);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a simulator from the list.
|
||||||
|
*/
|
||||||
|
remove(simulator) {
|
||||||
|
let simulators = this._simulators;
|
||||||
|
let remaining = simulators.filter(s => s !== simulator);
|
||||||
|
this._simulators = remaining;
|
||||||
|
if (remaining.length !== simulators.length) {
|
||||||
|
this.emitUpdated();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a unique name for a simulator (may add a suffix, e.g. "MyName (1)").
|
||||||
|
*/
|
||||||
|
uniqueName(name) {
|
||||||
|
let simulators = this._simulators;
|
||||||
|
|
||||||
|
let names = {};
|
||||||
|
simulators.forEach(simulator => names[simulator.name] = true);
|
||||||
|
|
||||||
|
// Strip any previous suffix, add a new suffix if necessary.
|
||||||
|
let stripped = name.replace(/ \(\d+\)$/, "");
|
||||||
|
let unique = stripped;
|
||||||
|
for (let i = 1; names[unique]; i++) {
|
||||||
|
unique = stripped + " (" + i + ")";
|
||||||
|
}
|
||||||
|
return unique;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare an addon's ID against the expected form of a simulator addon ID,
|
||||||
|
* and try to extract its version if there is a match.
|
||||||
|
*
|
||||||
|
* Note: If a simulator addon is recognized, but no version can be extracted
|
||||||
|
* (e.g. custom RegExp pref value), we return "Unknown" to keep the returned
|
||||||
|
* value 'truthy'.
|
||||||
|
*/
|
||||||
|
simulatorAddonVersion(addon) {
|
||||||
|
let match = SimulatorRegExp.exec(addon.id);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let version = match[1];
|
||||||
|
return version || "Unknown";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect simulator addons, including "unofficial" ones.
|
||||||
|
*/
|
||||||
|
isSimulatorAddon(addon) {
|
||||||
|
return !!this.simulatorAddonVersion(addon);
|
||||||
|
},
|
||||||
|
|
||||||
|
emitUpdated() {
|
||||||
|
this.emit("updated", { length: this._simulators.length });
|
||||||
|
this._simulators.sort(LocaleCompare);
|
||||||
|
this._save();
|
||||||
|
},
|
||||||
|
|
||||||
|
onConfigure(e, simulator) {
|
||||||
|
this._lastConfiguredSimulator = simulator;
|
||||||
|
},
|
||||||
|
|
||||||
|
onInstalled(addon) {
|
||||||
|
if (this.isSimulatorAddon(addon)) {
|
||||||
|
this.addIfUnusedAddon(addon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnabled(addon) {
|
||||||
|
if (this.isSimulatorAddon(addon)) {
|
||||||
|
this.addIfUnusedAddon(addon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisabled(addon) {
|
||||||
|
if (this.isSimulatorAddon(addon)) {
|
||||||
|
this.removeIfUsingAddon(addon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUninstalled(addon) {
|
||||||
|
if (this.isSimulatorAddon(addon)) {
|
||||||
|
this.removeIfUsingAddon(addon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
exports.Simulators = Simulators;
|
||||||
|
AddonManager.addAddonListener(Simulators);
|
||||||
|
EventEmitter.decorate(Simulators);
|
||||||
|
Simulators.on("configure", Simulators.onConfigure.bind(Simulators));
|
||||||
|
|
||||||
|
function Simulator(options = {}, addon = null) {
|
||||||
|
this.addon = addon;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
// Fill `this.options` with default values where needed.
|
||||||
|
let defaults = this.defaults;
|
||||||
|
for (let option in defaults) {
|
||||||
|
if (this.options[option] == null) {
|
||||||
|
this.options[option] = defaults[option];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Simulator.prototype = {
|
||||||
|
|
||||||
|
// Default simulation options.
|
||||||
|
_defaults: {
|
||||||
|
// Based on the Firefox OS Flame.
|
||||||
|
phone: {
|
||||||
|
width: 320,
|
||||||
|
height: 570,
|
||||||
|
pixelRatio: 1.5
|
||||||
|
},
|
||||||
|
// Based on a 720p HD TV.
|
||||||
|
television: {
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
pixelRatio: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_defaultType: "phone",
|
||||||
|
|
||||||
|
restoreDefaults() {
|
||||||
|
let defaults = this.defaults;
|
||||||
|
let options = this.options;
|
||||||
|
for (let option in defaults) {
|
||||||
|
options[option] = defaults[option];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
launch() {
|
||||||
|
// Close already opened simulation.
|
||||||
|
if (this.process) {
|
||||||
|
return this.kill().then(this.launch.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options.port = ConnectionManager.getFreeTCPPort();
|
||||||
|
|
||||||
|
// Choose simulator process type.
|
||||||
|
if (this.options.b2gBinary) {
|
||||||
|
// Custom binary.
|
||||||
|
this.process = new CustomSimulatorProcess(this.options);
|
||||||
|
} else if (this.version > "1.3") {
|
||||||
|
// Recent simulator addon.
|
||||||
|
this.process = new AddonSimulatorProcess(this.addon, this.options);
|
||||||
|
} else {
|
||||||
|
// Old simulator addon.
|
||||||
|
this.process = new OldAddonSimulatorProcess(this.addon, this.options);
|
||||||
|
}
|
||||||
|
this.process.run();
|
||||||
|
|
||||||
|
return promise.resolve(this.options.port);
|
||||||
|
},
|
||||||
|
|
||||||
|
kill() {
|
||||||
|
let process = this.process;
|
||||||
|
if (!process) {
|
||||||
|
return promise.resolve();
|
||||||
|
}
|
||||||
|
this.process = null;
|
||||||
|
return process.kill();
|
||||||
|
},
|
||||||
|
|
||||||
|
get defaults() {
|
||||||
|
let defaults = this._defaults;
|
||||||
|
return defaults[this.type] || defaults[this._defaultType];
|
||||||
|
},
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.options.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.options.type || this._defaultType;
|
||||||
|
},
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this.options.b2gBinary ? "Custom" : this.addon.name.match(/\d+\.\d+/)[0];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
exports.Simulator = Simulator;
|
||||||
178
devtools/client/webide/modules/tab-store.js
Normal file
178
devtools/client/webide/modules/tab-store.js
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
/* 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 { TargetFactory } = require("devtools/client/framework/target");
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
const { Connection } = require("devtools/shared/client/connection-manager");
|
||||||
|
const promise = require("promise");
|
||||||
|
const { Task } = require("devtools/shared/task");
|
||||||
|
|
||||||
|
const _knownTabStores = new WeakMap();
|
||||||
|
|
||||||
|
var TabStore;
|
||||||
|
|
||||||
|
module.exports = TabStore = function (connection) {
|
||||||
|
// If we already know about this connection,
|
||||||
|
// let's re-use the existing store.
|
||||||
|
if (_knownTabStores.has(connection)) {
|
||||||
|
return _knownTabStores.get(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
_knownTabStores.set(connection, this);
|
||||||
|
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
|
||||||
|
this._resetStore();
|
||||||
|
|
||||||
|
this.destroy = this.destroy.bind(this);
|
||||||
|
this._onStatusChanged = this._onStatusChanged.bind(this);
|
||||||
|
|
||||||
|
this._connection = connection;
|
||||||
|
this._connection.once(Connection.Events.DESTROYED, this.destroy);
|
||||||
|
this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged);
|
||||||
|
this._onTabListChanged = this._onTabListChanged.bind(this);
|
||||||
|
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||||
|
this._onStatusChanged();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
TabStore.prototype = {
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
if (this._connection) {
|
||||||
|
// While this.destroy is bound using .once() above, that event may not
|
||||||
|
// have occurred when the TabStore client calls destroy, so we
|
||||||
|
// manually remove it here.
|
||||||
|
this._connection.off(Connection.Events.DESTROYED, this.destroy);
|
||||||
|
this._connection.off(Connection.Events.STATUS_CHANGED, this._onStatusChanged);
|
||||||
|
_knownTabStores.delete(this._connection);
|
||||||
|
this._connection = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_resetStore: function () {
|
||||||
|
this.response = null;
|
||||||
|
this.tabs = [];
|
||||||
|
this._selectedTab = null;
|
||||||
|
this._selectedTabTargetPromise = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onStatusChanged: function () {
|
||||||
|
if (this._connection.status == Connection.Status.CONNECTED) {
|
||||||
|
// Watch for changes to remote browser tabs
|
||||||
|
this._connection.client.addListener("tabListChanged",
|
||||||
|
this._onTabListChanged);
|
||||||
|
this._connection.client.addListener("tabNavigated",
|
||||||
|
this._onTabNavigated);
|
||||||
|
this.listTabs();
|
||||||
|
} else {
|
||||||
|
if (this._connection.client) {
|
||||||
|
this._connection.client.removeListener("tabListChanged",
|
||||||
|
this._onTabListChanged);
|
||||||
|
this._connection.client.removeListener("tabNavigated",
|
||||||
|
this._onTabNavigated);
|
||||||
|
}
|
||||||
|
this._resetStore();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onTabListChanged: function () {
|
||||||
|
this.listTabs().then(() => this.emit("tab-list"))
|
||||||
|
.catch(console.error);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onTabNavigated: function (e, { from, title, url }) {
|
||||||
|
if (!this._selectedTab || from !== this._selectedTab.actor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selectedTab.url = url;
|
||||||
|
this._selectedTab.title = title;
|
||||||
|
this.emit("navigate");
|
||||||
|
},
|
||||||
|
|
||||||
|
listTabs: function () {
|
||||||
|
if (!this._connection || !this._connection.client) {
|
||||||
|
return promise.reject(new Error("Can't listTabs, not connected."));
|
||||||
|
}
|
||||||
|
let deferred = promise.defer();
|
||||||
|
this._connection.client.listTabs(response => {
|
||||||
|
if (response.error) {
|
||||||
|
this._connection.disconnect();
|
||||||
|
deferred.reject(response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tabsChanged = JSON.stringify(this.tabs) !== JSON.stringify(response.tabs);
|
||||||
|
this.response = response;
|
||||||
|
this.tabs = response.tabs;
|
||||||
|
this._checkSelectedTab();
|
||||||
|
if (tabsChanged) {
|
||||||
|
this.emit("tab-list");
|
||||||
|
}
|
||||||
|
deferred.resolve(response);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Tab "selection" should really take place by creating a TabProject
|
||||||
|
// which is the selected project. This should be done as part of the
|
||||||
|
// project-agnostic work.
|
||||||
|
_selectedTab: null,
|
||||||
|
_selectedTabTargetPromise: null,
|
||||||
|
get selectedTab() {
|
||||||
|
return this._selectedTab;
|
||||||
|
},
|
||||||
|
set selectedTab(tab) {
|
||||||
|
if (this._selectedTab === tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selectedTab = tab;
|
||||||
|
this._selectedTabTargetPromise = null;
|
||||||
|
// Attach to the tab to follow navigation events
|
||||||
|
if (this._selectedTab) {
|
||||||
|
this.getTargetForTab();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_checkSelectedTab: function () {
|
||||||
|
if (!this._selectedTab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let alive = this.tabs.some(tab => {
|
||||||
|
return tab.actor === this._selectedTab.actor;
|
||||||
|
});
|
||||||
|
if (!alive) {
|
||||||
|
this._selectedTab = null;
|
||||||
|
this._selectedTabTargetPromise = null;
|
||||||
|
this.emit("closed");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTargetForTab: function () {
|
||||||
|
if (this._selectedTabTargetPromise) {
|
||||||
|
return this._selectedTabTargetPromise;
|
||||||
|
}
|
||||||
|
let store = this;
|
||||||
|
this._selectedTabTargetPromise = Task.spawn(function* () {
|
||||||
|
// If you connect to a tab, then detach from it, the root actor may have
|
||||||
|
// de-listed the actors that belong to the tab. This breaks the toolbox
|
||||||
|
// if you try to connect to the same tab again. To work around this
|
||||||
|
// issue, we force a "listTabs" request before connecting to a tab.
|
||||||
|
yield store.listTabs();
|
||||||
|
return TargetFactory.forRemoteTab({
|
||||||
|
form: store._selectedTab,
|
||||||
|
client: store._connection.client,
|
||||||
|
chrome: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._selectedTabTargetPromise.then(target => {
|
||||||
|
target.once("close", () => {
|
||||||
|
this._selectedTabTargetPromise = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this._selectedTabTargetPromise;
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
68
devtools/client/webide/modules/utils.js
Normal file
68
devtools/client/webide/modules/utils.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
const { Cc, Cu, Ci } = require("chrome");
|
||||||
|
const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||||
|
const Services = require("Services");
|
||||||
|
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||||
|
|
||||||
|
function doesFileExist(location) {
|
||||||
|
let file = new FileUtils.File(location);
|
||||||
|
return file.exists();
|
||||||
|
}
|
||||||
|
exports.doesFileExist = doesFileExist;
|
||||||
|
|
||||||
|
function _getFile(location, ...pickerParams) {
|
||||||
|
if (location) {
|
||||||
|
return new FileUtils.File(location);
|
||||||
|
}
|
||||||
|
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||||
|
fp.init(...pickerParams);
|
||||||
|
let res = fp.show();
|
||||||
|
if (res == Ci.nsIFilePicker.returnCancel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fp.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCustomBinary(window, location) {
|
||||||
|
return _getFile(location, window, Strings.GetStringFromName("selectCustomBinary_title"), Ci.nsIFilePicker.modeOpen);
|
||||||
|
}
|
||||||
|
exports.getCustomBinary = getCustomBinary;
|
||||||
|
|
||||||
|
function getCustomProfile(window, location) {
|
||||||
|
return _getFile(location, window, Strings.GetStringFromName("selectCustomProfile_title"), Ci.nsIFilePicker.modeGetFolder);
|
||||||
|
}
|
||||||
|
exports.getCustomProfile = getCustomProfile;
|
||||||
|
|
||||||
|
function getPackagedDirectory(window, location) {
|
||||||
|
return _getFile(location, window, Strings.GetStringFromName("importPackagedApp_title"), Ci.nsIFilePicker.modeGetFolder);
|
||||||
|
}
|
||||||
|
exports.getPackagedDirectory = getPackagedDirectory;
|
||||||
|
|
||||||
|
function getHostedURL(window, location) {
|
||||||
|
let ret = { value: null };
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
Services.prompt.prompt(window,
|
||||||
|
Strings.GetStringFromName("importHostedApp_title"),
|
||||||
|
Strings.GetStringFromName("importHostedApp_header"),
|
||||||
|
ret, null, {});
|
||||||
|
location = ret.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean location string and add "http://" if missing
|
||||||
|
location = location.trim();
|
||||||
|
try { // Will fail if no scheme
|
||||||
|
Services.io.extractScheme(location);
|
||||||
|
} catch (e) {
|
||||||
|
location = "http://" + location;
|
||||||
|
}
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
exports.getHostedURL = getHostedURL;
|
||||||
23
devtools/client/webide/moz.build
Normal file
23
devtools/client/webide/moz.build
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- 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/.
|
||||||
|
|
||||||
|
DIRS += [
|
||||||
|
'content',
|
||||||
|
'components',
|
||||||
|
'modules',
|
||||||
|
'themes',
|
||||||
|
]
|
||||||
|
|
||||||
|
BROWSER_CHROME_MANIFESTS += [
|
||||||
|
'test/browser.ini'
|
||||||
|
]
|
||||||
|
MOCHITEST_CHROME_MANIFESTS += [
|
||||||
|
'test/chrome.ini'
|
||||||
|
]
|
||||||
|
|
||||||
|
JS_PREFERENCE_PP_FILES += [
|
||||||
|
'webide-prefs.js',
|
||||||
|
]
|
||||||
6
devtools/client/webide/test/.eslintrc.js
Normal file
6
devtools/client/webide/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Extend from the shared list of defined globals for mochitests.
|
||||||
|
"extends": "../../../.eslintrc.mochitests.js"
|
||||||
|
};
|
||||||
BIN
devtools/client/webide/test/addons/adbhelper-linux.xpi
Normal file
BIN
devtools/client/webide/test/addons/adbhelper-linux.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/adbhelper-linux64.xpi
Normal file
BIN
devtools/client/webide/test/addons/adbhelper-linux64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/adbhelper-mac64.xpi
Normal file
BIN
devtools/client/webide/test/addons/adbhelper-mac64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/adbhelper-win32.xpi
Normal file
BIN
devtools/client/webide/test/addons/adbhelper-win32.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxdt-adapters-linux32.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxdt-adapters-linux32.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxdt-adapters-linux64.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxdt-adapters-linux64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxdt-adapters-mac64.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxdt-adapters-mac64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxdt-adapters-win32.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxdt-adapters-win32.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-linux.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-linux.xpi
Normal file
Binary file not shown.
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-mac64.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-mac64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-win32.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_1_0_simulator-win32.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-linux.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-linux.xpi
Normal file
Binary file not shown.
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-mac64.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-mac64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-win32.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_2_0_simulator-win32.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-linux.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-linux.xpi
Normal file
Binary file not shown.
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-mac64.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-mac64.xpi
Normal file
Binary file not shown.
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-win32.xpi
Normal file
BIN
devtools/client/webide/test/addons/fxos_3_0_simulator-win32.xpi
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4
devtools/client/webide/test/addons/simulators.json
Normal file
4
devtools/client/webide/test/addons/simulators.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"stable": ["1.0", "2.0"],
|
||||||
|
"unstable": ["3.0", "3.0_tv"]
|
||||||
|
}
|
||||||
BIN
devtools/client/webide/test/app.zip
Normal file
BIN
devtools/client/webide/test/app.zip
Normal file
Binary file not shown.
6
devtools/client/webide/test/app/index.html
Normal file
6
devtools/client/webide/test/app/index.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head><title></title></head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
devtools/client/webide/test/app/manifest.webapp
Normal file
5
devtools/client/webide/test/app/manifest.webapp
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "A name (in app directory)",
|
||||||
|
"description": "desc",
|
||||||
|
"launch_path": "/index.html"
|
||||||
|
}
|
||||||
12
devtools/client/webide/test/browser.ini
Normal file
12
devtools/client/webide/test/browser.ini
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[DEFAULT]
|
||||||
|
tags = devtools
|
||||||
|
subsuite = devtools
|
||||||
|
support-files =
|
||||||
|
addons/simulators.json
|
||||||
|
doc_tabs.html
|
||||||
|
head.js
|
||||||
|
templates.json
|
||||||
|
|
||||||
|
[browser_tabs.js]
|
||||||
|
skip-if = e10s # Bug 1072167 - browser_tabs.js test fails under e10s
|
||||||
|
[browser_widget.js]
|
||||||
84
devtools/client/webide/test/browser_tabs.js
Normal file
84
devtools/client/webide/test/browser_tabs.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const TEST_URI = "http://example.com/browser/devtools/client/webide/test/doc_tabs.html";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
waitForExplicitFinish();
|
||||||
|
requestCompleteLog();
|
||||||
|
|
||||||
|
Task.spawn(function* () {
|
||||||
|
// Since we test the connections set below, destroy the server in case it
|
||||||
|
// was left open.
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
DebuggerServer.init();
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
|
||||||
|
let tab = yield addTab(TEST_URI);
|
||||||
|
|
||||||
|
let win = yield openWebIDE();
|
||||||
|
let docProject = getProjectDocument(win);
|
||||||
|
let docRuntime = getRuntimeDocument(win);
|
||||||
|
|
||||||
|
yield connectToLocal(win, docRuntime);
|
||||||
|
|
||||||
|
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
|
||||||
|
|
||||||
|
yield selectTabProject(win, docProject);
|
||||||
|
|
||||||
|
ok(win.UI.toolboxPromise, "Toolbox promise exists");
|
||||||
|
yield win.UI.toolboxPromise;
|
||||||
|
|
||||||
|
let project = win.AppManager.selectedProject;
|
||||||
|
is(project.location, TEST_URI, "Location is correct");
|
||||||
|
is(project.name, "example.com: Test Tab", "Name is correct");
|
||||||
|
|
||||||
|
// Ensure tab list changes are noticed
|
||||||
|
let tabsNode = docProject.querySelector("#project-panel-tabs");
|
||||||
|
is(tabsNode.querySelectorAll(".panel-item").length, 2, "2 tabs available");
|
||||||
|
yield removeTab(tab);
|
||||||
|
yield waitForUpdate(win, "project");
|
||||||
|
yield waitForUpdate(win, "runtime-targets");
|
||||||
|
is(tabsNode.querySelectorAll(".panel-item").length, 1, "1 tab available");
|
||||||
|
|
||||||
|
tab = yield addTab(TEST_URI);
|
||||||
|
|
||||||
|
is(tabsNode.querySelectorAll(".panel-item").length, 2, "2 tabs available");
|
||||||
|
|
||||||
|
yield removeTab(tab);
|
||||||
|
|
||||||
|
is(tabsNode.querySelectorAll(".panel-item").length, 2, "2 tabs available");
|
||||||
|
|
||||||
|
docProject.querySelector("#refresh-tabs").click();
|
||||||
|
|
||||||
|
yield waitForUpdate(win, "runtime-targets");
|
||||||
|
|
||||||
|
is(tabsNode.querySelectorAll(".panel-item").length, 1, "1 tab available");
|
||||||
|
|
||||||
|
yield win.Cmds.disconnectRuntime();
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
}).then(finish, handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToLocal(win, docRuntime) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
win.AppManager.connection.once(
|
||||||
|
win.Connection.Events.CONNECTED,
|
||||||
|
() => deferred.resolve());
|
||||||
|
docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTabProject(win, docProject) {
|
||||||
|
return Task.spawn(function* () {
|
||||||
|
yield waitForUpdate(win, "runtime-targets");
|
||||||
|
let tabsNode = docProject.querySelector("#project-panel-tabs");
|
||||||
|
let tabNode = tabsNode.querySelectorAll(".panel-item")[1];
|
||||||
|
let project = waitForUpdate(win, "project");
|
||||||
|
tabNode.click();
|
||||||
|
yield project;
|
||||||
|
});
|
||||||
|
}
|
||||||
15
devtools/client/webide/test/browser_widget.js
Normal file
15
devtools/client/webide/test/browser_widget.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
waitForExplicitFinish();
|
||||||
|
Task.spawn(function* () {
|
||||||
|
let win = yield openWebIDE();
|
||||||
|
ok(document.querySelector("#webide-button"), "Found WebIDE button");
|
||||||
|
Services.prefs.setBoolPref("devtools.webide.widget.enabled", false);
|
||||||
|
ok(!document.querySelector("#webide-button"), "WebIDE button uninstalled");
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
Services.prefs.clearUserPref("devtools.webide.widget.enabled");
|
||||||
|
}).then(finish, handleError);
|
||||||
|
}
|
||||||
5
devtools/client/webide/test/build_app1/package.json
Normal file
5
devtools/client/webide/test/build_app1/package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"webide": {
|
||||||
|
"prepackage": "echo \"{\\\"name\\\":\\\"hello\\\"}\" > manifest.webapp"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue