tor-browser

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

DebugTargetInfo.js (12595B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 "use strict";
      5 
      6 const {
      7  PureComponent,
      8  createFactory,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     12 const {
     13  CONNECTION_TYPES,
     14 } = require("resource://devtools/client/shared/remote-debugging/constants.js");
     15 const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js");
     16 const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
     17 const Localized = createFactory(FluentReact.Localized);
     18 
     19 /**
     20 * This is header that should be displayed on top of the toolbox when using
     21 * about:devtools-toolbox.
     22 */
     23 class DebugTargetInfo extends PureComponent {
     24  static get propTypes() {
     25    return {
     26      alwaysOnTop: PropTypes.bool.isRequired,
     27      focusedState: PropTypes.bool,
     28      toggleAlwaysOnTop: PropTypes.func.isRequired,
     29      debugTargetData: PropTypes.shape({
     30        connectionType: PropTypes.oneOf(Object.values(CONNECTION_TYPES))
     31          .isRequired,
     32        runtimeInfo: PropTypes.shape({
     33          deviceName: PropTypes.string,
     34          icon: PropTypes.string.isRequired,
     35          name: PropTypes.string.isRequired,
     36          version: PropTypes.string.isRequired,
     37        }).isRequired,
     38        descriptorType: PropTypes.oneOf(Object.values(DESCRIPTOR_TYPES))
     39          .isRequired,
     40        descriptorName: PropTypes.string.isRequired,
     41      }).isRequired,
     42      L10N: PropTypes.object.isRequired,
     43      toolbox: PropTypes.object.isRequired,
     44    };
     45  }
     46 
     47  constructor(props) {
     48    super(props);
     49 
     50    this.state = { urlValue: props.toolbox.target.url };
     51 
     52    this.onChange = this.onChange.bind(this);
     53    this.onFocus = this.onFocus.bind(this);
     54    this.onSubmit = this.onSubmit.bind(this);
     55  }
     56 
     57  componentDidMount() {
     58    this.updateTitle();
     59  }
     60 
     61  updateTitle() {
     62    const { L10N, debugTargetData, toolbox } = this.props;
     63    const title = toolbox.target.name;
     64    const descriptorTypeStr = L10N.getStr(
     65      this.getAssetsForDebugDescriptorType().l10nId
     66    );
     67 
     68    const { connectionType } = debugTargetData;
     69    if (connectionType === CONNECTION_TYPES.THIS_FIREFOX) {
     70      toolbox.doc.title = L10N.getFormatStr(
     71        "toolbox.debugTargetInfo.tabTitleLocal",
     72        descriptorTypeStr,
     73        title
     74      );
     75    } else {
     76      const connectionTypeStr = L10N.getStr(
     77        this.getAssetsForConnectionType().l10nId
     78      );
     79      toolbox.doc.title = L10N.getFormatStr(
     80        "toolbox.debugTargetInfo.tabTitleRemote",
     81        connectionTypeStr,
     82        descriptorTypeStr,
     83        title
     84      );
     85    }
     86  }
     87 
     88  getRuntimeText() {
     89    const { debugTargetData, L10N } = this.props;
     90    const { name, version } = debugTargetData.runtimeInfo;
     91    const { connectionType } = debugTargetData;
     92    const brandShorterName = L10N.getStr("brandShorterName");
     93 
     94    return connectionType === CONNECTION_TYPES.THIS_FIREFOX
     95      ? L10N.getFormatStr(
     96          "toolbox.debugTargetInfo.runtimeLabel.thisRuntime",
     97          brandShorterName,
     98          version
     99        )
    100      : L10N.getFormatStr(
    101          "toolbox.debugTargetInfo.runtimeLabel",
    102          name,
    103          version
    104        );
    105  }
    106 
    107  getAssetsForConnectionType() {
    108    const { connectionType } = this.props.debugTargetData;
    109 
    110    switch (connectionType) {
    111      case CONNECTION_TYPES.USB:
    112        return {
    113          image: "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg",
    114          l10nId: "toolbox.debugTargetInfo.connection.usb",
    115        };
    116      case CONNECTION_TYPES.NETWORK:
    117        return {
    118          image: "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg",
    119          l10nId: "toolbox.debugTargetInfo.connection.network",
    120        };
    121      default:
    122        return {};
    123    }
    124  }
    125 
    126  getAssetsForDebugDescriptorType() {
    127    const { descriptorType } = this.props.debugTargetData;
    128 
    129    // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1520723
    130    //       Show actual favicon (currently toolbox.target.activeTab.favicon
    131    //       is unpopulated)
    132    const favicon = "chrome://devtools/skin/images/globe.svg";
    133 
    134    switch (descriptorType) {
    135      case DESCRIPTOR_TYPES.EXTENSION:
    136        return {
    137          image: "chrome://devtools/skin/images/debugging-addons.svg",
    138          l10nId: "toolbox.debugTargetInfo.targetType.extension",
    139        };
    140      case DESCRIPTOR_TYPES.PROCESS:
    141        return {
    142          image: "chrome://devtools/skin/images/settings.svg",
    143          l10nId: "toolbox.debugTargetInfo.targetType.process",
    144        };
    145      case DESCRIPTOR_TYPES.TAB:
    146        return {
    147          image: favicon,
    148          l10nId: "toolbox.debugTargetInfo.targetType.tab",
    149        };
    150      case DESCRIPTOR_TYPES.WORKER:
    151        return {
    152          image: "chrome://devtools/skin/images/debugging-workers.svg",
    153          l10nId: "toolbox.debugTargetInfo.targetType.worker",
    154        };
    155      default:
    156        return {};
    157    }
    158  }
    159 
    160  onChange({ target }) {
    161    this.setState({ urlValue: target.value });
    162  }
    163 
    164  onFocus({ target }) {
    165    target.select();
    166  }
    167 
    168  onSubmit(event) {
    169    event.preventDefault();
    170    let url = this.state.urlValue;
    171 
    172    if (!url || !url.length) {
    173      return;
    174    }
    175 
    176    try {
    177      // Get the URL from the fixup service:
    178      const flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
    179      const uriInfo = Services.uriFixup.getFixupURIInfo(url, flags);
    180      url = uriInfo.fixedURI.spec;
    181    } catch (ex) {
    182      // The getFixupURIInfo service will throw an error if a malformed URI is
    183      // produced from the input.
    184      console.error(ex);
    185    }
    186 
    187    // Do not waitForLoad as we don't wait navigateTo to resolve anyway.
    188    // Bug 1968023: navigateTo is flaky and sometimes never catches the
    189    // STATE_STOP notification necessary for waitForLoad=true.
    190    this.props.toolbox.commands.targetCommand.navigateTo(
    191      url,
    192      false /* waitForLoad */
    193    );
    194  }
    195 
    196  shallRenderConnection() {
    197    const { connectionType } = this.props.debugTargetData;
    198    const renderableTypes = [CONNECTION_TYPES.USB, CONNECTION_TYPES.NETWORK];
    199 
    200    return renderableTypes.includes(connectionType);
    201  }
    202 
    203  renderConnection() {
    204    const { connectionType } = this.props.debugTargetData;
    205    const { image, l10nId } = this.getAssetsForConnectionType();
    206 
    207    return dom.span(
    208      {
    209        className: "iconized-label qa-connection-info",
    210      },
    211      dom.img({ src: image, alt: `${connectionType} icon` }),
    212      this.props.L10N.getStr(l10nId)
    213    );
    214  }
    215 
    216  renderRuntime() {
    217    if (
    218      !this.props.debugTargetData.runtimeInfo ||
    219      (this.props.debugTargetData.connectionType ===
    220        CONNECTION_TYPES.THIS_FIREFOX &&
    221        this.props.debugTargetData.descriptorType ===
    222          DESCRIPTOR_TYPES.EXTENSION)
    223    ) {
    224      // Skip the runtime render if no runtimeInfo is available.
    225      // Runtime info is retrieved from the remote-client-manager, which might not be
    226      // setup if about:devtools-toolbox was not opened from about:debugging.
    227      //
    228      // Also skip the runtime if we are debugging firefox itself, mainly to save some space.
    229      return null;
    230    }
    231 
    232    const { icon, deviceName } = this.props.debugTargetData.runtimeInfo;
    233 
    234    return dom.span(
    235      {
    236        className: "iconized-label qa-runtime-info",
    237      },
    238      dom.img({ src: icon, className: "channel-icon qa-runtime-icon" }),
    239      dom.b({ className: "devtools-ellipsis-text" }, this.getRuntimeText()),
    240      dom.span({ className: "devtools-ellipsis-text" }, deviceName)
    241    );
    242  }
    243 
    244  renderDescriptorName() {
    245    const name = this.props.debugTargetData.descriptorName;
    246 
    247    const { image, l10nId } = this.getAssetsForDebugDescriptorType();
    248 
    249    return dom.span(
    250      {
    251        className: "iconized-label debug-descriptor-title",
    252      },
    253      dom.img({ src: image, alt: this.props.L10N.getStr(l10nId) }),
    254      name
    255        ? dom.b(
    256            { className: "devtools-ellipsis-text qa-descriptor-title" },
    257            name
    258          )
    259        : null
    260    );
    261  }
    262 
    263  renderTargetURI() {
    264    const { descriptorType } = this.props.debugTargetData;
    265    const { url } = this.props.toolbox.target;
    266    const isWebExtension = descriptorType === DESCRIPTOR_TYPES.EXTENSION;
    267 
    268    // Avoid displaying the target url for web extension as it is always
    269    // the fallback document URL. Keeps rendering the url component
    270    // as it use flex to align the "always on top" button on the right.
    271    if (isWebExtension) {
    272      return dom.span({
    273        className: "debug-target-url",
    274      });
    275    }
    276 
    277    const isURLEditable = descriptorType === DESCRIPTOR_TYPES.TAB;
    278 
    279    return dom.span(
    280      {
    281        key: url,
    282        className: "debug-target-url",
    283      },
    284      isURLEditable
    285        ? this.renderTargetInput(url)
    286        : dom.span(
    287            { className: "debug-target-url-readonly devtools-ellipsis-text" },
    288            url
    289          )
    290    );
    291  }
    292 
    293  renderTargetInput(url) {
    294    return dom.form(
    295      {
    296        className: "debug-target-url-form",
    297        onSubmit: this.onSubmit,
    298      },
    299      dom.input({
    300        className: "devtools-textinput debug-target-url-input",
    301        onChange: this.onChange,
    302        onFocus: this.onFocus,
    303        defaultValue: url,
    304      })
    305    );
    306  }
    307 
    308  renderAlwaysOnTopButton() {
    309    // This is only displayed for local web extension debugging
    310    const { descriptorType, connectionType } = this.props.debugTargetData;
    311    const isLocalWebExtension =
    312      descriptorType === DESCRIPTOR_TYPES.EXTENSION &&
    313      connectionType === CONNECTION_TYPES.THIS_FIREFOX;
    314    if (!isLocalWebExtension) {
    315      return [];
    316    }
    317 
    318    const checked = this.props.alwaysOnTop;
    319    const toolboxFocused = this.props.focusedState;
    320    return [
    321      Localized(
    322        {
    323          id: checked
    324            ? "toolbox-always-on-top-enabled2"
    325            : "toolbox-always-on-top-disabled2",
    326          attrs: { title: true },
    327        },
    328        dom.button({
    329          className:
    330            `toolbox-always-on-top` +
    331            (checked ? " checked" : "") +
    332            (toolboxFocused ? " toolbox-is-focused" : ""),
    333          onClick: this.props.toggleAlwaysOnTop,
    334        })
    335      ),
    336    ];
    337  }
    338 
    339  renderNavigationButton(detail) {
    340    const { L10N } = this.props;
    341 
    342    return dom.button(
    343      {
    344        className: `iconized-label navigation-button ${detail.className}`,
    345        onClick: detail.onClick,
    346        title: L10N.getStr(detail.l10nId),
    347      },
    348      dom.img({
    349        src: detail.icon,
    350        alt: L10N.getStr(detail.l10nId),
    351      })
    352    );
    353  }
    354 
    355  renderNavigation() {
    356    const { debugTargetData } = this.props;
    357    const { descriptorType } = debugTargetData;
    358 
    359    if (
    360      descriptorType !== DESCRIPTOR_TYPES.TAB &&
    361      descriptorType !== DESCRIPTOR_TYPES.EXTENSION
    362    ) {
    363      return null;
    364    }
    365 
    366    const items = [];
    367 
    368    // There is little value in exposing back/forward for WebExtensions
    369    if (
    370      this.props.toolbox.target.getTrait("navigation") &&
    371      descriptorType === DESCRIPTOR_TYPES.TAB
    372    ) {
    373      items.push(
    374        this.renderNavigationButton({
    375          className: "qa-back-button",
    376          icon: "chrome://browser/skin/back.svg",
    377          l10nId: "toolbox.debugTargetInfo.back",
    378          onClick: () => this.props.toolbox.commands.targetCommand.goBack(),
    379        }),
    380        this.renderNavigationButton({
    381          className: "qa-forward-button",
    382          icon: "chrome://browser/skin/forward.svg",
    383          l10nId: "toolbox.debugTargetInfo.forward",
    384          onClick: () => this.props.toolbox.commands.targetCommand.goForward(),
    385        })
    386      );
    387    }
    388 
    389    items.push(
    390      this.renderNavigationButton({
    391        className: "qa-reload-button",
    392        icon: "chrome://global/skin/icons/reload.svg",
    393        l10nId: "toolbox.debugTargetInfo.reload",
    394        onClick: () => this.props.toolbox.reload(),
    395      })
    396    );
    397 
    398    return dom.div(
    399      {
    400        className: "debug-target-navigation",
    401      },
    402      ...items
    403    );
    404  }
    405 
    406  render() {
    407    return dom.header(
    408      {
    409        className: "debug-target-info qa-debug-target-info",
    410      },
    411      this.shallRenderConnection() ? this.renderConnection() : null,
    412      this.renderRuntime(),
    413      this.renderDescriptorName(),
    414      this.renderNavigation(),
    415      this.renderTargetURI(),
    416      ...this.renderAlwaysOnTopButton()
    417    );
    418  }
    419 }
    420 
    421 module.exports = DebugTargetInfo;