tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

debug-targets.js (10516B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { AddonManager } = ChromeUtils.importESModule(
      8  "resource://gre/modules/AddonManager.sys.mjs",
      9  // AddonManager is a singleton, never create two instances of it.
     10  { global: "shared" }
     11 );
     12 const {
     13  remoteClientManager,
     14 } = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js");
     15 
     16 const {
     17  l10n,
     18 } = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js");
     19 
     20 const {
     21  isSupportedDebugTargetPane,
     22 } = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js");
     23 
     24 const {
     25  openTemporaryExtension,
     26 } = require("resource://devtools/client/aboutdebugging/src/modules/extensions-helper.js");
     27 
     28 const {
     29  getCurrentClient,
     30  getCurrentRuntime,
     31 } = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
     32 
     33 const {
     34  gDevTools,
     35 } = require("resource://devtools/client/framework/devtools.js");
     36 
     37 const {
     38  DEBUG_TARGETS,
     39  DEBUG_TARGET_PANE,
     40  REQUEST_EXTENSIONS_FAILURE,
     41  REQUEST_EXTENSIONS_START,
     42  REQUEST_EXTENSIONS_SUCCESS,
     43  REQUEST_PROCESSES_FAILURE,
     44  REQUEST_PROCESSES_START,
     45  REQUEST_PROCESSES_SUCCESS,
     46  REQUEST_TABS_FAILURE,
     47  REQUEST_TABS_START,
     48  REQUEST_TABS_SUCCESS,
     49  REQUEST_WORKERS_FAILURE,
     50  REQUEST_WORKERS_START,
     51  REQUEST_WORKERS_SUCCESS,
     52  TEMPORARY_EXTENSION_INSTALL_FAILURE,
     53  TEMPORARY_EXTENSION_INSTALL_START,
     54  TEMPORARY_EXTENSION_INSTALL_SUCCESS,
     55  TEMPORARY_EXTENSION_RELOAD_FAILURE,
     56  TEMPORARY_EXTENSION_RELOAD_START,
     57  TEMPORARY_EXTENSION_RELOAD_SUCCESS,
     58  TERMINATE_EXTENSION_BGSCRIPT_FAILURE,
     59  TERMINATE_EXTENSION_BGSCRIPT_SUCCESS,
     60  TERMINATE_EXTENSION_BGSCRIPT_START,
     61  RUNTIMES,
     62 } = require("resource://devtools/client/aboutdebugging/src/constants.js");
     63 
     64 const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
     65 
     66 function getTabForUrl(url) {
     67  for (const navigator of Services.wm.getEnumerator("navigator:browser")) {
     68    for (const browser of navigator.gBrowser.browsers) {
     69      if (
     70        browser.contentWindow &&
     71        browser.contentWindow.location.href === url
     72      ) {
     73        return navigator.gBrowser.getTabForBrowser(browser);
     74      }
     75    }
     76  }
     77 
     78  return null;
     79 }
     80 
     81 function inspectDebugTarget(type, id) {
     82  return async ({ dispatch, getState }) => {
     83    const runtime = getCurrentRuntime(getState().runtimes);
     84 
     85    if (
     86      type == DEBUG_TARGETS.EXTENSION &&
     87      runtime.id === RUNTIMES.THIS_FIREFOX
     88    ) {
     89      // Bug 1780912: To avoid UX issues when debugging local web extensions,
     90      // we are opening the toolbox in an independant window.
     91      // Whereas all others are opened in new tabs.
     92      gDevTools.showToolboxForWebExtension(id);
     93    } else {
     94      const urlParams = {
     95        type,
     96      };
     97      // Main process may not provide any ID.
     98      if (id) {
     99        urlParams.id = id;
    100      }
    101 
    102      if (runtime.id !== RUNTIMES.THIS_FIREFOX) {
    103        urlParams.remoteId = remoteClientManager.getRemoteId(
    104          runtime.id,
    105          runtime.type
    106        );
    107      }
    108 
    109      const url = `about:devtools-toolbox?${new window.URLSearchParams(
    110        urlParams
    111      )}`;
    112 
    113      const existingTab = getTabForUrl(url);
    114      if (existingTab) {
    115        const navigator = existingTab.ownerGlobal;
    116        navigator.gBrowser.selectedTab = existingTab;
    117        navigator.focus();
    118      } else {
    119        window.open(url);
    120      }
    121    }
    122 
    123    dispatch(
    124      Actions.recordTelemetryEvent("inspect", {
    125        target_type: type.toUpperCase(),
    126        runtime_type: runtime.type,
    127      })
    128    );
    129  };
    130 }
    131 
    132 function installTemporaryExtension() {
    133  const message = l10n.getString(
    134    "about-debugging-tmp-extension-install-message"
    135  );
    136  return async ({ dispatch }) => {
    137    dispatch({ type: TEMPORARY_EXTENSION_INSTALL_START });
    138    const file = await openTemporaryExtension(window, message);
    139    try {
    140      await AddonManager.installTemporaryAddon(file);
    141      dispatch({ type: TEMPORARY_EXTENSION_INSTALL_SUCCESS });
    142    } catch (e) {
    143      dispatch({ type: TEMPORARY_EXTENSION_INSTALL_FAILURE, error: e });
    144    }
    145  };
    146 }
    147 
    148 function pushServiceWorker(id, registrationFront) {
    149  return async () => {
    150    try {
    151      // The push button is only available if canDebugServiceWorkers is true.
    152      // With this configuration, `push` should always be called on the
    153      // registration front, and not on the (service) WorkerTargetActor.
    154      await registrationFront.push();
    155    } catch (e) {
    156      console.error(e);
    157    }
    158  };
    159 }
    160 
    161 function reloadTemporaryExtension(id) {
    162  return async ({ dispatch, getState }) => {
    163    dispatch({ type: TEMPORARY_EXTENSION_RELOAD_START, id });
    164    const clientWrapper = getCurrentClient(getState().runtimes);
    165 
    166    try {
    167      const addonTargetFront = await clientWrapper.getAddon({ id });
    168      await addonTargetFront.reload();
    169      dispatch({ type: TEMPORARY_EXTENSION_RELOAD_SUCCESS, id });
    170    } catch (e) {
    171      const error = typeof e === "string" ? new Error(e) : e;
    172      dispatch({ type: TEMPORARY_EXTENSION_RELOAD_FAILURE, id, error });
    173    }
    174  };
    175 }
    176 
    177 function removeTemporaryExtension(id) {
    178  return async ({ getState }) => {
    179    const clientWrapper = getCurrentClient(getState().runtimes);
    180 
    181    try {
    182      await clientWrapper.uninstallAddon({ id });
    183    } catch (e) {
    184      console.error(e);
    185    }
    186  };
    187 }
    188 
    189 function terminateExtensionBackgroundScript(id) {
    190  return async ({ dispatch, getState }) => {
    191    dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_START, id });
    192    const clientWrapper = getCurrentClient(getState().runtimes);
    193 
    194    try {
    195      const addonTargetFront = await clientWrapper.getAddon({ id });
    196      await addonTargetFront.terminateBackgroundScript();
    197      dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, id });
    198    } catch (e) {
    199      const error = typeof e === "string" ? new Error(e) : e;
    200      dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_FAILURE, id, error });
    201    }
    202  };
    203 }
    204 
    205 function requestTabs() {
    206  return async ({ dispatch, getState }) => {
    207    dispatch({ type: REQUEST_TABS_START });
    208 
    209    const runtime = getCurrentRuntime(getState().runtimes);
    210    const clientWrapper = getCurrentClient(getState().runtimes);
    211 
    212    try {
    213      const isSupported = isSupportedDebugTargetPane(
    214        runtime.runtimeDetails.info.type,
    215        DEBUG_TARGET_PANE.TAB
    216      );
    217      const tabs = isSupported ? await clientWrapper.listTabs() : [];
    218 
    219      // Fetch the favicon for all tabs.
    220      await Promise.all(
    221        tabs.map(descriptorFront => descriptorFront.retrieveFavicon())
    222      );
    223 
    224      dispatch({ type: REQUEST_TABS_SUCCESS, tabs });
    225    } catch (e) {
    226      dispatch({ type: REQUEST_TABS_FAILURE, error: e });
    227    }
    228  };
    229 }
    230 
    231 function requestExtensions() {
    232  return async ({ dispatch, getState }) => {
    233    dispatch({ type: REQUEST_EXTENSIONS_START });
    234 
    235    const runtime = getCurrentRuntime(getState().runtimes);
    236    const clientWrapper = getCurrentClient(getState().runtimes);
    237 
    238    try {
    239      const isIconDataURLRequired = runtime.type !== RUNTIMES.THIS_FIREFOX;
    240      const addons = await clientWrapper.listAddons({
    241        iconDataURL: isIconDataURLRequired,
    242      });
    243 
    244      const showHiddenAddons = getState().ui.showHiddenAddons;
    245 
    246      // Filter out non-debuggable addons as well as hidden ones, unless the dedicated
    247      // preference is set to true.
    248      const extensions = addons.filter(
    249        a => a.debuggable && (!a.hidden || showHiddenAddons)
    250      );
    251 
    252      const installedExtensions = extensions.filter(
    253        e => !e.temporarilyInstalled
    254      );
    255      const temporaryExtensions = extensions.filter(
    256        e => e.temporarilyInstalled
    257      );
    258 
    259      dispatch({
    260        type: REQUEST_EXTENSIONS_SUCCESS,
    261        installedExtensions: sortTargetsByName(installedExtensions),
    262        temporaryExtensions: sortTargetsByName(temporaryExtensions),
    263      });
    264    } catch (e) {
    265      dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e });
    266    }
    267  };
    268 }
    269 
    270 function requestProcesses() {
    271  return async ({ dispatch, getState }) => {
    272    dispatch({ type: REQUEST_PROCESSES_START });
    273 
    274    const clientWrapper = getCurrentClient(getState().runtimes);
    275 
    276    try {
    277      const mainProcessDescriptorFront = await clientWrapper.getMainProcess();
    278      dispatch({
    279        type: REQUEST_PROCESSES_SUCCESS,
    280        mainProcess: {
    281          id: 0,
    282          processFront: mainProcessDescriptorFront,
    283        },
    284      });
    285    } catch (e) {
    286      dispatch({ type: REQUEST_PROCESSES_FAILURE, error: e });
    287    }
    288  };
    289 }
    290 
    291 function requestWorkers() {
    292  return async ({ dispatch, getState }) => {
    293    dispatch({ type: REQUEST_WORKERS_START });
    294 
    295    const clientWrapper = getCurrentClient(getState().runtimes);
    296 
    297    try {
    298      const { otherWorkers, serviceWorkers, sharedWorkers } =
    299        await clientWrapper.listWorkers();
    300 
    301      for (const serviceWorker of serviceWorkers) {
    302        const { registrationFront } = serviceWorker;
    303        if (!registrationFront) {
    304          continue;
    305        }
    306 
    307        const subscription = await registrationFront.getPushSubscription();
    308        serviceWorker.subscription = subscription;
    309      }
    310 
    311      dispatch({
    312        type: REQUEST_WORKERS_SUCCESS,
    313        otherWorkers: sortTargetsByName(otherWorkers),
    314        serviceWorkers: sortTargetsByName(serviceWorkers),
    315        sharedWorkers: sortTargetsByName(sharedWorkers),
    316      });
    317    } catch (e) {
    318      dispatch({ type: REQUEST_WORKERS_FAILURE, error: e });
    319    }
    320  };
    321 }
    322 
    323 function startServiceWorker(registrationFront) {
    324  return async () => {
    325    try {
    326      await registrationFront.start();
    327    } catch (e) {
    328      console.error(e);
    329    }
    330  };
    331 }
    332 
    333 function sortTargetsByName(targets) {
    334  return targets.sort((target1, target2) => {
    335    // Fallback to empty string in case some targets don't have a valid name.
    336    const name1 = target1.name || "";
    337    const name2 = target2.name || "";
    338    return name1.localeCompare(name2);
    339  });
    340 }
    341 
    342 function unregisterServiceWorker(registrationFront) {
    343  return async () => {
    344    try {
    345      await registrationFront.unregister();
    346    } catch (e) {
    347      console.error(e);
    348    }
    349  };
    350 }
    351 
    352 module.exports = {
    353  inspectDebugTarget,
    354  installTemporaryExtension,
    355  pushServiceWorker,
    356  reloadTemporaryExtension,
    357  removeTemporaryExtension,
    358  requestTabs,
    359  requestExtensions,
    360  requestProcesses,
    361  requestWorkers,
    362  startServiceWorker,
    363  terminateExtensionBackgroundScript,
    364  unregisterServiceWorker,
    365 };