Search.jsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /* globals ContentSearchUIController */
  2. "use strict";
  3. import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
  4. import {FormattedMessage, injectIntl} from "react-intl";
  5. import {connect} from "react-redux";
  6. import {IS_NEWTAB} from "content-src/lib/constants";
  7. import React from "react";
  8. export class _Search extends React.PureComponent {
  9. constructor(props) {
  10. super(props);
  11. this.onSearchClick = this.onSearchClick.bind(this);
  12. this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
  13. this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
  14. this.onSearchHandoffDrop = this.onSearchHandoffDrop.bind(this);
  15. this.onInputMount = this.onInputMount.bind(this);
  16. this.onSearchHandoffButtonMount = this.onSearchHandoffButtonMount.bind(this);
  17. }
  18. handleEvent(event) {
  19. // Also track search events with our own telemetry
  20. if (event.detail.type === "Search") {
  21. this.props.dispatch(ac.UserEvent({event: "SEARCH"}));
  22. }
  23. }
  24. onSearchClick(event) {
  25. window.gContentSearchController.search(event);
  26. }
  27. doSearchHandoff(text) {
  28. this.props.dispatch(ac.OnlyToMain({type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: {text}}));
  29. this.props.dispatch({type: at.FAKE_FOCUS_SEARCH});
  30. this.props.dispatch(ac.UserEvent({event: "SEARCH_HANDOFF"}));
  31. if (text) {
  32. this.props.dispatch({type: at.HIDE_SEARCH});
  33. }
  34. }
  35. onSearchHandoffClick(event) {
  36. // When search hand-off is enabled, we render a big button that is styled to
  37. // look like a search textbox. If the button is clicked, we style
  38. // the button as if it was a focused search box and show a fake cursor but
  39. // really focus the awesomebar without the focus styles ("hidden focus").
  40. event.preventDefault();
  41. this.doSearchHandoff();
  42. }
  43. onSearchHandoffPaste(event) {
  44. event.preventDefault();
  45. this.doSearchHandoff(event.clipboardData.getData("Text"));
  46. }
  47. onSearchHandoffDrop(event) {
  48. event.preventDefault();
  49. let text = event.dataTransfer.getData("text");
  50. if (text) {
  51. this.doSearchHandoff(text);
  52. }
  53. }
  54. componentWillUnmount() {
  55. delete window.gContentSearchController;
  56. }
  57. onInputMount(input) {
  58. if (input) {
  59. // The "healthReportKey" and needs to be "newtab" or "abouthome" so that
  60. // BrowserUsageTelemetry.jsm knows to handle events with this name, and
  61. // can add the appropriate telemetry probes for search. Without the correct
  62. // name, certain tests like browser_UsageTelemetry_content.js will fail
  63. // (See github ticket #2348 for more details)
  64. const healthReportKey = IS_NEWTAB ? "newtab" : "abouthome";
  65. // The "searchSource" needs to be "newtab" or "homepage" and is sent with
  66. // the search data and acts as context for the search request (See
  67. // nsISearchEngine.getSubmission). It is necessary so that search engine
  68. // plugins can correctly atribute referrals. (See github ticket #3321 for
  69. // more details)
  70. const searchSource = IS_NEWTAB ? "newtab" : "homepage";
  71. // gContentSearchController needs to exist as a global so that tests for
  72. // the existing about:home can find it; and so it allows these tests to pass.
  73. // In the future, when activity stream is default about:home, this can be renamed
  74. window.gContentSearchController = new ContentSearchUIController(input, input.parentNode,
  75. healthReportKey, searchSource);
  76. addEventListener("ContentSearchClient", this);
  77. } else {
  78. window.gContentSearchController = null;
  79. removeEventListener("ContentSearchClient", this);
  80. }
  81. }
  82. onSearchHandoffButtonMount(button) {
  83. // Keep a reference to the button for use during "paste" event handling.
  84. this._searchHandoffButton = button;
  85. }
  86. /*
  87. * Do not change the ID on the input field, as legacy newtab code
  88. * specifically looks for the id 'newtab-search-text' on input fields
  89. * in order to execute searches in various tests
  90. */
  91. render() {
  92. const wrapperClassName = [
  93. "search-wrapper",
  94. this.props.hide && "search-hidden",
  95. this.props.fakeFocus && "fake-focus",
  96. ].filter(v => v).join(" ");
  97. return (<div className={wrapperClassName}>
  98. {this.props.showLogo &&
  99. <div className="logo-and-wordmark">
  100. <div className="logo" />
  101. <div className="wordmark" />
  102. </div>
  103. }
  104. {!this.props.handoffEnabled &&
  105. <div className="search-inner-wrapper">
  106. <label htmlFor="newtab-search-text" className="search-label">
  107. <span className="sr-only"><FormattedMessage id="search_web_placeholder" /></span>
  108. </label>
  109. <input
  110. id="newtab-search-text"
  111. maxLength="256"
  112. placeholder={this.props.intl.formatMessage({id: "search_web_placeholder"})}
  113. ref={this.onInputMount}
  114. title={this.props.intl.formatMessage({id: "search_web_placeholder"})}
  115. type="search" />
  116. <button
  117. id="searchSubmit"
  118. className="search-button"
  119. onClick={this.onSearchClick}
  120. title={this.props.intl.formatMessage({id: "search_button"})}>
  121. <span className="sr-only"><FormattedMessage id="search_button" /></span>
  122. </button>
  123. </div>
  124. }
  125. {this.props.handoffEnabled &&
  126. <div className="search-inner-wrapper">
  127. <button
  128. className="search-handoff-button"
  129. ref={this.onSearchHandoffButtonMount}
  130. onClick={this.onSearchHandoffClick}
  131. tabIndex="-1"
  132. title={this.props.intl.formatMessage({id: "search_web_placeholder"})}>
  133. <div className="fake-textbox">{this.props.intl.formatMessage({id: "search_web_placeholder"})}</div>
  134. <input type="search" className="fake-editable" tabIndex="-1" aria-hidden="true" onDrop={this.onSearchHandoffDrop} onPaste={this.onSearchHandoffPaste} />
  135. <div className="fake-caret" />
  136. </button>
  137. {/*
  138. This dummy and hidden input below is so we can load ContentSearchUIController.
  139. Why? It sets --newtab-search-icon for us and it isn't trivial to port over.
  140. */}
  141. <input
  142. type="search"
  143. style={{display: "none"}}
  144. ref={this.onInputMount} />
  145. </div>
  146. }
  147. </div>);
  148. }
  149. }
  150. export const Search = connect()(injectIntl(_Search));