tor-browser

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

Search.jsx (6952B)


      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 
      5 /* globals ContentSearchHandoffUIController */
      6 
      7 /**
      8 * @backward-compat { version 148 }
      9 *
     10 * Temporary dual implementation to support train hopping. The old handoff UI
     11 * is kept alongside the new contentSearchHandoffUI.mjs custom element until
     12 * the module lands on all channels. Controlled by the pref
     13 * browser.newtabpage.activity-stream.search.useHandoffComponent.
     14 * Remove the old implementation and the pref once this ships to Release.
     15 */
     16 
     17 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
     18 import { connect } from "react-redux";
     19 import { Logo } from "content-src/components/Logo/Logo";
     20 import React from "react";
     21 import { ExternalComponentWrapper } from "content-src/components/ExternalComponentWrapper/ExternalComponentWrapper";
     22 
     23 export class _Search extends React.PureComponent {
     24  constructor(props) {
     25    super(props);
     26    this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
     27    this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
     28    this.onSearchHandoffDrop = this.onSearchHandoffDrop.bind(this);
     29    this.onInputMountHandoff = this.onInputMountHandoff.bind(this);
     30    this.onSearchHandoffButtonMount =
     31      this.onSearchHandoffButtonMount.bind(this);
     32  }
     33 
     34  handleEvent(event) {
     35    // Also track search events with our own telemetry
     36    if (event.detail.type === "Search") {
     37      this.props.dispatch(ac.UserEvent({ event: "SEARCH" }));
     38    }
     39  }
     40 
     41  doSearchHandoff(text) {
     42    this.props.dispatch(
     43      ac.OnlyToMain({ type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: { text } })
     44    );
     45    this.props.dispatch({ type: at.FAKE_FOCUS_SEARCH });
     46    this.props.dispatch(ac.UserEvent({ event: "SEARCH_HANDOFF" }));
     47    if (text) {
     48      this.props.dispatch({ type: at.DISABLE_SEARCH });
     49    }
     50  }
     51 
     52  onSearchHandoffClick(event) {
     53    // When search hand-off is enabled, we render a big button that is styled to
     54    // look like a search textbox. If the button is clicked, we style
     55    // the button as if it was a focused search box and show a fake cursor but
     56    // really focus the awesomebar without the focus styles ("hidden focus").
     57    event.preventDefault();
     58    this.doSearchHandoff();
     59  }
     60 
     61  onSearchHandoffPaste(event) {
     62    event.preventDefault();
     63    this.doSearchHandoff(event.clipboardData.getData("Text"));
     64  }
     65 
     66  onSearchHandoffDrop(event) {
     67    event.preventDefault();
     68    let text = event.dataTransfer.getData("text");
     69    if (text) {
     70      this.doSearchHandoff(text);
     71    }
     72  }
     73 
     74  componentDidMount() {
     75    const {
     76      caretBlinkCount,
     77      caretBlinkTime,
     78      "search.useHandoffComponent": useHandoffComponent,
     79      "externalComponents.enabled": useExternalComponents,
     80    } = this.props.Prefs.values;
     81 
     82    if (useExternalComponents) {
     83      // Nothing to do - the external component will have set the caret
     84      // values itself.
     85      return;
     86    }
     87 
     88    if (useHandoffComponent) {
     89      const { handoffUI } = this;
     90      if (handoffUI) {
     91        // If caret blink count isn't defined, use the default infinite behavior for animation
     92        handoffUI.style.setProperty(
     93          "--caret-blink-count",
     94          caretBlinkCount > -1 ? caretBlinkCount : "infinite"
     95        );
     96 
     97        // Apply custom blink rate if set, else fallback to default (567ms on/off --> 1134ms total)
     98        handoffUI.style.setProperty(
     99          "--caret-blink-time",
    100          caretBlinkTime > 0 ? `${caretBlinkTime * 2}ms` : `${1134}ms`
    101        );
    102      }
    103    } else {
    104      const caret = this.fakeCaret;
    105      if (caret) {
    106        // If caret blink count isn't defined, use the default infinite behavior for animation
    107        caret.style.setProperty(
    108          "--caret-blink-count",
    109          caretBlinkCount > -1 ? caretBlinkCount : "infinite"
    110        );
    111 
    112        // Apply custom blink rate if set, else fallback to default (567ms on/off --> 1134ms total)
    113        caret.style.setProperty(
    114          "--caret-blink-time",
    115          caretBlinkTime > 0 ? `${caretBlinkTime * 2}ms` : `${1134}ms`
    116        );
    117      }
    118    }
    119  }
    120 
    121  onInputMountHandoff(input) {
    122    if (input) {
    123      // The handoff UI controller helps us set the search icon and reacts to
    124      // changes to default engine to keep everything in sync.
    125      this._handoffSearchController = new ContentSearchHandoffUIController();
    126    }
    127  }
    128 
    129  onSearchHandoffButtonMount(button) {
    130    // Keep a reference to the button for use during "paste" event handling.
    131    this._searchHandoffButton = button;
    132  }
    133 
    134  /*
    135   * Do not change the ID on the input field, as legacy newtab code
    136   * specifically looks for the id 'newtab-search-text' on input fields
    137   * in order to execute searches in various tests
    138   */
    139  render() {
    140    const useHandoffComponent =
    141      this.props.Prefs.values["search.useHandoffComponent"];
    142    const useExternalComponents =
    143      this.props.Prefs.values["externalComponents.enabled"];
    144 
    145    if (useHandoffComponent) {
    146      if (useExternalComponents) {
    147        return (
    148          <div className="search-wrapper">
    149            {this.props.showLogo && <Logo />}
    150            <ExternalComponentWrapper
    151              type="SEARCH"
    152              className="search-inner-wrapper"
    153            ></ExternalComponentWrapper>
    154          </div>
    155        );
    156      }
    157      return (
    158        <div className="search-wrapper">
    159          {this.props.showLogo && <Logo />}
    160          <div className="search-inner-wrapper">
    161            <content-search-handoff-ui
    162              ref={el => {
    163                this.handoffUI = el;
    164              }}
    165            ></content-search-handoff-ui>
    166          </div>
    167        </div>
    168      );
    169    }
    170 
    171    const wrapperClassName = [
    172      "search-wrapper",
    173      this.props.disable && "search-disabled",
    174      this.props.fakeFocus && "fake-focus",
    175    ]
    176      .filter(v => v)
    177      .join(" ");
    178 
    179    return (
    180      <div className={wrapperClassName}>
    181        {this.props.showLogo && <Logo />}
    182        <div className="search-inner-wrapper">
    183          <button
    184            className="search-handoff-button"
    185            ref={this.onSearchHandoffButtonMount}
    186            onClick={this.onSearchHandoffClick}
    187            tabIndex="-1"
    188          >
    189            <div className="fake-textbox" />
    190            <input
    191              type="search"
    192              className="fake-editable"
    193              tabIndex="-1"
    194              aria-hidden="true"
    195              onDrop={this.onSearchHandoffDrop}
    196              onPaste={this.onSearchHandoffPaste}
    197              ref={this.onInputMountHandoff}
    198            />
    199            <div
    200              className="fake-caret"
    201              ref={el => {
    202                this.fakeCaret = el;
    203              }}
    204            />
    205          </button>
    206        </div>
    207      </div>
    208    );
    209  }
    210 }
    211 
    212 export const Search = connect(state => ({
    213  Prefs: state.Prefs,
    214 }))(_Search);