diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /devtools/client/aboutdebugging/src/actions | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/aboutdebugging/src/actions')
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/debug-targets.js | 316 | ||||
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/index.js | 12 | ||||
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/moz.build | 11 | ||||
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/runtimes.js | 513 | ||||
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/telemetry.js | 23 | ||||
-rw-r--r-- | devtools/client/aboutdebugging/src/actions/ui.js | 202 |
6 files changed, 1077 insertions, 0 deletions
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..1d0fcb7da9 --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/debug-targets.js @@ -0,0 +1,316 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { AddonManager } = require("resource://gre/modules/AddonManager.jsm"); +const { + remoteClientManager, +} = require("devtools/client/shared/remote-debugging/remote-client-manager"); +const Services = require("Services"); + +const { l10n } = require("devtools/client/aboutdebugging/src/modules/l10n"); + +const { + isSupportedDebugTargetPane, +} = require("devtools/client/aboutdebugging/src/modules/debug-target-support"); + +const { + openTemporaryExtension, + uninstallAddon, +} = require("devtools/client/aboutdebugging/src/modules/extensions-helper"); + +const { + getCurrentClient, + getCurrentRuntime, +} = require("devtools/client/aboutdebugging/src/modules/runtimes-state-helper"); + +const { + 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, + RUNTIMES, +} = require("devtools/client/aboutdebugging/src/constants"); + +const Actions = require("devtools/client/aboutdebugging/src/actions/index"); + +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); + + const urlParams = { + id, + type, + }; + + 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, + // which is only true if dom.serviceWorkers.parent_intercept 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 () => { + try { + await uninstallAddon(id); + } catch (e) { + console.error(e); + } + }; +} + +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, + 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..e9dac32eeb --- /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("devtools/client/aboutdebugging/src/actions/debug-targets"); +const runtimes = require("devtools/client/aboutdebugging/src/actions/runtimes"); +const telemetry = require("devtools/client/aboutdebugging/src/actions/telemetry"); +const ui = require("devtools/client/aboutdebugging/src/actions/ui"); + +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..3d9ce0490b --- /dev/null +++ b/devtools/client/aboutdebugging/src/actions/runtimes.js @@ -0,0 +1,513 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Services = require("Services"); + +const Actions = require("devtools/client/aboutdebugging/src/actions/index"); + +const { + getAllRuntimes, + getCurrentRuntime, + findRuntimeById, +} = require("devtools/client/aboutdebugging/src/modules/runtimes-state-helper"); + +const { l10n } = require("devtools/client/aboutdebugging/src/modules/l10n"); +const { + setDefaultPreferencesIfNeeded, + DEFAULT_PREFERENCES, +} = require("devtools/client/aboutdebugging/src/modules/runtime-default-preferences"); +const { + createClientForRuntime, +} = require("devtools/client/aboutdebugging/src/modules/runtime-client-factory"); +const { + isSupportedDebugTargetPane, +} = require("devtools/client/aboutdebugging/src/modules/debug-target-support"); + +const { + remoteClientManager, +} = require("devtools/client/shared/remote-debugging/remote-client-manager"); + +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("devtools/client/aboutdebugging/src/constants"); + +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, + 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..ef69bdcfd0 --- /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("devtools/client/aboutdebugging/src/constants"); + +/** + * 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..e99786781c --- /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("devtools/client/aboutdebugging/src/constants"); + +const NetworkLocationsModule = require("devtools/client/aboutdebugging/src/modules/network-locations"); +const { + adbAddon, +} = require("devtools/client/shared/remote-debugging/adb/adb-addon"); +const { + refreshUSBRuntimes, +} = require("devtools/client/aboutdebugging/src/modules/usb-runtimes"); + +const Actions = require("devtools/client/aboutdebugging/src/actions/index"); + +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, +}; |