From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../aboutdebugging/src/actions/debug-targets.js | 356 ++++++++++++++ .../client/aboutdebugging/src/actions/index.js | 12 + .../client/aboutdebugging/src/actions/moz.build | 11 + .../client/aboutdebugging/src/actions/runtimes.js | 515 ++++++++++++++++++++ .../client/aboutdebugging/src/actions/telemetry.js | 23 + devtools/client/aboutdebugging/src/actions/ui.js | 202 ++++++++ devtools/client/aboutdebugging/src/base.css | 521 +++++++++++++++++++++ .../client/aboutdebugging/src/components/App.css | 71 +++ .../client/aboutdebugging/src/components/App.js | 213 +++++++++ .../src/components/CompatibilityWarning.js | 110 +++++ .../src/components/ConnectionPromptSetting.js | 55 +++ .../src/components/ProfilerDialog.css | 63 +++ .../src/components/ProfilerDialog.js | 168 +++++++ .../src/components/RuntimeActions.css | 9 + .../src/components/RuntimeActions.js | 82 ++++ .../aboutdebugging/src/components/RuntimeInfo.css | 42 ++ .../aboutdebugging/src/components/RuntimeInfo.js | 89 ++++ .../aboutdebugging/src/components/RuntimePage.js | 306 ++++++++++++ .../src/components/ServiceWorkersWarning.js | 52 ++ .../src/components/connect/ConnectPage.css | 50 ++ .../src/components/connect/ConnectPage.js | 315 +++++++++++++ .../src/components/connect/ConnectSection.css | 50 ++ .../src/components/connect/ConnectSection.js | 69 +++ .../src/components/connect/ConnectSteps.css | 13 + .../src/components/connect/ConnectSteps.js | 51 ++ .../components/connect/NetworkLocationsForm.css | 23 + .../src/components/connect/NetworkLocationsForm.js | 148 ++++++ .../components/connect/NetworkLocationsList.css | 20 + .../src/components/connect/NetworkLocationsList.js | 67 +++ .../src/components/connect/moz.build | 11 + .../src/components/debugtarget/DebugTargetItem.css | 97 ++++ .../src/components/debugtarget/DebugTargetItem.js | 91 ++++ .../src/components/debugtarget/DebugTargetList.css | 7 + .../src/components/debugtarget/DebugTargetList.js | 80 ++++ .../src/components/debugtarget/DebugTargetPane.css | 43 ++ .../src/components/debugtarget/DebugTargetPane.js | 147 ++++++ .../src/components/debugtarget/ExtensionDetail.css | 27 ++ .../src/components/debugtarget/ExtensionDetail.js | 243 ++++++++++ .../src/components/debugtarget/FieldPair.css | 29 ++ .../src/components/debugtarget/FieldPair.js | 49 ++ .../src/components/debugtarget/InspectAction.js | 58 +++ .../src/components/debugtarget/ProcessDetail.js | 32 ++ .../components/debugtarget/ServiceWorkerAction.css | 26 + .../components/debugtarget/ServiceWorkerAction.js | 124 +++++ .../debugtarget/ServiceWorkerAdditionalActions.js | 176 +++++++ .../src/components/debugtarget/TabAction.js | 52 ++ .../src/components/debugtarget/TabDetail.js | 34 ++ .../TemporaryExtensionAdditionalActions.js | 182 +++++++ .../debugtarget/TemporaryExtensionDetail.js | 67 +++ .../TemporaryExtensionInstallSection.css | 8 + .../TemporaryExtensionInstallSection.js | 101 ++++ .../debugtarget/TemporaryExtensionInstaller.js | 52 ++ .../src/components/debugtarget/WorkerDetail.js | 120 +++++ .../src/components/debugtarget/moz.build | 22 + .../client/aboutdebugging/src/components/moz.build | 21 + .../src/components/shared/DetailsLog.js | 64 +++ .../src/components/shared/IconLabel.css | 27 ++ .../src/components/shared/IconLabel.js | 48 ++ .../src/components/shared/Message.css | 79 ++++ .../src/components/shared/Message.js | 109 +++++ .../aboutdebugging/src/components/shared/moz.build | 9 + .../src/components/sidebar/RefreshDevicesButton.js | 46 ++ .../src/components/sidebar/Sidebar.css | 43 ++ .../src/components/sidebar/Sidebar.js | 259 ++++++++++ .../src/components/sidebar/SidebarFixedItem.css | 29 ++ .../src/components/sidebar/SidebarFixedItem.js | 60 +++ .../src/components/sidebar/SidebarItem.css | 71 +++ .../src/components/sidebar/SidebarItem.js | 81 ++++ .../src/components/sidebar/SidebarRuntimeItem.css | 41 ++ .../src/components/sidebar/SidebarRuntimeItem.js | 216 +++++++++ .../src/components/sidebar/moz.build | 11 + devtools/client/aboutdebugging/src/constants.js | 185 ++++++++ devtools/client/aboutdebugging/src/create-store.js | 78 +++ .../src/middleware/debug-target-listener.js | 111 +++++ .../aboutdebugging/src/middleware/error-logging.js | 35 ++ .../src/middleware/event-recording.js | 268 +++++++++++ .../src/middleware/extension-component-data.js | 84 ++++ .../client/aboutdebugging/src/middleware/moz.build | 13 + .../src/middleware/process-component-data.js | 55 +++ .../src/middleware/tab-component-data.js | 51 ++ .../src/middleware/worker-component-data.js | 82 ++++ .../aboutdebugging/src/modules/client-wrapper.js | 217 +++++++++ .../src/modules/debug-target-collapsibilities.js | 46 ++ .../src/modules/debug-target-support.js | 98 ++++ .../src/modules/extensions-helper.js | 92 ++++ devtools/client/aboutdebugging/src/modules/l10n.js | 12 + .../client/aboutdebugging/src/modules/moz.build | 16 + .../src/modules/network-locations.js | 69 +++ .../src/modules/runtime-client-factory.js | 68 +++ .../src/modules/runtime-default-preferences.js | 101 ++++ .../src/modules/runtimes-state-helper.js | 37 ++ .../aboutdebugging/src/modules/usb-runtimes.js | 122 +++++ devtools/client/aboutdebugging/src/moz.build | 17 + .../src/reducers/debug-targets-state.js | 155 ++++++ .../client/aboutdebugging/src/reducers/index.js | 24 + .../client/aboutdebugging/src/reducers/moz.build | 10 + .../aboutdebugging/src/reducers/runtimes-state.js | 178 +++++++ .../client/aboutdebugging/src/reducers/ui-state.js | 115 +++++ .../aboutdebugging/src/types/debug-target.js | 71 +++ devtools/client/aboutdebugging/src/types/index.js | 18 + devtools/client/aboutdebugging/src/types/moz.build | 10 + .../client/aboutdebugging/src/types/runtime.js | 164 +++++++ devtools/client/aboutdebugging/src/types/ui.js | 85 ++++ 103 files changed, 9485 insertions(+) create mode 100644 devtools/client/aboutdebugging/src/actions/debug-targets.js create mode 100644 devtools/client/aboutdebugging/src/actions/index.js create mode 100644 devtools/client/aboutdebugging/src/actions/moz.build create mode 100644 devtools/client/aboutdebugging/src/actions/runtimes.js create mode 100644 devtools/client/aboutdebugging/src/actions/telemetry.js create mode 100644 devtools/client/aboutdebugging/src/actions/ui.js create mode 100644 devtools/client/aboutdebugging/src/base.css create mode 100644 devtools/client/aboutdebugging/src/components/App.css create mode 100644 devtools/client/aboutdebugging/src/components/App.js create mode 100644 devtools/client/aboutdebugging/src/components/CompatibilityWarning.js create mode 100644 devtools/client/aboutdebugging/src/components/ConnectionPromptSetting.js create mode 100644 devtools/client/aboutdebugging/src/components/ProfilerDialog.css create mode 100644 devtools/client/aboutdebugging/src/components/ProfilerDialog.js create mode 100644 devtools/client/aboutdebugging/src/components/RuntimeActions.css create mode 100644 devtools/client/aboutdebugging/src/components/RuntimeActions.js create mode 100644 devtools/client/aboutdebugging/src/components/RuntimeInfo.css create mode 100644 devtools/client/aboutdebugging/src/components/RuntimeInfo.js create mode 100644 devtools/client/aboutdebugging/src/components/RuntimePage.js create mode 100644 devtools/client/aboutdebugging/src/components/ServiceWorkersWarning.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectPage.css create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectPage.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectSection.css create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectSection.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectSteps.css create mode 100644 devtools/client/aboutdebugging/src/components/connect/ConnectSteps.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.css create mode 100644 devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.css create mode 100644 devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.js create mode 100644 devtools/client/aboutdebugging/src/components/connect/moz.build create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.css create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js create mode 100644 devtools/client/aboutdebugging/src/components/debugtarget/moz.build create mode 100644 devtools/client/aboutdebugging/src/components/moz.build create mode 100644 devtools/client/aboutdebugging/src/components/shared/DetailsLog.js create mode 100644 devtools/client/aboutdebugging/src/components/shared/IconLabel.css create mode 100644 devtools/client/aboutdebugging/src/components/shared/IconLabel.js create mode 100644 devtools/client/aboutdebugging/src/components/shared/Message.css create mode 100644 devtools/client/aboutdebugging/src/components/shared/Message.js create mode 100644 devtools/client/aboutdebugging/src/components/shared/moz.build create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/RefreshDevicesButton.js create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/Sidebar.css create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/Sidebar.js create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarFixedItem.css create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarFixedItem.js create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarItem.css create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarItem.js create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarRuntimeItem.css create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/SidebarRuntimeItem.js create mode 100644 devtools/client/aboutdebugging/src/components/sidebar/moz.build create mode 100644 devtools/client/aboutdebugging/src/constants.js create mode 100644 devtools/client/aboutdebugging/src/create-store.js create mode 100644 devtools/client/aboutdebugging/src/middleware/debug-target-listener.js create mode 100644 devtools/client/aboutdebugging/src/middleware/error-logging.js create mode 100644 devtools/client/aboutdebugging/src/middleware/event-recording.js create mode 100644 devtools/client/aboutdebugging/src/middleware/extension-component-data.js create mode 100644 devtools/client/aboutdebugging/src/middleware/moz.build create mode 100644 devtools/client/aboutdebugging/src/middleware/process-component-data.js create mode 100644 devtools/client/aboutdebugging/src/middleware/tab-component-data.js create mode 100644 devtools/client/aboutdebugging/src/middleware/worker-component-data.js create mode 100644 devtools/client/aboutdebugging/src/modules/client-wrapper.js create mode 100644 devtools/client/aboutdebugging/src/modules/debug-target-collapsibilities.js create mode 100644 devtools/client/aboutdebugging/src/modules/debug-target-support.js create mode 100644 devtools/client/aboutdebugging/src/modules/extensions-helper.js create mode 100644 devtools/client/aboutdebugging/src/modules/l10n.js create mode 100644 devtools/client/aboutdebugging/src/modules/moz.build create mode 100644 devtools/client/aboutdebugging/src/modules/network-locations.js create mode 100644 devtools/client/aboutdebugging/src/modules/runtime-client-factory.js create mode 100644 devtools/client/aboutdebugging/src/modules/runtime-default-preferences.js create mode 100644 devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js create mode 100644 devtools/client/aboutdebugging/src/modules/usb-runtimes.js create mode 100644 devtools/client/aboutdebugging/src/moz.build create mode 100644 devtools/client/aboutdebugging/src/reducers/debug-targets-state.js create mode 100644 devtools/client/aboutdebugging/src/reducers/index.js create mode 100644 devtools/client/aboutdebugging/src/reducers/moz.build create mode 100644 devtools/client/aboutdebugging/src/reducers/runtimes-state.js create mode 100644 devtools/client/aboutdebugging/src/reducers/ui-state.js create mode 100644 devtools/client/aboutdebugging/src/types/debug-target.js create mode 100644 devtools/client/aboutdebugging/src/types/index.js create mode 100644 devtools/client/aboutdebugging/src/types/moz.build create mode 100644 devtools/client/aboutdebugging/src/types/runtime.js create mode 100644 devtools/client/aboutdebugging/src/types/ui.js (limited to 'devtools/client/aboutdebugging/src') diff --git a/devtools/client/aboutdebugging/src/actions/debug-targets.js b/devtools/client/aboutdebugging/src/actions/debug-targets.js new file mode 100644 index 0000000000..33b2f1cbea --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/debug-targets.js @@ -0,0 +1,356 @@ +/* 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 } = ChromeUtils.importESModule( + "resource://gre/modules/AddonManager.sys.mjs", + // AddonManager is a singleton, never create two instances of it. + { loadInDevToolsLoader: false } +); +const { + remoteClientManager, +} = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js"); + +const { + l10n, +} = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js"); + +const { + isSupportedDebugTargetPane, +} = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js"); + +const { + openTemporaryExtension, +} = require("resource://devtools/client/aboutdebugging/src/modules/extensions-helper.js"); + +const { + getCurrentClient, + getCurrentRuntime, +} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js"); + +const { + gDevTools, +} = require("resource://devtools/client/framework/devtools.js"); + +const { + DEBUG_TARGETS, + DEBUG_TARGET_PANE, + REQUEST_EXTENSIONS_FAILURE, + REQUEST_EXTENSIONS_START, + REQUEST_EXTENSIONS_SUCCESS, + REQUEST_PROCESSES_FAILURE, + REQUEST_PROCESSES_START, + REQUEST_PROCESSES_SUCCESS, + REQUEST_TABS_FAILURE, + REQUEST_TABS_START, + REQUEST_TABS_SUCCESS, + REQUEST_WORKERS_FAILURE, + REQUEST_WORKERS_START, + REQUEST_WORKERS_SUCCESS, + TEMPORARY_EXTENSION_INSTALL_FAILURE, + TEMPORARY_EXTENSION_INSTALL_START, + TEMPORARY_EXTENSION_INSTALL_SUCCESS, + TEMPORARY_EXTENSION_RELOAD_FAILURE, + TEMPORARY_EXTENSION_RELOAD_START, + TEMPORARY_EXTENSION_RELOAD_SUCCESS, + TERMINATE_EXTENSION_BGSCRIPT_FAILURE, + TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, + TERMINATE_EXTENSION_BGSCRIPT_START, + RUNTIMES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); + +function getTabForUrl(url) { + for (const navigator of Services.wm.getEnumerator("navigator:browser")) { + for (const browser of navigator.gBrowser.browsers) { + if ( + browser.contentWindow && + browser.contentWindow.location.href === url + ) { + return navigator.gBrowser.getTabForBrowser(browser); + } + } + } + + return null; +} + +function inspectDebugTarget(type, id) { + return async ({ dispatch, getState }) => { + const runtime = getCurrentRuntime(getState().runtimes); + + if ( + type == DEBUG_TARGETS.EXTENSION && + runtime.id === RUNTIMES.THIS_FIREFOX + ) { + // Bug 1780912: To avoid UX issues when debugging local web extensions, + // we are opening the toolbox in an independant window. + // Whereas all others are opened in new tabs. + gDevTools.showToolboxForWebExtension(id); + } else { + const urlParams = { + type, + }; + // Main process may not provide any ID. + if (id) { + urlParams.id = id; + } + + if (runtime.id !== RUNTIMES.THIS_FIREFOX) { + urlParams.remoteId = remoteClientManager.getRemoteId( + runtime.id, + runtime.type + ); + } + + const url = `about:devtools-toolbox?${new window.URLSearchParams( + urlParams + )}`; + + const existingTab = getTabForUrl(url); + if (existingTab) { + const navigator = existingTab.ownerGlobal; + navigator.gBrowser.selectedTab = existingTab; + navigator.focus(); + } else { + window.open(url); + } + } + + dispatch( + Actions.recordTelemetryEvent("inspect", { + target_type: type.toUpperCase(), + runtime_type: runtime.type, + }) + ); + }; +} + +function installTemporaryExtension() { + const message = l10n.getString( + "about-debugging-tmp-extension-install-message" + ); + return async ({ dispatch, getState }) => { + dispatch({ type: TEMPORARY_EXTENSION_INSTALL_START }); + const file = await openTemporaryExtension(window, message); + try { + await AddonManager.installTemporaryAddon(file); + dispatch({ type: TEMPORARY_EXTENSION_INSTALL_SUCCESS }); + } catch (e) { + dispatch({ type: TEMPORARY_EXTENSION_INSTALL_FAILURE, error: e }); + } + }; +} + +function pushServiceWorker(id, registrationFront) { + return async ({ dispatch, getState }) => { + try { + // The push button is only available if canDebugServiceWorkers is true. + // With this configuration, `push` should always be called on the + // registration front, and not on the (service) WorkerTargetActor. + await registrationFront.push(); + } catch (e) { + console.error(e); + } + }; +} + +function reloadTemporaryExtension(id) { + return async ({ dispatch, getState }) => { + dispatch({ type: TEMPORARY_EXTENSION_RELOAD_START, id }); + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const addonTargetFront = await clientWrapper.getAddon({ id }); + await addonTargetFront.reload(); + dispatch({ type: TEMPORARY_EXTENSION_RELOAD_SUCCESS, id }); + } catch (e) { + const error = typeof e === "string" ? new Error(e) : e; + dispatch({ type: TEMPORARY_EXTENSION_RELOAD_FAILURE, id, error }); + } + }; +} + +function removeTemporaryExtension(id) { + return async ({ getState }) => { + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + await clientWrapper.uninstallAddon({ id }); + } catch (e) { + console.error(e); + } + }; +} + +function terminateExtensionBackgroundScript(id) { + return async ({ dispatch, getState }) => { + dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_START, id }); + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const addonTargetFront = await clientWrapper.getAddon({ id }); + await addonTargetFront.terminateBackgroundScript(); + dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, id }); + } catch (e) { + const error = typeof e === "string" ? new Error(e) : e; + dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_FAILURE, id, error }); + } + }; +} + +function requestTabs() { + return async ({ dispatch, getState }) => { + dispatch({ type: REQUEST_TABS_START }); + + const runtime = getCurrentRuntime(getState().runtimes); + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const isSupported = isSupportedDebugTargetPane( + runtime.runtimeDetails.info.type, + DEBUG_TARGET_PANE.TAB + ); + const tabs = isSupported ? await clientWrapper.listTabs() : []; + + // Fetch the favicon for all tabs. + await Promise.all( + tabs.map(descriptorFront => descriptorFront.retrieveFavicon()) + ); + + dispatch({ type: REQUEST_TABS_SUCCESS, tabs }); + } catch (e) { + dispatch({ type: REQUEST_TABS_FAILURE, error: e }); + } + }; +} + +function requestExtensions() { + return async ({ dispatch, getState }) => { + dispatch({ type: REQUEST_EXTENSIONS_START }); + + const runtime = getCurrentRuntime(getState().runtimes); + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const isIconDataURLRequired = runtime.type !== RUNTIMES.THIS_FIREFOX; + const addons = await clientWrapper.listAddons({ + iconDataURL: isIconDataURLRequired, + }); + + const showHiddenAddons = getState().ui.showHiddenAddons; + + // Filter out non-debuggable addons as well as hidden ones, unless the dedicated + // preference is set to true. + const extensions = addons.filter( + a => a.debuggable && (!a.hidden || showHiddenAddons) + ); + + const installedExtensions = extensions.filter( + e => !e.temporarilyInstalled + ); + const temporaryExtensions = extensions.filter( + e => e.temporarilyInstalled + ); + + dispatch({ + type: REQUEST_EXTENSIONS_SUCCESS, + installedExtensions, + temporaryExtensions, + }); + } catch (e) { + dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e }); + } + }; +} + +function requestProcesses() { + return async ({ dispatch, getState }) => { + dispatch({ type: REQUEST_PROCESSES_START }); + + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const mainProcessDescriptorFront = await clientWrapper.getMainProcess(); + dispatch({ + type: REQUEST_PROCESSES_SUCCESS, + mainProcess: { + id: 0, + processFront: mainProcessDescriptorFront, + }, + }); + } catch (e) { + dispatch({ type: REQUEST_PROCESSES_FAILURE, error: e }); + } + }; +} + +function requestWorkers() { + return async ({ dispatch, getState }) => { + dispatch({ type: REQUEST_WORKERS_START }); + + const clientWrapper = getCurrentClient(getState().runtimes); + + try { + const { otherWorkers, serviceWorkers, sharedWorkers } = + await clientWrapper.listWorkers(); + + for (const serviceWorker of serviceWorkers) { + const { registrationFront } = serviceWorker; + if (!registrationFront) { + continue; + } + + const subscription = await registrationFront.getPushSubscription(); + serviceWorker.subscription = subscription; + } + + dispatch({ + type: REQUEST_WORKERS_SUCCESS, + otherWorkers, + serviceWorkers, + sharedWorkers, + }); + } catch (e) { + dispatch({ type: REQUEST_WORKERS_FAILURE, error: e }); + } + }; +} + +function startServiceWorker(registrationFront) { + return async () => { + try { + await registrationFront.start(); + } catch (e) { + console.error(e); + } + }; +} + +function unregisterServiceWorker(registrationFront) { + return async () => { + try { + await registrationFront.unregister(); + } catch (e) { + console.error(e); + } + }; +} + +module.exports = { + inspectDebugTarget, + installTemporaryExtension, + pushServiceWorker, + reloadTemporaryExtension, + removeTemporaryExtension, + requestTabs, + requestExtensions, + requestProcesses, + requestWorkers, + startServiceWorker, + terminateExtensionBackgroundScript, + unregisterServiceWorker, +}; diff --git a/devtools/client/aboutdebugging/src/actions/index.js b/devtools/client/aboutdebugging/src/actions/index.js new file mode 100644 index 0000000000..797d2c5831 --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/index.js @@ -0,0 +1,12 @@ +/* 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 debugTargets = require("resource://devtools/client/aboutdebugging/src/actions/debug-targets.js"); +const runtimes = require("resource://devtools/client/aboutdebugging/src/actions/runtimes.js"); +const telemetry = require("resource://devtools/client/aboutdebugging/src/actions/telemetry.js"); +const ui = require("resource://devtools/client/aboutdebugging/src/actions/ui.js"); + +Object.assign(exports, ui, runtimes, telemetry, debugTargets); diff --git a/devtools/client/aboutdebugging/src/actions/moz.build b/devtools/client/aboutdebugging/src/actions/moz.build new file mode 100644 index 0000000000..a750640d06 --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/moz.build @@ -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/. + +DevToolsModules( + "debug-targets.js", + "index.js", + "runtimes.js", + "telemetry.js", + "ui.js", +) diff --git a/devtools/client/aboutdebugging/src/actions/runtimes.js b/devtools/client/aboutdebugging/src/actions/runtimes.js new file mode 100644 index 0000000000..fba620951e --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/runtimes.js @@ -0,0 +1,515 @@ +/* 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 Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); + +const { + getAllRuntimes, + getCurrentRuntime, + findRuntimeById, +} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js"); + +const { + l10n, +} = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js"); +const { + setDefaultPreferencesIfNeeded, + DEFAULT_PREFERENCES, +} = require("resource://devtools/client/aboutdebugging/src/modules/runtime-default-preferences.js"); +const { + createClientForRuntime, +} = require("resource://devtools/client/aboutdebugging/src/modules/runtime-client-factory.js"); +const { + isSupportedDebugTargetPane, +} = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js"); + +const { + remoteClientManager, +} = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js"); + +const { + CONNECT_RUNTIME_CANCEL, + CONNECT_RUNTIME_FAILURE, + CONNECT_RUNTIME_NOT_RESPONDING, + CONNECT_RUNTIME_START, + CONNECT_RUNTIME_SUCCESS, + DEBUG_TARGET_PANE, + DISCONNECT_RUNTIME_FAILURE, + DISCONNECT_RUNTIME_START, + DISCONNECT_RUNTIME_SUCCESS, + PAGE_TYPES, + REMOTE_RUNTIMES_UPDATED, + RUNTIME_PREFERENCE, + RUNTIMES, + THIS_FIREFOX_RUNTIME_CREATED, + UNWATCH_RUNTIME_FAILURE, + UNWATCH_RUNTIME_START, + UNWATCH_RUNTIME_SUCCESS, + UPDATE_CONNECTION_PROMPT_SETTING_FAILURE, + UPDATE_CONNECTION_PROMPT_SETTING_START, + UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS, + WATCH_RUNTIME_FAILURE, + WATCH_RUNTIME_START, + WATCH_RUNTIME_SUCCESS, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +const CONNECTION_TIMING_OUT_DELAY = 3000; +const CONNECTION_CANCEL_DELAY = 13000; + +async function getRuntimeIcon(runtime, channel) { + if (runtime.isFenix) { + switch (channel) { + case "release": + case "beta": + return "chrome://devtools/skin/images/aboutdebugging-fenix.svg"; + case "aurora": + default: + return "chrome://devtools/skin/images/aboutdebugging-fenix-nightly.svg"; + } + } + + return channel === "release" || channel === "beta" || channel === "aurora" + ? `chrome://devtools/skin/images/aboutdebugging-firefox-${channel}.svg` + : "chrome://devtools/skin/images/aboutdebugging-firefox-nightly.svg"; +} + +function onRemoteDevToolsClientClosed() { + window.AboutDebugging.onNetworkLocationsUpdated(); + window.AboutDebugging.onUSBRuntimesUpdated(); +} + +function connectRuntime(id) { + // Create a random connection id to track the connection attempt in telemetry. + const connectionId = (Math.random() * 100000) | 0; + + return async ({ dispatch, getState }) => { + dispatch({ type: CONNECT_RUNTIME_START, connectionId, id }); + + // The preferences test-connection-timing-out-delay and test-connection-cancel-delay + // don't have a default value but will be overridden during our tests. + const connectionTimingOutDelay = Services.prefs.getIntPref( + "devtools.aboutdebugging.test-connection-timing-out-delay", + CONNECTION_TIMING_OUT_DELAY + ); + const connectionCancelDelay = Services.prefs.getIntPref( + "devtools.aboutdebugging.test-connection-cancel-delay", + CONNECTION_CANCEL_DELAY + ); + + const connectionNotRespondingTimer = setTimeout(() => { + // If connecting to the runtime takes time over CONNECTION_TIMING_OUT_DELAY, + // we assume the connection prompt is showing on the runtime, show a dialog + // to let user know that. + dispatch({ type: CONNECT_RUNTIME_NOT_RESPONDING, connectionId, id }); + }, connectionTimingOutDelay); + const connectionCancelTimer = setTimeout(() => { + // Connect button of the runtime will be disabled during connection, but the status + // continues till the connection was either succeed or failed. This may have a + // possibility that the disabling continues unless page reloading, user will not be + // able to click again. To avoid this, revert the connect button status after + // CONNECTION_CANCEL_DELAY ms. + dispatch({ type: CONNECT_RUNTIME_CANCEL, connectionId, id }); + }, connectionCancelDelay); + + try { + const runtime = findRuntimeById(id, getState().runtimes); + const clientWrapper = await createClientForRuntime(runtime); + + await setDefaultPreferencesIfNeeded(clientWrapper, DEFAULT_PREFERENCES); + + const deviceDescription = await clientWrapper.getDeviceDescription(); + const compatibilityReport = + await clientWrapper.checkVersionCompatibility(); + const icon = await getRuntimeIcon(runtime, deviceDescription.channel); + + const { + CONNECTION_PROMPT, + PERMANENT_PRIVATE_BROWSING, + SERVICE_WORKERS_ENABLED, + } = RUNTIME_PREFERENCE; + const connectionPromptEnabled = await clientWrapper.getPreference( + CONNECTION_PROMPT, + false + ); + const privateBrowsing = await clientWrapper.getPreference( + PERMANENT_PRIVATE_BROWSING, + false + ); + const serviceWorkersEnabled = await clientWrapper.getPreference( + SERVICE_WORKERS_ENABLED, + true + ); + const serviceWorkersAvailable = serviceWorkersEnabled && !privateBrowsing; + + // Fenix specific workarounds are needed until we can get proper server side APIs + // to detect Fenix and get the proper application names and versions. + // See https://github.com/mozilla-mobile/fenix/issues/2016. + + // For Fenix runtimes, the ADB runtime name is more accurate than the one returned + // by the Device actor. + const runtimeName = runtime.isFenix + ? runtime.name + : deviceDescription.name; + + // For Fenix runtimes, the version we should display is the application version + // retrieved from ADB, and not the Gecko version returned by the Device actor. + const version = runtime.isFenix + ? runtime.extra.adbPackageVersion + : deviceDescription.version; + + const runtimeDetails = { + canDebugServiceWorkers: deviceDescription.canDebugServiceWorkers, + clientWrapper, + compatibilityReport, + connectionPromptEnabled, + info: { + deviceName: deviceDescription.deviceName, + icon, + isFenix: runtime.isFenix, + name: runtimeName, + os: deviceDescription.os, + type: runtime.type, + version, + }, + serviceWorkersAvailable, + }; + + if (runtime.type !== RUNTIMES.THIS_FIREFOX) { + // `closed` event will be emitted when disabling remote debugging + // on the connected remote runtime. + clientWrapper.once("closed", onRemoteDevToolsClientClosed); + } + + dispatch({ + type: CONNECT_RUNTIME_SUCCESS, + connectionId, + runtime: { + id, + runtimeDetails, + type: runtime.type, + }, + }); + } catch (e) { + dispatch({ type: CONNECT_RUNTIME_FAILURE, connectionId, id, error: e }); + } finally { + clearTimeout(connectionNotRespondingTimer); + clearTimeout(connectionCancelTimer); + } + }; +} + +function createThisFirefoxRuntime() { + return ({ dispatch, getState }) => { + const thisFirefoxRuntime = { + id: RUNTIMES.THIS_FIREFOX, + isConnecting: false, + isConnectionFailed: false, + isConnectionNotResponding: false, + isConnectionTimeout: false, + isUnavailable: false, + isUnplugged: false, + name: l10n.getString("about-debugging-this-firefox-runtime-name"), + type: RUNTIMES.THIS_FIREFOX, + }; + dispatch({ + type: THIS_FIREFOX_RUNTIME_CREATED, + runtime: thisFirefoxRuntime, + }); + }; +} + +function disconnectRuntime(id, shouldRedirect = false) { + return async ({ dispatch, getState }) => { + dispatch({ type: DISCONNECT_RUNTIME_START }); + try { + const runtime = findRuntimeById(id, getState().runtimes); + const { clientWrapper } = runtime.runtimeDetails; + + if (runtime.type !== RUNTIMES.THIS_FIREFOX) { + clientWrapper.off("closed", onRemoteDevToolsClientClosed); + } + await clientWrapper.close(); + if (shouldRedirect) { + await dispatch( + Actions.selectPage(PAGE_TYPES.RUNTIME, RUNTIMES.THIS_FIREFOX) + ); + } + + dispatch({ + type: DISCONNECT_RUNTIME_SUCCESS, + runtime: { + id, + type: runtime.type, + }, + }); + } catch (e) { + dispatch({ type: DISCONNECT_RUNTIME_FAILURE, error: e }); + } + }; +} + +function updateConnectionPromptSetting(connectionPromptEnabled) { + return async ({ dispatch, getState }) => { + dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_START }); + try { + const runtime = getCurrentRuntime(getState().runtimes); + const { clientWrapper } = runtime.runtimeDetails; + const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT; + await clientWrapper.setPreference( + promptPrefName, + connectionPromptEnabled + ); + // Re-get actual value from the runtime. + connectionPromptEnabled = await clientWrapper.getPreference( + promptPrefName, + connectionPromptEnabled + ); + + dispatch({ + type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS, + connectionPromptEnabled, + runtime, + }); + } catch (e) { + dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_FAILURE, error: e }); + } + }; +} + +function watchRuntime(id) { + return async ({ dispatch, getState }) => { + dispatch({ type: WATCH_RUNTIME_START }); + + try { + if (id === RUNTIMES.THIS_FIREFOX) { + // THIS_FIREFOX connects and disconnects on the fly when opening the page. + await dispatch(connectRuntime(RUNTIMES.THIS_FIREFOX)); + } + + // The selected runtime should already have a connected client assigned. + const runtime = findRuntimeById(id, getState().runtimes); + await dispatch({ type: WATCH_RUNTIME_SUCCESS, runtime }); + + dispatch(Actions.requestExtensions()); + // we have to wait for tabs, otherwise the requests to getTarget may interfer + // with listProcesses + await dispatch(Actions.requestTabs()); + dispatch(Actions.requestWorkers()); + + if ( + isSupportedDebugTargetPane( + runtime.runtimeDetails.info.type, + DEBUG_TARGET_PANE.PROCESSES + ) + ) { + dispatch(Actions.requestProcesses()); + } + } catch (e) { + dispatch({ type: WATCH_RUNTIME_FAILURE, error: e }); + } + }; +} + +function unwatchRuntime(id) { + return async ({ dispatch, getState }) => { + const runtime = findRuntimeById(id, getState().runtimes); + + dispatch({ type: UNWATCH_RUNTIME_START, runtime }); + + try { + if (id === RUNTIMES.THIS_FIREFOX) { + // THIS_FIREFOX connects and disconnects on the fly when opening the page. + await dispatch(disconnectRuntime(RUNTIMES.THIS_FIREFOX)); + } + + dispatch({ type: UNWATCH_RUNTIME_SUCCESS }); + } catch (e) { + dispatch({ type: UNWATCH_RUNTIME_FAILURE, error: e }); + } + }; +} + +function updateNetworkRuntimes(locations) { + const runtimes = locations.map(location => { + const [host, port] = location.split(":"); + return { + id: location, + extra: { + connectionParameters: { host, port: parseInt(port, 10) }, + }, + isConnecting: false, + isConnectionFailed: false, + isConnectionNotResponding: false, + isConnectionTimeout: false, + isFenix: false, + isUnavailable: false, + isUnplugged: false, + isUnknown: false, + name: location, + type: RUNTIMES.NETWORK, + }; + }); + return updateRemoteRuntimes(runtimes, RUNTIMES.NETWORK); +} + +function updateUSBRuntimes(adbRuntimes) { + const runtimes = adbRuntimes.map(adbRuntime => { + // Set connectionParameters only for known runtimes. + const socketPath = adbRuntime.socketPath; + const deviceId = adbRuntime.deviceId; + const connectionParameters = socketPath ? { deviceId, socketPath } : null; + return { + id: adbRuntime.id, + extra: { + connectionParameters, + deviceName: adbRuntime.deviceName, + adbPackageVersion: adbRuntime.versionName, + }, + isConnecting: false, + isConnectionFailed: false, + isConnectionNotResponding: false, + isConnectionTimeout: false, + isFenix: adbRuntime.isFenix, + isUnavailable: adbRuntime.isUnavailable, + isUnplugged: adbRuntime.isUnplugged, + name: adbRuntime.shortName, + type: RUNTIMES.USB, + }; + }); + return updateRemoteRuntimes(runtimes, RUNTIMES.USB); +} + +/** + * Check that a given runtime can still be found in the provided array of runtimes, and + * that the connection of the associated DevToolsClient is still valid. + * Note that this check is only valid for runtimes which match the type of the runtimes + * in the array. + */ +function _isRuntimeValid(runtime, runtimes) { + const isRuntimeAvailable = runtimes.some(r => r.id === runtime.id); + const isConnectionValid = + runtime.runtimeDetails && !runtime.runtimeDetails.clientWrapper.isClosed(); + return isRuntimeAvailable && isConnectionValid; +} + +function updateRemoteRuntimes(runtimes, type) { + return async ({ dispatch, getState }) => { + const currentRuntime = getCurrentRuntime(getState().runtimes); + + // Check if the updated remote runtimes should trigger a navigation out of the current + // runtime page. + if ( + currentRuntime && + currentRuntime.type === type && + !_isRuntimeValid(currentRuntime, runtimes) + ) { + // Since current remote runtime is invalid, move to this firefox page. + // This case is considered as followings and so on: + // * Remove ADB addon + // * (Physically) Disconnect USB runtime + // + // The reason we call selectPage before REMOTE_RUNTIMES_UPDATED is fired is below. + // Current runtime can not be retrieved after REMOTE_RUNTIMES_UPDATED action, since + // that updates runtime state. So, before that we fire selectPage action to execute + // `unwatchRuntime` correctly. + await dispatch( + Actions.selectPage(PAGE_TYPES.RUNTIME, RUNTIMES.THIS_FIREFOX) + ); + } + + // For existing runtimes, transfer all properties that are not available in the + // runtime objects passed to this method: + // - runtimeDetails (set by about:debugging after a successful connection) + // - isConnecting (set by about:debugging during the connection) + // - isConnectionFailed (set by about:debugging if connection was failed) + // - isConnectionNotResponding + // (set by about:debugging if connection is taking too much time) + // - isConnectionTimeout (set by about:debugging if connection was timeout) + runtimes.forEach(runtime => { + const existingRuntime = findRuntimeById(runtime.id, getState().runtimes); + const isConnectionValid = + existingRuntime?.runtimeDetails && + !existingRuntime.runtimeDetails.clientWrapper.isClosed(); + runtime.runtimeDetails = isConnectionValid + ? existingRuntime.runtimeDetails + : null; + runtime.isConnecting = existingRuntime + ? existingRuntime.isConnecting + : false; + runtime.isConnectionFailed = existingRuntime + ? existingRuntime.isConnectionFailed + : false; + runtime.isConnectionNotResponding = existingRuntime + ? existingRuntime.isConnectionNotResponding + : false; + runtime.isConnectionTimeout = existingRuntime + ? existingRuntime.isConnectionTimeout + : false; + }); + + const existingRuntimes = getAllRuntimes(getState().runtimes); + for (const runtime of existingRuntimes) { + // Runtime was connected before. + const isConnected = runtime.runtimeDetails; + // Runtime is of the same type as the updated runtimes array, so we should check it. + const isSameType = runtime.type === type; + if (isConnected && isSameType && !_isRuntimeValid(runtime, runtimes)) { + // Disconnect runtimes that were no longer valid. + await dispatch(disconnectRuntime(runtime.id)); + } + } + + dispatch({ type: REMOTE_RUNTIMES_UPDATED, runtimes, runtimeType: type }); + + for (const runtime of getAllRuntimes(getState().runtimes)) { + if (runtime.type !== type) { + continue; + } + + // Reconnect clients already available in the RemoteClientManager. + const isConnected = !!runtime.runtimeDetails; + const hasConnectedClient = remoteClientManager.hasClient( + runtime.id, + runtime.type + ); + if (!isConnected && hasConnectedClient) { + await dispatch(connectRuntime(runtime.id)); + } + } + }; +} + +/** + * Remove all the listeners added on client objects. Since those objects are persisted + * regardless of the about:debugging lifecycle, all the added events should be removed + * before leaving about:debugging. + */ +function removeRuntimeListeners() { + return ({ dispatch, getState }) => { + const allRuntimes = getAllRuntimes(getState().runtimes); + const remoteRuntimes = allRuntimes.filter( + r => r.type !== RUNTIMES.THIS_FIREFOX + ); + for (const runtime of remoteRuntimes) { + if (runtime.runtimeDetails) { + const { clientWrapper } = runtime.runtimeDetails; + clientWrapper.off("closed", onRemoteDevToolsClientClosed); + } + } + }; +} + +module.exports = { + connectRuntime, + createThisFirefoxRuntime, + disconnectRuntime, + removeRuntimeListeners, + unwatchRuntime, + updateConnectionPromptSetting, + updateNetworkRuntimes, + updateUSBRuntimes, + watchRuntime, +}; diff --git a/devtools/client/aboutdebugging/src/actions/telemetry.js b/devtools/client/aboutdebugging/src/actions/telemetry.js new file mode 100644 index 0000000000..b418c77a50 --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/telemetry.js @@ -0,0 +1,23 @@ +/* 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 { + TELEMETRY_RECORD, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +/** + * If a given event cannot be mapped to an existing action, use this action that will only + * be processed by the event recording middleware. + */ +function recordTelemetryEvent(method, details) { + return ({ dispatch, getState }) => { + dispatch({ type: TELEMETRY_RECORD, method, details }); + }; +} + +module.exports = { + recordTelemetryEvent, +}; diff --git a/devtools/client/aboutdebugging/src/actions/ui.js b/devtools/client/aboutdebugging/src/actions/ui.js new file mode 100644 index 0000000000..fb676cefd6 --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/ui.js @@ -0,0 +1,202 @@ +/* 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 { + ADB_ADDON_INSTALL_START, + ADB_ADDON_INSTALL_SUCCESS, + ADB_ADDON_INSTALL_FAILURE, + ADB_ADDON_UNINSTALL_START, + ADB_ADDON_UNINSTALL_SUCCESS, + ADB_ADDON_UNINSTALL_FAILURE, + ADB_ADDON_STATUS_UPDATED, + ADB_READY_UPDATED, + DEBUG_TARGET_COLLAPSIBILITY_UPDATED, + HIDE_PROFILER_DIALOG, + NETWORK_LOCATIONS_UPDATE_FAILURE, + NETWORK_LOCATIONS_UPDATE_START, + NETWORK_LOCATIONS_UPDATE_SUCCESS, + PAGE_TYPES, + SELECT_PAGE_FAILURE, + SELECT_PAGE_START, + SELECT_PAGE_SUCCESS, + SELECTED_RUNTIME_ID_UPDATED, + SHOW_PROFILER_DIALOG, + SWITCH_PROFILER_CONTEXT, + USB_RUNTIMES_SCAN_START, + USB_RUNTIMES_SCAN_SUCCESS, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +const NetworkLocationsModule = require("resource://devtools/client/aboutdebugging/src/modules/network-locations.js"); +const { + adbAddon, +} = require("resource://devtools/client/shared/remote-debugging/adb/adb-addon.js"); +const { + refreshUSBRuntimes, +} = require("resource://devtools/client/aboutdebugging/src/modules/usb-runtimes.js"); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); + +function selectPage(page, runtimeId) { + return async ({ dispatch, getState }) => { + dispatch({ type: SELECT_PAGE_START }); + + try { + const isSamePage = (oldPage, newPage) => { + if (newPage === PAGE_TYPES.RUNTIME && oldPage === PAGE_TYPES.RUNTIME) { + return runtimeId === getState().runtimes.selectedRuntimeId; + } + return newPage === oldPage; + }; + + if (!page) { + throw new Error("No page provided."); + } + + const currentPage = getState().ui.selectedPage; + // Nothing to dispatch if the page is the same as the current page + if (isSamePage(currentPage, page)) { + return; + } + + // Stop showing the profiler dialog if we are navigating to another page. + if (getState().ui.showProfilerDialog) { + await dispatch({ type: HIDE_PROFILER_DIALOG }); + } + + // Stop watching current runtime, if currently on a RUNTIME page. + if (currentPage === PAGE_TYPES.RUNTIME) { + const currentRuntimeId = getState().runtimes.selectedRuntimeId; + await dispatch(Actions.unwatchRuntime(currentRuntimeId)); + } + + // Always update the selected runtime id. + // If we are navigating to a non-runtime page, the Runtime page components are no + // longer rendered so it is safe to nullify the runtimeId. + // If we are navigating to a runtime page, the runtime corresponding to runtimeId + // is already connected, so components can safely get runtimeDetails on this new + // runtime. + dispatch({ type: SELECTED_RUNTIME_ID_UPDATED, runtimeId }); + + // Start watching current runtime, if moving to a RUNTIME page. + if (page === PAGE_TYPES.RUNTIME) { + await dispatch(Actions.watchRuntime(runtimeId)); + } + + dispatch({ type: SELECT_PAGE_SUCCESS, page }); + } catch (e) { + dispatch({ type: SELECT_PAGE_FAILURE, error: e }); + } + }; +} + +function updateDebugTargetCollapsibility(key, isCollapsed) { + return { type: DEBUG_TARGET_COLLAPSIBILITY_UPDATED, key, isCollapsed }; +} + +function addNetworkLocation(location) { + return ({ dispatch, getState }) => { + NetworkLocationsModule.addNetworkLocation(location); + }; +} + +function removeNetworkLocation(location) { + return ({ dispatch, getState }) => { + NetworkLocationsModule.removeNetworkLocation(location); + }; +} + +function showProfilerDialog() { + return { type: SHOW_PROFILER_DIALOG }; +} + +/** + * The profiler can switch between "devtools-remote" and "aboutprofiling-remote" + * page contexts. + */ +function switchProfilerContext(profilerContext) { + return { type: SWITCH_PROFILER_CONTEXT, profilerContext }; +} + +function hideProfilerDialog() { + return { type: HIDE_PROFILER_DIALOG }; +} + +function updateAdbAddonStatus(adbAddonStatus) { + return { type: ADB_ADDON_STATUS_UPDATED, adbAddonStatus }; +} + +function updateAdbReady(isAdbReady) { + return { type: ADB_READY_UPDATED, isAdbReady }; +} + +function updateNetworkLocations(locations) { + return async ({ dispatch, getState }) => { + dispatch({ type: NETWORK_LOCATIONS_UPDATE_START }); + try { + await dispatch(Actions.updateNetworkRuntimes(locations)); + dispatch({ type: NETWORK_LOCATIONS_UPDATE_SUCCESS, locations }); + } catch (e) { + dispatch({ type: NETWORK_LOCATIONS_UPDATE_FAILURE, error: e }); + } + }; +} + +function installAdbAddon() { + return async ({ dispatch, getState }) => { + dispatch({ type: ADB_ADDON_INSTALL_START }); + + try { + // "aboutdebugging" will be forwarded to telemetry as the installation source + // for the addon. + await adbAddon.install("about:debugging"); + dispatch({ type: ADB_ADDON_INSTALL_SUCCESS }); + } catch (e) { + dispatch({ type: ADB_ADDON_INSTALL_FAILURE, error: e }); + } + }; +} + +function uninstallAdbAddon() { + return async ({ dispatch, getState }) => { + dispatch({ type: ADB_ADDON_UNINSTALL_START }); + + try { + await adbAddon.uninstall(); + dispatch({ type: ADB_ADDON_UNINSTALL_SUCCESS }); + } catch (e) { + dispatch({ type: ADB_ADDON_UNINSTALL_FAILURE, error: e }); + } + }; +} + +function scanUSBRuntimes() { + return async ({ dispatch, getState }) => { + // do not re-scan if we are already doing it + if (getState().ui.isScanningUsb) { + return; + } + + dispatch({ type: USB_RUNTIMES_SCAN_START }); + await refreshUSBRuntimes(); + dispatch({ type: USB_RUNTIMES_SCAN_SUCCESS }); + }; +} + +module.exports = { + addNetworkLocation, + hideProfilerDialog, + installAdbAddon, + removeNetworkLocation, + scanUSBRuntimes, + selectPage, + showProfilerDialog, + switchProfilerContext, + uninstallAdbAddon, + updateAdbAddonStatus, + updateAdbReady, + updateDebugTargetCollapsibility, + updateNetworkLocations, +}; diff --git a/devtools/client/aboutdebugging/src/base.css b/devtools/client/aboutdebugging/src/base.css new file mode 100644 index 0000000000..728cbeed0c --- /dev/null +++ b/devtools/client/aboutdebugging/src/base.css @@ -0,0 +1,521 @@ +/* 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/. */ + +:root { + /* Colors from common.css */ + --in-content-background-color: f9f9fa; + --in-content-border-color: #d7d7db; + --in-content-primary-button-background: rgb(0, 97, 224); + --in-content-primary-button-background-active: rgb(5, 62, 148); + --in-content-primary-button-background-hover: rgb(2, 80, 187); + --in-content-text-color: #0c0c0d; + + --bg-color: var(--in-content-background-color); + --text-color: var(--in-content-text-color); + --secondary-text-color: var(--grey-50); + + --border-color: var(--in-content-border-color); + + --box-background: #fff; + --box-border-color: var(--in-content-border-color); + + --button-background-color: var(--grey-90-a10); /* Note: this is from Photon Default button */ + --button-color: var(--grey-90); /* Note: this is from Photon Default button */ + --button-hover-background-color: var(--grey-90-a20); /* Note: this is from Photon Default button */ + --button-active-background-color: var(--grey-90-a30); /* Note: this is from Photon Default button */ + + --category-background-hover: rgba(12,12,13,0.1); + --category-text: rgba(12,12,13); + --category-text-selected: var(--in-content-primary-button-background); + + --fieldpair-text-color: var(--grey-50); + + --sidebar-text-color: var(--category-text); + --sidebar-selected-color: var(--category-text-selected); + --sidebar-background-hover: var(--category-background-hover); + + --card-background-color: var(--white-100); + --card-separator-color: var(--grey-20); + + /* Dimensions from common.css #categories > .category */ + /* TODO: Values are not based on photon's 4px base distance, see bug 1501638 */ + --category-height: 48px; + --category-padding: 10px; + --category-transition-duration: 150ms; + + --icon-ok-color: var(--green-70); + --icon-info-color: var(--grey-90); + + --link-color: var(--in-content-primary-button-background); + --link-color-active: var(--in-content-primary-button-background-active); + --link-color-hover: var(--in-content-primary-button-background-hover); + + --primary-button-background-color: var(--blue-60); + --primary-button-color: var(--white-100); + --primary-button-hover-background-color: var(--blue-70); + --primary-button-active-background-color: var(--blue-80); + + --popup-header-background-color: var(--grey-20); + --popup-header-color: var(--grey-90); + + /* Colors from Photon */ + --success-background: #30e60b; + --warning-background: #fffbd6; /* from the Web Console */ + --warning-border: rgba(164, 127, 0, 0.27); /* yellow-70(#a47f00) at 27% */ + --warning-icon: var(--yellow-65); /* from the Web Console */ + --warning-text: var(--yellow-80); /* from the Web Console */ + --error-background: #fdf2f5; /* from the Web Console */ + --error-border: rgba(90, 0, 2, 0.16); /* red-80(#5a0002) at 16% */ + --error-icon: var(--red-60); /* from the Web Console */ + --error-text: var(--red-70); /* from the Web Console */ + --highlight-50: #0a84ff; + --grey-20: #ededf0; /* for ui, no special semantic */ + --grey-30: #d7d7db; /* for ui, no special semantic */ + --grey-50: #737373; /* for ui, no special semantic */ + --grey-90: #0c0c0d; /* for ui, no special semantic */ + --grey-90-a10: rgba(12, 12, 13, 0.1); + --grey-90-a20: rgba(12, 12, 13, 0.2); + --grey-90-a30: rgba(12, 12, 13, 0.3); + --grey-90-a60: rgba(12, 12, 13, 0.6); + --red-70: #a4000f; /* for ui, no special semantic */ + --white-100: #fff; /* for ui, no special semantic */ + --yellow-60: #d7b600; /* for ui, no special semantic */ + --yellow-70: #a47f00; /* for ui, no special semantic */ + + /* Typography from Photon */ + /* See https://firefox-dev.tools/photon/visuals/typography.html */ + --body-10-font-size: 13px; + --body-10-font-weight: 400; + --body-20-font-size: 15px; + --body-20-font-weight: 400; + --body-20-font-weight-bold: 700; + --caption-10-font-size: 11px; + --caption-10-font-weight: 400; + --caption-20-font-size: 13px; + --caption-20-font-weight: 400; + --display-10-font-size: 28px; + --display-10-font-weight: 200; + --title-20-font-size: 17px; + --title-20-font-weight: 600; + --title-30-font-size: 22px; + --title-30-font-weight: 300; + + /* Global layout vars */ + --page-width: 664px; + --base-unit: 4px; + + /* Global styles */ + --base-font-style: message-box; + --base-font-size: var(--body-10-font-size); + --base-font-weight: var(--body-10-font-weight); + --base-line-height: 1.8; + --icon-label-font-size: var(--body-10-font-size); + --message-font-size: var(--body-10-font-size); + --button-font-size: var(--base-font-size); + --micro-font-size: 11px; + --monospace-font-family: monospace; + + --card-shadow-blur-radius: var(--base-unit); + + + /* + * Variables particular to about:debugging + */ + --alt-heading-icon-size: calc(var(--base-unit) * 6); + --alt-heading-icon-gap: var(--base-unit); + --main-heading-icon-size: calc(var(--base-unit) * 17); /* 4px * 17 = 68px */ + --main-heading-icon-gap: calc(var(--base-unit) * 3); + --main-subheading-icon-size: calc(var(--base-unit) * 4); + --main-subheading-heading-icon-gap: calc(var(--base-unit) * 2); +} + +/* Dark Theme variables */ + +@media (prefers-color-scheme: dark) { + :root { + --in-content-background-color: rgb(28, 27, 34); + --in-content-border-color: rgba(249,249,250,0.2); + --in-content-primary-button-background: #00ddff; + --in-content-primary-button-background-active: rgb(170,242,255); + --in-content-primary-button-background-hover: rgb(128,235,255); + --in-content-text-color: #eee; + + --secondary-text-color: rgb(168, 168, 168); + + --box-background: rgb(35, 34, 43); + + --button-background-color: rgb(72, 72, 84); + --button-color: var(--white-100); + --button-hover-background-color: rgb(92, 92, 106); + + --category-background-hover: rgba(12,12,13,0.1); + --category-text: var(--text-color); + + --fieldpair-text-color: var(--text-color); + + --sidebar-text-color: var(--text-color); + --sidebar-background-hover: rgb(92, 92, 106); + + --card-background-color: rgb(35, 34, 43); + --card-separator-color: var(--grey-50); + + --icon-ok-color: var(--white-100); + --icon-info-color: var(--white-100); + + --popup-header-background-color: var(--grey-50); + --popup-header-color: var(--white-100); + + /* + * From common.inc.css + * https://searchfox.org/mozilla-central/rev/b52cf6bbe214bd9d93ed9333d0403f7d556ad7c8/toolkit/themes/shared/in-content/common.inc.css#165-168 + */ + --primary-button-background-color: #00ddff; + --primary-button-color: rgb(43,42,51); + --primary-button-active-background-color: rgb(170,242,255); + --primary-button-hover-background-color: rgb(128,235,255); + } +} + +/* +* Reset some tags +*/ + +html { + font: var(--base-font-style); +} + +body { + margin: 0; + padding: 0; + color: var(--text-color); + font-size: var(--base-font-size); + font-weight: var(--base-font-weight); + line-height: var(--base-line-height); + background: var(--bg-color); +} + +dd { + margin: 0; + padding: 0; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} + +a { + color: var(--link-color); +} +a:hover { + color: var(--link-color-hover); +} +a:active { + color: var(--link-color-active); +} + +p, h1 { + margin: 0; +} + +/* +* Utils +*/ + +/* text that needs to be cut with … */ +.ellipsis-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Technical text that should use a monospace font, such as code, error messages. */ +.technical-text { + font-family: var(--monospace-font-family); +} + +/* Links that need to look like current text */ +.undecorated-link, +.undecorated-link:hover { + text-decoration: none; + color: currentColor; +} + +/* Text needs to wrap anywhere */ +.word-wrap-anywhere { + word-wrap: anywhere; +} + +/* +* Typography +*/ + +/* Main style for heading (i.e. h1) */ +.main-heading { + font-size: var(--display-10-font-size); + font-weight: var(--display-10-font-weight); + line-height: 1.2; +} + +.main-heading__icon { + width: 100%; +} + +.main-heading-subtitle { + font-size: var(--title-30-font-size); + font-weight: var(--title-30-font-weight); +} + +/* Main style for a subheading (i.e. h2). It features an icon */ +/* +--------+-------------+ +* | [Icon] | Lorem ipsum | +* +--------+-------------+ +*/ +.main-subheading { + margin-block: calc(var(--base-unit) * 4) 0; + font-size: var(--title-20-font-size); /* Note: this is from Photon Title 20 */ + font-weight: var(--title-20-font-weight); /* Note: this is from Photon Title 20 */ + + display: grid; + grid-template-columns: var(--main-subheading-icon-size) 1fr; + grid-column-gap: var(--main-subheading-heading-icon-gap); + align-items: center; +} + +.main-subheading__icon { + width: 100%; + fill: currentColor; + -moz-context-properties: fill; +} + +/* Alternative style for a heading (i.e. h1) */ +.alt-heading { + font-weight: var(--title-20-font-weight); + font-size: var(--title-20-font-size); + + margin-block-start: 0; + margin-block-end: calc(var(--base-unit) * 4); +} + +.alt-heading--larger { + font-size: var(--title-30-font-size); + font-weight: var(--title-30-font-weight); +} + +/* Alternative style for a subheading (i.e. h2). It features an icon */ +/* +--------+-------------+ +* | [Icon] | Lorem ipsum | +* +--------+-------------+ +*/ +.alt-subheading { + margin-block-start: calc(var(--base-unit) * 4); + font-weight: 600; + font-size: 1.14em; + line-height: 1.4em; /* odd value - from common.inc.css */ + + display: grid; + grid-template-columns: var(--alt-heading-icon-size) 1fr; + grid-column-gap: var(--alt-heading-icon-gap); + align-items: center; +} + +.alt-subheading__icon { + width: 100%; + fill: currentColor; + -moz-context-properties: fill; +} + +/* +* Layout elements +*/ + +/* for horizontal rules / separators */ +.separator { + border-style: solid none none none; + border-color: var(--border-color); +} + +/* adds breathing space to the separator */ +.separator--breathe { + margin: calc(var(--base-unit) * 5) 0; +} + +/* a series of button-like elements, layed out horizontally */ +.toolbar { + display: flex; + column-gap: calc(var(--base-unit) * 3); +} + +.toolbar--right-align { + justify-content: end; +} + +/* +Form controls +*/ +.default-button, .default-input { + box-sizing: border-box; + font-size: 1em; +} + +/* Buttons from Photon */ +.default-button, .primary-button { + appearance: none; + margin: 0; + height: calc(var(--base-unit) * 8); + padding-inline-start: calc(var(--base-unit) * 5); + padding-inline-end: calc(var(--base-unit) * 5); + + border: none; + border-radius: calc(var(--base-unit) / 2); + + font-size: var(--button-font-size); +} + +/* Disabled state for buttons from Photon */ +.default-button:disabled, .primary-button:disabled { + opacity: 0.4; +} + +/* Smaller variant size for buttons, from Photon */ +.default-button--micro, .primary-button--micro { + padding-inline-start: calc(2 * var(--base-unit)); + padding-inline-end: calc(2 * var(--base-unit)); + font-size: var(--micro-font-size); + height: calc(var(--base-unit) * 6); +} + +/* Photon button representing a primary action */ +.primary-button { + color: var(--primary-button-color); + background-color: var(--primary-button-background-color); +} + +.primary-button:enabled:hover { + background: var(--primary-button-hover-background-color); +} + +.primary-button:enabled:active { + background: var(--primary-button-active-background-color); +} + +/* Photon standard button */ +.default-button { + color: var(--button-color); + background-color: var(--button-background-color); +} + +.default-button:enabled:hover { + background: var(--button-hover-background-color); +} + +.default-button:enabled:active { + background: var(--button-active-background-color); +} + +@media (prefers-contrast) { + .default-button, + .ghost-button, + .primary-button { + background-color: ButtonFace; + /* Add a border to make buttons visible in high contrast */ + border: 1px solid ButtonText; + color: ButtonText; + } + + .ghost-button { + fill: ButtonText; + } + + :is( + .default-button, + .ghost-button, + .primary-button + ):enabled:is(:hover, :active) { + background-color: ButtonText; + color: ButtonFace; + } +} + +/* Photon ghost button. Icon button with no background */ +.ghost-button { + background: transparent; + border: none; + border-radius: calc(var(--base-unit) / 2); + fill: var(--button-color); + height: calc(var(--base-unit) * 6); + padding: calc(var(--base-unit)); + width: calc(var(--base-unit) * 6); + + -moz-context-properties: fill; +} + +.ghost-button:hover { + background: var(--button-hover-background-color); +} + +.ghost-button:active { + background: var(--button-active-background-color); +} + +/* Standard inputs */ +.default-input { + line-height: unset; + padding: 0 calc(var(--base-unit) * 2); + height: 100%; + + border: 1px solid var(--box-border-color); + border-radius: 2px; + color: var(--text-color); + background-color: var(--box-background); +} + +/* +* Other UI components +*/ + +/* +* A small, colored badge. +* NOTE: styles borrowed from Photon's micro buttons (there aren't badges) +*/ +.badge { + background: var(--grey-30); + border-radius: calc(var(--base-unit) / 2); + font-size: var(--micro-font-size); + padding: var(--base-unit) calc(2 * var(--base-unit)); +} + +.badge--info { + background: var(--highlight-50); +} + +.badge--success { + background: var(--success-background); +} + +.badge--warning { + background: var(--warning-background); +} + +.badge--error { + background: var(--error-background); +} + +/* + * Card UI, from Photon + */ +.card { + background-color: var(--card-background-color); /* from common.inc.css */ + border-radius: var(--card-shadow-blur-radius); /* from common.inc.css */ + box-shadow: 0 1px 4px var(--grey-90-a10); /* from common.inc.css */ + box-sizing: border-box; + min-width: min-content; + padding-block: calc(var(--base-unit) * 5); +} + +.card__heading { + font-size: var(--title-20-font-size); /* Note: this is from Photon Title 20 */ + font-weight: var(--title-20-font-weight); /* Note: this is from Photon Title 20 */ +} diff --git a/devtools/client/aboutdebugging/src/components/App.css b/devtools/client/aboutdebugging/src/components/App.css new file mode 100644 index 0000000000..5196ce8e2e --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/App.css @@ -0,0 +1,71 @@ +/* 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/. */ + +/* + * The current layout of about:debugging is + * + * +-------------+-------------------------------+ + * | Sidebar | Page (Runtime or Connect) | + * | (240px) | | + * | | | + * +-------------+-------------------------------+ + * + * Some of the values (font sizes, widths, etc.) are the same as + * about:preferences, which uses the shared common.css + */ + +.app { + /* from common */ + --sidebar-width: 280px; + --app-top-padding: 70px; + --app-bottom-padding: 40px; + --app-left-padding: 32px; + --app-right-padding: 32px; + + box-sizing: border-box; + width: 100vw; + height: 100vh; + overflow: hidden; /* we don't want the sidebar to scroll, only the main content */ + + display: grid; + grid-column-gap: 40px; + grid-template-columns: var(--sidebar-width) auto; + + font-size: var(--base-font-size); + font-weight: var(--base-font-weight); + line-height: var(--base-line-height); +} + +.app__sidebar { + padding-block-start: var(--app-top-padding); + padding-block-end: var(--app-bottom-padding); + padding-inline-start: var(--app-left-padding); +} + +.app__content { + /* we want to scroll only the main content, not the sidebar */ + overflow-y: auto; + + /* padding will give space for card shadow to appear and + margin will correct the alignment */ + margin-inline-start: calc(var(--card-shadow-blur-radius) * -1); + padding-inline: var(--card-shadow-blur-radius); + padding-block-start: var(--app-top-padding); +} + +/* Workaround for Gecko clipping the padding-bottom of a scrollable container; + we create a block to act as the bottom padding instead. */ +.app__content::after { + content: ""; + display: block; + height: var(--app-bottom-padding); +} + +.page { + max-width: var(--page-width); + min-width: min-content; + font-size: var(--body-20-font-size); + font-weight: var(--body-20-font-weight); + padding-inline-end: var(--app-right-padding); +} diff --git a/devtools/client/aboutdebugging/src/components/App.js b/devtools/client/aboutdebugging/src/components/App.js new file mode 100644 index 0000000000..7bdf3eb0c5 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/App.js @@ -0,0 +1,213 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +const { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Route = createFactory( + require("resource://devtools/client/shared/vendor/react-router-dom.js").Route +); +const Switch = createFactory( + require("resource://devtools/client/shared/vendor/react-router-dom.js").Switch +); +const Redirect = createFactory( + require("resource://devtools/client/shared/vendor/react-router-dom.js") + .Redirect +); + +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); +const { + PAGE_TYPES, + RUNTIMES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +const ConnectPage = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/connect/ConnectPage.js") +); +const RuntimePage = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/RuntimePage.js") +); +const Sidebar = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/sidebar/Sidebar.js") +); + +class App extends PureComponent { + static get propTypes() { + return { + adbAddonStatus: Types.adbAddonStatus, + // The "dispatch" helper is forwarded to the App component via connect. + // From that point, components are responsible for forwarding the dispatch + // property to all components who need to dispatch actions. + dispatch: PropTypes.func.isRequired, + // getString prop is injected by the withLocalization wrapper + getString: PropTypes.func.isRequired, + isAdbReady: PropTypes.bool.isRequired, + isScanningUsb: PropTypes.bool.isRequired, + networkLocations: PropTypes.arrayOf(Types.location).isRequired, + networkRuntimes: PropTypes.arrayOf(Types.runtime).isRequired, + selectedPage: Types.page, + selectedRuntimeId: PropTypes.string, + usbRuntimes: PropTypes.arrayOf(Types.runtime).isRequired, + }; + } + + componentDidUpdate() { + this.updateTitle(); + } + + updateTitle() { + const { getString, selectedPage, selectedRuntimeId } = this.props; + + const pageTitle = + selectedPage === PAGE_TYPES.RUNTIME + ? getString("about-debugging-page-title-runtime-page", { + selectedRuntimeId, + }) + : getString("about-debugging-page-title-setup-page"); + + document.title = pageTitle; + } + + renderConnect() { + const { adbAddonStatus, dispatch, networkLocations } = this.props; + + return ConnectPage({ + adbAddonStatus, + dispatch, + networkLocations, + }); + } + + // The `match` object here is passed automatically by the Route object. + // We are using it to read the route path. + // See react-router docs: + // https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/match.md + renderRuntime({ match }) { + const isRuntimeAvailable = id => { + const runtimes = [ + ...this.props.networkRuntimes, + ...this.props.usbRuntimes, + ]; + const runtime = runtimes.find(x => x.id === id); + return runtime?.runtimeDetails; + }; + + const { dispatch } = this.props; + + let runtimeId = match.params.runtimeId || RUNTIMES.THIS_FIREFOX; + if (match.params.runtimeId !== RUNTIMES.THIS_FIREFOX) { + const rawId = decodeURIComponent(match.params.runtimeId); + if (isRuntimeAvailable(rawId)) { + runtimeId = rawId; + } else { + // Also redirect to "This Firefox" if runtime is not found + return Redirect({ to: `/runtime/${RUNTIMES.THIS_FIREFOX}` }); + } + } + + // we need to pass a key so the component updates when we want to showcase + // a different runtime + return RuntimePage({ dispatch, key: runtimeId, runtimeId }); + } + + renderRoutes() { + return Switch( + {}, + Route({ + path: "/setup", + render: () => this.renderConnect(), + }), + Route({ + path: "/runtime/:runtimeId", + render: routeProps => this.renderRuntime(routeProps), + }), + // default route when there's no match which includes "/" + // TODO: the url does not match "/" means invalid URL, + // in this case maybe we'd like to do something else than a redirect. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1509897 + Route({ + render: routeProps => { + const { pathname } = routeProps.location; + // The old about:debugging supported the following routes: + // about:debugging#workers, about:debugging#addons and about:debugging#tabs. + // Such links can still be found in external documentation pages. + // We redirect to This Firefox rather than the Setup Page here. + if ( + pathname === "/workers" || + pathname === "/addons" || + pathname === "/tabs" + ) { + return Redirect({ to: `/runtime/${RUNTIMES.THIS_FIREFOX}` }); + } + return Redirect({ to: "/setup" }); + }, + }) + ); + } + + render() { + const { + adbAddonStatus, + dispatch, + isAdbReady, + isScanningUsb, + networkRuntimes, + selectedPage, + selectedRuntimeId, + usbRuntimes, + } = this.props; + + return Localized( + {}, + dom.div( + { className: "app" }, + Sidebar({ + adbAddonStatus, + className: "app__sidebar", + dispatch, + isAdbReady, + isScanningUsb, + networkRuntimes, + selectedPage, + selectedRuntimeId, + usbRuntimes, + }), + dom.main({ className: "app__content" }, this.renderRoutes()) + ) + ); + } +} + +const mapStateToProps = state => { + return { + adbAddonStatus: state.ui.adbAddonStatus, + isAdbReady: state.ui.isAdbReady, + isScanningUsb: state.ui.isScanningUsb, + networkLocations: state.ui.networkLocations, + networkRuntimes: state.runtimes.networkRuntimes, + selectedPage: state.ui.selectedPage, + selectedRuntimeId: state.runtimes.selectedRuntimeId, + usbRuntimes: state.runtimes.usbRuntimes, + }; +}; + +const mapDispatchToProps = dispatch => ({ + dispatch, +}); + +module.exports = FluentReact.withLocalization( + connect(mapStateToProps, mapDispatchToProps)(App) +); diff --git a/devtools/client/aboutdebugging/src/components/CompatibilityWarning.js b/devtools/client/aboutdebugging/src/components/CompatibilityWarning.js new file mode 100644 index 0000000000..42284fa672 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/CompatibilityWarning.js @@ -0,0 +1,110 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Message = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js") +); + +const { + MESSAGE_LEVEL, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const { + COMPATIBILITY_STATUS, +} = require("resource://devtools/client/shared/remote-debugging/version-checker.js"); + +const TROUBLESHOOTING_URL = + "https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/"; +const FENNEC_TROUBLESHOOTING_URL = + "https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/index.html#connection-to-firefox-for-android-68"; + +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +class CompatibilityWarning extends PureComponent { + static get propTypes() { + return { + compatibilityReport: Types.compatibilityReport.isRequired, + }; + } + + render() { + const { + localID, + localVersion, + minVersion, + runtimeID, + runtimeVersion, + status, + } = this.props.compatibilityReport; + + if (status === COMPATIBILITY_STATUS.COMPATIBLE) { + return null; + } + + let localizationId, statusClassName; + switch (status) { + case COMPATIBILITY_STATUS.TOO_OLD: + statusClassName = "qa-compatibility-warning-too-old"; + localizationId = "about-debugging-browser-version-too-old"; + break; + case COMPATIBILITY_STATUS.TOO_RECENT: + statusClassName = "qa-compatibility-warning-too-recent"; + localizationId = "about-debugging-browser-version-too-recent"; + break; + case COMPATIBILITY_STATUS.TOO_OLD_FENNEC: + statusClassName = "qa-compatibility-warning-too-old-fennec"; + localizationId = "about-debugging-browser-version-too-old-fennec"; + break; + } + + const troubleshootingUrl = + status === COMPATIBILITY_STATUS.TOO_OLD_FENNEC + ? FENNEC_TROUBLESHOOTING_URL + : TROUBLESHOOTING_URL; + + const messageLevel = + status === COMPATIBILITY_STATUS.TOO_OLD_FENNEC + ? MESSAGE_LEVEL.ERROR + : MESSAGE_LEVEL.WARNING; + + return Message( + { + level: messageLevel, + isCloseable: true, + }, + Localized( + { + id: localizationId, + a: dom.a({ + href: troubleshootingUrl, + target: "_blank", + }), + $localID: localID, + $localVersion: localVersion, + $minVersion: minVersion, + $runtimeID: runtimeID, + $runtimeVersion: runtimeVersion, + }, + dom.p( + { + className: `qa-compatibility-warning ${statusClassName}`, + }, + localizationId + ) + ) + ); + } +} + +module.exports = CompatibilityWarning; diff --git a/devtools/client/aboutdebugging/src/components/ConnectionPromptSetting.js b/devtools/client/aboutdebugging/src/components/ConnectionPromptSetting.js new file mode 100644 index 0000000000..d4773bb298 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/ConnectionPromptSetting.js @@ -0,0 +1,55 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); + +class ConnectionPromptSetting extends PureComponent { + static get propTypes() { + return { + className: PropTypes.string, + connectionPromptEnabled: PropTypes.bool.isRequired, + dispatch: PropTypes.func.isRequired, + }; + } + + onToggleClick() { + const { connectionPromptEnabled, dispatch } = this.props; + dispatch(Actions.updateConnectionPromptSetting(!connectionPromptEnabled)); + } + + render() { + const { className, connectionPromptEnabled } = this.props; + + const localizedState = connectionPromptEnabled + ? "about-debugging-connection-prompt-disable-button" + : "about-debugging-connection-prompt-enable-button"; + + return Localized( + { + id: localizedState, + }, + dom.button( + { + className: `${className} default-button qa-connection-prompt-toggle-button`, + onClick: () => this.onToggleClick(), + }, + localizedState + ) + ); + } +} + +module.exports = ConnectionPromptSetting; diff --git a/devtools/client/aboutdebugging/src/components/ProfilerDialog.css b/devtools/client/aboutdebugging/src/components/ProfilerDialog.css new file mode 100644 index 0000000000..d5352bbea2 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/ProfilerDialog.css @@ -0,0 +1,63 @@ +/* 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/. */ + +.profiler-dialog__frame { + border: none; + height: 100%; + width: 100%; +} + +/* + * The current layout of the dialog header is + * + * +-----------------------------+---+ + * | dialog title (auto) | X | + * +-----------------------------+---+ + */ +.profiler-dialog__header { + align-items: center; + background-color: var(--popup-header-background-color); + color: var(--popup-header-color); + display: grid; + grid-template-columns: 1fr max-content; + padding: var(--base-unit); +} + +.profiler-dialog__header__title { + margin-inline-start: calc(var(--base-unit) * 2); + + /* Reset

styles */ + font-size: 15px; + font-weight: normal; +} + +.profiler-dialog__inner { + background-color: var(--box-background); + display: grid; + grid-template-rows: max-content auto; + max-height: calc(100% - calc(var(--base-unit) * 25)); /* 100% - 100px */ + position: fixed; +} + +.profiler-dialog__inner--medium { + width: calc(var(--base-unit) * 150); /* 600px */ + height: calc(var(--base-unit) * 150); /* 600px */ +} + +.profiler-dialog__inner--large { + width: calc(var(--base-unit) * 200); /* 800px */ + height: calc(var(--base-unit) * 175); /* 700px */ +} + +.profiler-dialog__mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--grey-90-a60); + display: flex; + align-items: center; + justify-content: center; +} diff --git a/devtools/client/aboutdebugging/src/components/ProfilerDialog.js b/devtools/client/aboutdebugging/src/components/ProfilerDialog.js new file mode 100644 index 0000000000..f4bb583464 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/ProfilerDialog.js @@ -0,0 +1,168 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +const { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); +const { + PROFILER_PAGE_CONTEXT, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +/** + * This component is a modal dialog containing the performance profiler UI. It uses + * the simplified DevTools panel located in devtools/client/performance-new. When + * using a custom preset, and editing the settings, the page context switches + * to about:profiling, which receives the PerfFront of the remote debuggee. + */ +class ProfilerDialog extends PureComponent { + static get propTypes() { + return { + runtimeDetails: Types.runtimeDetails.isRequired, + profilerContext: PropTypes.string.isRequired, + hideProfilerDialog: PropTypes.func.isRequired, + switchProfilerContext: PropTypes.func.isRequired, + }; + } + + hide() { + this.props.hideProfilerDialog(); + } + + setProfilerIframeDirection(frameWindow) { + // Set iframe direction according to the parent document direction. + const { documentElement } = document; + const dir = window.getComputedStyle(documentElement).direction; + frameWindow.document.documentElement.setAttribute("dir", dir); + } + + /** + * The profiler iframe can either be the simplified devtools recording panel, + * or the more detailed about:profiling settings page. + */ + renderProfilerIframe() { + const { + runtimeDetails: { clientWrapper }, + switchProfilerContext, + profilerContext, + } = this.props; + + let src, onLoad; + + switch (profilerContext) { + case PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE: + src = clientWrapper.getPerformancePanelUrl(); + onLoad = e => { + const frameWindow = e.target.contentWindow; + this.setProfilerIframeDirection(frameWindow); + + clientWrapper.loadPerformanceProfiler(frameWindow, () => { + switchProfilerContext(PROFILER_PAGE_CONTEXT.ABOUTPROFILING_REMOTE); + }); + }; + break; + + case PROFILER_PAGE_CONTEXT.ABOUTPROFILING_REMOTE: + src = "about:profiling#remote"; + onLoad = e => { + const frameWindow = e.target.contentWindow; + this.setProfilerIframeDirection(frameWindow); + + clientWrapper.loadAboutProfiling(frameWindow, () => { + switchProfilerContext(PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE); + }); + }; + break; + + default: + throw new Error(`Unhandled profiler context: "${profilerContext}"`); + } + + return dom.iframe({ + key: profilerContext, + className: "profiler-dialog__frame", + src, + onLoad, + }); + } + + render() { + const { profilerContext, switchProfilerContext } = this.props; + const dialogSizeClassName = + profilerContext === PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE + ? "profiler-dialog__inner--medium" + : "profiler-dialog__inner--large"; + + return dom.div( + { + className: "profiler-dialog__mask qa-profiler-dialog-mask", + onClick: () => this.hide(), + }, + dom.article( + { + className: `profiler-dialog__inner ${dialogSizeClassName} qa-profiler-dialog`, + onClick: e => e.stopPropagation(), + }, + dom.header( + { + className: "profiler-dialog__header", + }, + Localized( + { + id: "about-debugging-profiler-dialog-title2", + }, + dom.h1( + { + className: "profiler-dialog__header__title", + }, + "about-debugging-profiler-dialog-title2" + ) + ), + dom.button( + { + className: "ghost-button qa-profiler-dialog-close", + onClick: () => { + if (profilerContext === PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE) { + this.hide(); + } else { + switchProfilerContext(PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE); + } + }, + }, + dom.img({ + src: "chrome://devtools/skin/images/close.svg", + }) + ) + ), + this.renderProfilerIframe() + ) + ); + } +} + +const mapStateToProps = state => { + return { + profilerContext: state.ui.profilerContext, + }; +}; + +const mapDispatchToProps = { + hideProfilerDialog: Actions.hideProfilerDialog, + switchProfilerContext: Actions.switchProfilerContext, +}; + +module.exports = connect(mapStateToProps, mapDispatchToProps)(ProfilerDialog); diff --git a/devtools/client/aboutdebugging/src/components/RuntimeActions.css b/devtools/client/aboutdebugging/src/components/RuntimeActions.css new file mode 100644 index 0000000000..6333560d4b --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/RuntimeActions.css @@ -0,0 +1,9 @@ +/* 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/. */ + +.runtime-actions__toolbar { + column-gap: var(--base-unit); + display: flex; + justify-content: end; +} diff --git a/devtools/client/aboutdebugging/src/components/RuntimeActions.js b/devtools/client/aboutdebugging/src/components/RuntimeActions.js new file mode 100644 index 0000000000..eefa8b500b --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/RuntimeActions.js @@ -0,0 +1,82 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const ConnectionPromptSetting = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/ConnectionPromptSetting.js") +); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const { + RUNTIMES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +class RuntimeActions extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + runtimeDetails: Types.runtimeDetails, + runtimeId: PropTypes.string.isRequired, + }; + } + + onProfilerButtonClick() { + this.props.dispatch(Actions.showProfilerDialog()); + } + + renderConnectionPromptSetting() { + const { dispatch, runtimeDetails, runtimeId } = this.props; + const { connectionPromptEnabled } = runtimeDetails; + // do not show the connection prompt setting in 'This Firefox' + return runtimeId !== RUNTIMES.THIS_FIREFOX + ? ConnectionPromptSetting({ + connectionPromptEnabled, + dispatch, + }) + : null; + } + + renderProfileButton() { + const { runtimeId } = this.props; + + return runtimeId !== RUNTIMES.THIS_FIREFOX + ? Localized( + { + id: "about-debugging-runtime-profile-button2", + }, + dom.button( + { + className: "default-button qa-profile-runtime-button", + onClick: () => this.onProfilerButtonClick(), + }, + "about-debugging-runtime-profile-button2" + ) + ) + : null; + } + + render() { + return dom.div( + { + className: "runtime-actions__toolbar", + }, + this.renderProfileButton(), + this.renderConnectionPromptSetting() + ); + } +} + +module.exports = RuntimeActions; diff --git a/devtools/client/aboutdebugging/src/components/RuntimeInfo.css b/devtools/client/aboutdebugging/src/components/RuntimeInfo.css new file mode 100644 index 0000000000..e6fcd9dd7e --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/RuntimeInfo.css @@ -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/. */ + + +/** + * Layout for the runtime info container is: + * + * <- 68px --x--------- 1fr ----------><---- max ----> + * ∧ +---------+------------------------+--------------+ + * 1fr | | Runtime Info | [Action] | + * | | Icon | eg "Firefox (70.0a1)" | | + * x | +------------------------+ | + * max | | Device Name (optional) | | + * ∨ +---------+------------------------+--------------+ + */ +.runtime-info { + align-items: center; + display: grid; + + grid-column-gap: var(--main-heading-icon-gap); + grid-template-areas: + "icon title action" + "icon subtitle ."; + grid-template-columns: var(--main-heading-icon-size) 1fr max-content; + grid-template-rows: 1fr max-content; + + margin-block-end: calc(var(--base-unit) * 5); +} + +.runtime-info__icon { + grid-area: icon; +} +.runtime-info__title { + grid-area: title; +} +.runtime-info__subtitle { + grid-area: subtitle; +} +.runtime-info__action { + grid-area: action; +} diff --git a/devtools/client/aboutdebugging/src/components/RuntimeInfo.js b/devtools/client/aboutdebugging/src/components/RuntimeInfo.js new file mode 100644 index 0000000000..6a8c67dd33 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/RuntimeInfo.js @@ -0,0 +1,89 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const { + RUNTIMES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +/** + * This component displays runtime information. + */ +class RuntimeInfo extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + icon: PropTypes.string.isRequired, + deviceName: PropTypes.string, + name: PropTypes.string.isRequired, + version: PropTypes.string.isRequired, + runtimeId: PropTypes.string.isRequired, + }; + } + render() { + const { icon, deviceName, name, version, runtimeId, dispatch } = this.props; + + return dom.h1( + { + className: "main-heading runtime-info", + }, + dom.img({ + className: "main-heading__icon runtime-info__icon qa-runtime-icon", + src: icon, + }), + Localized( + { + id: "about-debugging-runtime-name", + $name: name, + $version: version, + }, + dom.label( + { + className: "qa-runtime-name runtime-info__title", + }, + `${name} (${version})` + ) + ), + deviceName + ? dom.label( + { + className: "main-heading-subtitle runtime-info__subtitle", + }, + deviceName + ) + : null, + runtimeId !== RUNTIMES.THIS_FIREFOX + ? Localized( + { + id: "about-debugging-runtime-disconnect-button", + }, + dom.button( + { + className: + "default-button runtime-info__action qa-runtime-info__action", + onClick() { + dispatch(Actions.disconnectRuntime(runtimeId, true)); + }, + }, + "Disconnect" + ) + ) + : null + ); + } +} + +module.exports = RuntimeInfo; diff --git a/devtools/client/aboutdebugging/src/components/RuntimePage.js b/devtools/client/aboutdebugging/src/components/RuntimePage.js new file mode 100644 index 0000000000..e2dae9b0cd --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/RuntimePage.js @@ -0,0 +1,306 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +const { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const CompatibilityWarning = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/CompatibilityWarning.js") +); +const DebugTargetPane = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js") +); +const ExtensionDetail = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js") +); +const InspectAction = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js") +); +const ProfilerDialog = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/ProfilerDialog.js") +); +const RuntimeActions = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/RuntimeActions.js") +); +const RuntimeInfo = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/RuntimeInfo.js") +); +const ServiceWorkerAction = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js") +); +const ServiceWorkerAdditionalActions = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js") +); +const ServiceWorkersWarning = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/ServiceWorkersWarning.js") +); +const ProcessDetail = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js") +); +const TabAction = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js") +); +const TabDetail = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js") +); +const TemporaryExtensionAdditionalActions = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js") +); +const TemporaryExtensionDetail = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js") +); +const TemporaryExtensionInstallSection = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js") +); +const WorkerDetail = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js") +); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const { + DEBUG_TARGETS, + DEBUG_TARGET_PANE, + PAGE_TYPES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +const { + getCurrentRuntimeDetails, +} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js"); +const { + isSupportedDebugTargetPane, + supportsTemporaryExtensionInstaller, +} = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js"); + +class RuntimePage extends PureComponent { + static get propTypes() { + return { + collapsibilities: Types.collapsibilities.isRequired, + dispatch: PropTypes.func.isRequired, + installedExtensions: PropTypes.arrayOf(PropTypes.object).isRequired, + otherWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, + runtimeDetails: Types.runtimeDetails, + runtimeId: PropTypes.string.isRequired, + processes: PropTypes.arrayOf(PropTypes.object).isRequired, + serviceWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, + sharedWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, + showProfilerDialog: PropTypes.bool.isRequired, + tabs: PropTypes.arrayOf(PropTypes.object).isRequired, + temporaryExtensions: PropTypes.arrayOf(PropTypes.object).isRequired, + temporaryInstallError: PropTypes.object, + }; + } + + // TODO: avoid the use of this method + // https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillMount() { + const { dispatch, runtimeId } = this.props; + dispatch(Actions.selectPage(PAGE_TYPES.RUNTIME, runtimeId)); + } + + getIconByType(type) { + switch (type) { + case DEBUG_TARGETS.EXTENSION: + return "chrome://devtools/skin/images/debugging-addons.svg"; + case DEBUG_TARGETS.PROCESS: + return "chrome://devtools/skin/images/aboutdebugging-process-icon.svg"; + case DEBUG_TARGETS.TAB: + return "chrome://devtools/skin/images/debugging-tabs.svg"; + case DEBUG_TARGETS.WORKER: + return "chrome://devtools/skin/images/debugging-workers.svg"; + } + + throw new Error(`Unsupported type [${type}]`); + } + + renderDebugTargetPane({ + actionComponent, + additionalActionsComponent, + children, + detailComponent, + icon, + localizationId, + name, + paneKey, + targets, + }) { + const { collapsibilities, dispatch, runtimeDetails } = this.props; + + if (!isSupportedDebugTargetPane(runtimeDetails.info.type, paneKey)) { + return null; + } + + return Localized( + { + id: localizationId, + attrs: { name: true }, + }, + DebugTargetPane( + { + actionComponent, + additionalActionsComponent, + collapsibilityKey: paneKey, + detailComponent, + dispatch, + icon, + isCollapsed: collapsibilities.get(paneKey), + name, + targets, + }, + children + ) + ); + } + + renderTemporaryExtensionInstallSection() { + const runtimeType = this.props.runtimeDetails.info.type; + if ( + !isSupportedDebugTargetPane( + runtimeType, + DEBUG_TARGET_PANE.TEMPORARY_EXTENSION + ) || + !supportsTemporaryExtensionInstaller(runtimeType) + ) { + return null; + } + + const { dispatch, temporaryInstallError } = this.props; + return TemporaryExtensionInstallSection({ + dispatch, + temporaryInstallError, + }); + } + + render() { + const { + dispatch, + installedExtensions, + otherWorkers, + processes, + runtimeDetails, + runtimeId, + serviceWorkers, + sharedWorkers, + showProfilerDialog, + tabs, + temporaryExtensions, + } = this.props; + + if (!runtimeDetails) { + // runtimeInfo can be null when the selectPage action navigates from a runtime A + // to a runtime B (between unwatchRuntime and watchRuntime). + return null; + } + + const { compatibilityReport } = runtimeDetails; + + return dom.article( + { + className: "page qa-runtime-page", + }, + RuntimeInfo({ ...runtimeDetails.info, runtimeId, dispatch }), + RuntimeActions({ dispatch, runtimeId, runtimeDetails }), + runtimeDetails.serviceWorkersAvailable ? null : ServiceWorkersWarning(), + CompatibilityWarning({ compatibilityReport }), + this.renderDebugTargetPane({ + actionComponent: TabAction, + detailComponent: TabDetail, + icon: this.getIconByType(DEBUG_TARGETS.TAB), + localizationId: "about-debugging-runtime-tabs", + name: "Tabs", + paneKey: DEBUG_TARGET_PANE.TAB, + targets: tabs, + }), + this.renderDebugTargetPane({ + actionComponent: InspectAction, + additionalActionsComponent: TemporaryExtensionAdditionalActions, + children: this.renderTemporaryExtensionInstallSection(), + detailComponent: TemporaryExtensionDetail, + icon: this.getIconByType(DEBUG_TARGETS.EXTENSION), + localizationId: "about-debugging-runtime-temporary-extensions", + name: "Temporary Extensions", + paneKey: DEBUG_TARGET_PANE.TEMPORARY_EXTENSION, + targets: temporaryExtensions, + }), + this.renderDebugTargetPane({ + actionComponent: InspectAction, + detailComponent: ExtensionDetail, + icon: this.getIconByType(DEBUG_TARGETS.EXTENSION), + localizationId: "about-debugging-runtime-extensions", + name: "Extensions", + paneKey: DEBUG_TARGET_PANE.INSTALLED_EXTENSION, + targets: installedExtensions, + }), + this.renderDebugTargetPane({ + actionComponent: ServiceWorkerAction, + additionalActionsComponent: ServiceWorkerAdditionalActions, + detailComponent: WorkerDetail, + icon: this.getIconByType(DEBUG_TARGETS.WORKER), + localizationId: "about-debugging-runtime-service-workers", + name: "Service Workers", + paneKey: DEBUG_TARGET_PANE.SERVICE_WORKER, + targets: serviceWorkers, + }), + this.renderDebugTargetPane({ + actionComponent: InspectAction, + detailComponent: WorkerDetail, + icon: this.getIconByType(DEBUG_TARGETS.WORKER), + localizationId: "about-debugging-runtime-shared-workers", + name: "Shared Workers", + paneKey: DEBUG_TARGET_PANE.SHARED_WORKER, + targets: sharedWorkers, + }), + this.renderDebugTargetPane({ + actionComponent: InspectAction, + detailComponent: WorkerDetail, + icon: this.getIconByType(DEBUG_TARGETS.WORKER), + localizationId: "about-debugging-runtime-other-workers", + name: "Other Workers", + paneKey: DEBUG_TARGET_PANE.OTHER_WORKER, + targets: otherWorkers, + }), + this.renderDebugTargetPane({ + actionComponent: InspectAction, + detailComponent: ProcessDetail, + icon: this.getIconByType(DEBUG_TARGETS.PROCESS), + localizationId: "about-debugging-runtime-processes", + name: "Processes", + paneKey: DEBUG_TARGET_PANE.PROCESSES, + targets: processes, + }), + + showProfilerDialog ? ProfilerDialog({ dispatch, runtimeDetails }) : null + ); + } +} + +const mapStateToProps = state => { + return { + collapsibilities: state.ui.debugTargetCollapsibilities, + installedExtensions: state.debugTargets.installedExtensions, + processes: state.debugTargets.processes, + otherWorkers: state.debugTargets.otherWorkers, + runtimeDetails: getCurrentRuntimeDetails(state.runtimes), + serviceWorkers: state.debugTargets.serviceWorkers, + sharedWorkers: state.debugTargets.sharedWorkers, + showProfilerDialog: state.ui.showProfilerDialog, + tabs: state.debugTargets.tabs, + temporaryExtensions: state.debugTargets.temporaryExtensions, + temporaryInstallError: state.ui.temporaryInstallError, + }; +}; + +module.exports = connect(mapStateToProps)(RuntimePage); diff --git a/devtools/client/aboutdebugging/src/components/ServiceWorkersWarning.js b/devtools/client/aboutdebugging/src/components/ServiceWorkersWarning.js new file mode 100644 index 0000000000..4f9dc93d7f --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/ServiceWorkersWarning.js @@ -0,0 +1,52 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Message = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js") +); + +const { + MESSAGE_LEVEL, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const DOC_URL = + "https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/index.html#service-workers-not-compatible"; + +class ServiceWorkersWarning extends PureComponent { + render() { + return Message( + { + level: MESSAGE_LEVEL.WARNING, + isCloseable: true, + }, + Localized( + { + id: "about-debugging-runtime-service-workers-not-compatible", + a: dom.a({ + href: DOC_URL, + target: "_blank", + }), + }, + dom.p( + { + className: "qa-service-workers-warning", + }, + "about-debugging-runtime-service-workers-not-compatible" + ) + ) + ); + } +} + +module.exports = ServiceWorkersWarning; diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css new file mode 100644 index 0000000000..a693bf4113 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css @@ -0,0 +1,50 @@ +/* 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/. */ + +.connect-page__breather { + margin-block-start: calc(var(--base-unit) * 6); +} + +/* + * +--------+----------------------+ + * | USB | |