summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/components/Search/Search.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/components/Search/Search.jsx')
-rw-r--r--browser/components/newtab/content-src/components/Search/Search.jsx223
1 files changed, 223 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/components/Search/Search.jsx b/browser/components/newtab/content-src/components/Search/Search.jsx
new file mode 100644
index 0000000000..b131c884c1
--- /dev/null
+++ b/browser/components/newtab/content-src/components/Search/Search.jsx
@@ -0,0 +1,223 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals ContentSearchUIController, ContentSearchHandoffUIController */
+"use strict";
+
+import {
+ actionCreators as ac,
+ actionTypes as at,
+} from "common/Actions.sys.mjs";
+import { connect } from "react-redux";
+import { IS_NEWTAB } from "content-src/lib/constants";
+import React from "react";
+
+export class _Search extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onSearchClick = this.onSearchClick.bind(this);
+ this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
+ this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
+ this.onSearchHandoffDrop = this.onSearchHandoffDrop.bind(this);
+ this.onInputMount = this.onInputMount.bind(this);
+ this.onInputMountHandoff = this.onInputMountHandoff.bind(this);
+ this.onSearchHandoffButtonMount =
+ this.onSearchHandoffButtonMount.bind(this);
+ }
+
+ handleEvent(event) {
+ // Also track search events with our own telemetry
+ if (event.detail.type === "Search") {
+ this.props.dispatch(ac.UserEvent({ event: "SEARCH" }));
+ }
+ }
+
+ onSearchClick(event) {
+ window.gContentSearchController.search(event);
+ }
+
+ doSearchHandoff(text) {
+ this.props.dispatch(
+ ac.OnlyToMain({ type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: { text } })
+ );
+ this.props.dispatch({ type: at.FAKE_FOCUS_SEARCH });
+ this.props.dispatch(ac.UserEvent({ event: "SEARCH_HANDOFF" }));
+ if (text) {
+ this.props.dispatch({ type: at.DISABLE_SEARCH });
+ }
+ }
+
+ onSearchHandoffClick(event) {
+ // When search hand-off is enabled, we render a big button that is styled to
+ // look like a search textbox. If the button is clicked, we style
+ // the button as if it was a focused search box and show a fake cursor but
+ // really focus the awesomebar without the focus styles ("hidden focus").
+ event.preventDefault();
+ this.doSearchHandoff();
+ }
+
+ onSearchHandoffPaste(event) {
+ event.preventDefault();
+ this.doSearchHandoff(event.clipboardData.getData("Text"));
+ }
+
+ onSearchHandoffDrop(event) {
+ event.preventDefault();
+ let text = event.dataTransfer.getData("text");
+ if (text) {
+ this.doSearchHandoff(text);
+ }
+ }
+
+ componentWillUnmount() {
+ delete window.gContentSearchController;
+ }
+
+ onInputMount(input) {
+ if (input) {
+ // The "healthReportKey" and needs to be "newtab" or "abouthome" so that
+ // BrowserUsageTelemetry.jsm knows to handle events with this name, and
+ // can add the appropriate telemetry probes for search. Without the correct
+ // name, certain tests like browser_UsageTelemetry_content.js will fail
+ // (See github ticket #2348 for more details)
+ const healthReportKey = IS_NEWTAB ? "newtab" : "abouthome";
+
+ // The "searchSource" needs to be "newtab" or "homepage" and is sent with
+ // the search data and acts as context for the search request (See
+ // nsISearchEngine.getSubmission). It is necessary so that search engine
+ // plugins can correctly atribute referrals. (See github ticket #3321 for
+ // more details)
+ const searchSource = IS_NEWTAB ? "newtab" : "homepage";
+
+ // gContentSearchController needs to exist as a global so that tests for
+ // the existing about:home can find it; and so it allows these tests to pass.
+ // In the future, when activity stream is default about:home, this can be renamed
+ window.gContentSearchController = new ContentSearchUIController(
+ input,
+ input.parentNode,
+ healthReportKey,
+ searchSource
+ );
+ addEventListener("ContentSearchClient", this);
+ } else {
+ window.gContentSearchController = null;
+ removeEventListener("ContentSearchClient", this);
+ }
+ }
+
+ onInputMountHandoff(input) {
+ if (input) {
+ // The handoff UI controller helps us set the search icon and reacts to
+ // changes to default engine to keep everything in sync.
+ this._handoffSearchController = new ContentSearchHandoffUIController();
+ }
+ }
+
+ getDefaultEngineName() {
+ // _handoffSearchController will manage engine names once it is initialized.
+ return this.props.Prefs.values["urlbar.placeholderName"];
+ }
+
+ getHandoffInputL10nAttributes() {
+ let defaultEngineName = this.getDefaultEngineName();
+ return defaultEngineName
+ ? {
+ "data-l10n-id": "newtab-search-box-handoff-input",
+ "data-l10n-args": `{"engine": "${defaultEngineName}"}`,
+ }
+ : {
+ "data-l10n-id": "newtab-search-box-handoff-input-no-engine",
+ };
+ }
+
+ getHandoffTextL10nAttributes() {
+ let defaultEngineName = this.getDefaultEngineName();
+ return defaultEngineName
+ ? {
+ "data-l10n-id": "newtab-search-box-handoff-text",
+ "data-l10n-args": `{"engine": "${defaultEngineName}"}`,
+ }
+ : {
+ "data-l10n-id": "newtab-search-box-handoff-text-no-engine",
+ };
+ }
+
+ onSearchHandoffButtonMount(button) {
+ // Keep a reference to the button for use during "paste" event handling.
+ this._searchHandoffButton = button;
+ }
+
+ /*
+ * Do not change the ID on the input field, as legacy newtab code
+ * specifically looks for the id 'newtab-search-text' on input fields
+ * in order to execute searches in various tests
+ */
+ render() {
+ const wrapperClassName = [
+ "search-wrapper",
+ this.props.disable && "search-disabled",
+ this.props.fakeFocus && "fake-focus",
+ ]
+ .filter(v => v)
+ .join(" ");
+
+ return (
+ <div className={wrapperClassName}>
+ {this.props.showLogo && (
+ <div className="logo-and-wordmark">
+ <div className="logo" />
+ <div className="wordmark" />
+ </div>
+ )}
+ {!this.props.handoffEnabled && (
+ <div className="search-inner-wrapper">
+ <input
+ id="newtab-search-text"
+ data-l10n-id="newtab-search-box-input"
+ maxLength="256"
+ ref={this.onInputMount}
+ type="search"
+ />
+ <button
+ id="searchSubmit"
+ className="search-button"
+ data-l10n-id="newtab-search-box-search-button"
+ onClick={this.onSearchClick}
+ />
+ </div>
+ )}
+ {this.props.handoffEnabled && (
+ <div className="search-inner-wrapper">
+ <button
+ className="search-handoff-button"
+ {...this.getHandoffInputL10nAttributes()}
+ ref={this.onSearchHandoffButtonMount}
+ onClick={this.onSearchHandoffClick}
+ tabIndex="-1"
+ >
+ <div
+ className="fake-textbox"
+ {...this.getHandoffTextL10nAttributes()}
+ />
+ <input
+ type="search"
+ className="fake-editable"
+ tabIndex="-1"
+ aria-hidden="true"
+ onDrop={this.onSearchHandoffDrop}
+ onPaste={this.onSearchHandoffPaste}
+ ref={this.onInputMountHandoff}
+ />
+ <div className="fake-caret" />
+ </button>
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+export const Search = connect(state => ({
+ Prefs: state.Prefs,
+}))(_Search);