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);