From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../content-src/components/Search/Search.jsx | 223 +++++++++++ .../content-src/components/Search/_Search.scss | 412 +++++++++++++++++++++ 2 files changed, 635 insertions(+) create mode 100644 browser/components/newtab/content-src/components/Search/Search.jsx create mode 100644 browser/components/newtab/content-src/components/Search/_Search.scss (limited to 'browser/components/newtab/content-src/components/Search') 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 ( +
+ {this.props.showLogo && ( +
+
+
+
+ )} + {!this.props.handoffEnabled && ( +
+ +
+ )} + {this.props.handoffEnabled && ( +
+ +
+ )} +
+ ); + } +} + +export const Search = connect(state => ({ + Prefs: state.Prefs, +}))(_Search); diff --git a/browser/components/newtab/content-src/components/Search/_Search.scss b/browser/components/newtab/content-src/components/Search/_Search.scss new file mode 100644 index 0000000000..189198a16c --- /dev/null +++ b/browser/components/newtab/content-src/components/Search/_Search.scss @@ -0,0 +1,412 @@ +$search-height: 48px; +$search-height-new: 52px; +$search-icon-size: 24px; +$search-icon-padding: 16px; +$search-icon-width: 2 * $search-icon-padding + $search-icon-size - 4px; +$search-button-width: 48px; +$glyph-forward: url('chrome://browser/skin/forward.svg'); + +.search-wrapper { + padding: 34px 0 38px; + + .only-search & { + padding: 0 0 38px; + } + + .logo-and-wordmark { + $logo-size: 82px; + $wordmark-size: 134px; + + align-items: center; + display: flex; + justify-content: center; + margin-bottom: 48px; + + .logo { + display: inline-block; + height: $logo-size; + width: $logo-size; + background: url('chrome://branding/content/about-logo.png') no-repeat center; + background-size: $logo-size; + + @media (min-resolution: 2x) { + background-image: url('chrome://branding/content/about-logo@2x.png'); + } + } + + .wordmark { + background: url('chrome://branding/content/firefox-wordmark.svg') no-repeat center center; + background-size: $wordmark-size; + -moz-context-properties: fill; + display: inline-block; + fill: var(--newtab-wordmark-color); + height: $logo-size; + margin-inline-start: 16px; + width: $wordmark-size; + } + + @media (max-width: $break-point-medium - 1) { + $logo-size-small: 64px; + $wordmark-small-size: 100px; + + .logo { + background-size: $logo-size-small; + height: $logo-size-small; + width: $logo-size-small; + } + + .wordmark { + background-size: $wordmark-small-size; + height: $logo-size-small; + width: $wordmark-small-size; + margin-inline-start: 12px; + } + } + } + + .search-inner-wrapper { + cursor: default; + display: flex; + min-height: $search-height-new; + margin: 0 auto; + position: relative; + width: $searchbar-width-small; + + @media (min-width: $break-point-medium) { + width: $searchbar-width-medium; + } + + @media (min-width: $break-point-large) { + width: $searchbar-width-large; + } + + @media (min-width: $break-point-widest) { + width: $searchbar-width-largest; + } + } + + .search-handoff-button, + input { + background: var(--newtab-background-color-secondary) var(--newtab-search-icon) $search-icon-padding center no-repeat; + background-size: $search-icon-size; + padding-inline-start: $search-icon-width; + padding-inline-end: 10px; + padding-block: 0; + width: 100%; + box-shadow: $shadow-card; + border: 1px solid transparent; + border-radius: 8px; + color: var(--newtab-text-primary-color); + -moz-context-properties: fill; + fill: var(--newtab-text-secondary-color); + + &:dir(rtl) { + background-position-x: right $search-icon-padding; + } + } + + .search-inner-wrapper:active input, + input:focus { + border: 1px solid var(--newtab-primary-action-background); + outline: 0; + box-shadow: $shadow-focus; + } + + .search-button { + background: $glyph-forward no-repeat center center; + background-size: 16px 16px; + border: 0; + border-radius: 0 $border-radius $border-radius 0; + -moz-context-properties: fill; + fill: var(--newtab-text-secondary-color); + height: 100%; + inset-inline-end: 0; + position: absolute; + width: $search-button-width; + + &:focus, + &:hover { + background-color: var(--newtab-element-hover-color); + cursor: pointer; + } + + &:focus { + outline: 0; + box-shadow: $shadow-focus; + border: 1px solid var(--newtab-primary-action-background); + border-radius: 0 $border-radius-new $border-radius-new 0; + } + + &:active { + background-color: var(--newtab-element-hover-color); + } + + &:dir(rtl) { + transform: scaleX(-1); + } + } + + &.fake-focus:not(.search.disabled) { + .search-handoff-button { + border: 1px solid var(--newtab-primary-action-background); + box-shadow: $shadow-focus; + } + } + + .search-handoff-button { + padding-inline-end: 15px; + color: var(--newtab-text-primary-color); + fill: var(--newtab-text-secondary-color); + -moz-context-properties: fill; + + .fake-caret { + top: 18px; + inset-inline-start: $search-icon-width; + + &:dir(rtl) { + background-position-x: right $search-icon-padding; + } + } + } + + &.visible-logo { + .logo-and-wordmark { + .wordmark { + fill: var(--newtab-wordmark-color); + } + } + } +} + +.non-collapsible-section + .below-search-snippet-wrapper { + // If search is enabled, we need to invade its large bottom padding. + margin-top: -48px; +} + +@media (max-height: 700px) { + .search-wrapper { + padding: 0 0 30px; + } + + .non-collapsible-section + .below-search-snippet-wrapper { + // In shorter windows, search doesn't have such a large padding. + margin-top: -14px; + } + + .below-search-snippet-wrapper { + min-height: 0; + } +} + +.search-handoff-button { + background: var(--newtab-background-color-secondary) var(--newtab-search-icon) $search-icon-padding center no-repeat; + background-size: $search-icon-size; + border: solid 1px transparent; + border-radius: 3px; + box-shadow: $shadow-secondary, 0 0 0 1px $black-15; + cursor: text; + font-size: 15px; + padding: 0; + padding-inline-end: 48px; + padding-inline-start: 46px; + opacity: 1; + width: 100%; + + &:dir(rtl) { + background-position-x: right $search-icon-padding; + } + + .fake-focus:not(.search-disabled) & { + border: $input-border-active; + box-shadow: var(--newtab-textbox-focus-boxshadow); + + .fake-caret { + display: block; + } + } + + .search-disabled & { + opacity: 0.5; + box-shadow: none; + } + + .fake-editable:focus { + outline: none; + caret-color: transparent; + } + + .fake-editable { + color: transparent; + height: 100%; + opacity: 0; + position: absolute; + inset: 0; + } + + .fake-textbox { + opacity: 0.54; + text-align: start; + } + + .fake-caret { + animation: caret-animation 1.3s steps(5, start) infinite; + background: var(--newtab-text-primary-color); + display: none; + inset-inline-start: 47px; + height: 17px; + position: absolute; + top: 16px; + width: 1px; + + @keyframes caret-animation { + to { + visibility: hidden; + } + } + } +} + +@media (min-height: 701px) { + body:not(.inline-onboarding) .fixed-search { + main { + padding-top: 124px; + } + + &.visible-logo { + main { + padding-top: 254px; + } + } + + .search-wrapper { + $search-height: 45px; + $search-icon-size: 24px; + $search-icon-padding: 16px; + $search-header-bar-height: 95px; + + border-bottom: solid 1px var(--newtab-border-color); + padding: 27px 0; + background-color: var(--newtab-overlay-color); + min-height: $search-header-bar-height; + left: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 9; + + .search-inner-wrapper { + min-height: $search-height; + } + + input { + background-position-x: $search-icon-padding; + background-size: $search-icon-size; + + &:dir(rtl) { + background-position-x: right $search-icon-padding; + } + } + + .search-handoff-button .fake-caret { + top: 14px; + } + + .logo-and-wordmark { + display: none; + } + } + + .search-handoff-button { + background-position-x: $search-icon-padding; + background-size: $search-icon-size; + + &:dir(rtl) { + background-position-x: right $search-icon-padding; + } + + .fake-caret { + top: 10px; + } + } + } +} + +@at-root { + // Adjust the style of the contentSearchUI-generated table + .contentSearchSuggestionTable { + border: 0; + box-shadow: $context-menu-shadow; + transform: translateY($textbox-shadow-size); + background-color: var(--newtab-background-color); + + .contentSearchHeader { + color: var(--newtab-text-secondary-color); + background-color: var(--newtab-background-color-secondary); + } + + .contentSearchHeader, + .contentSearchSettingsButton { + border-color: var(--newtab-border-color); + } + + .contentSearchSuggestionsList { + color: var(--newtab-text-primary-color); + border: 0; + } + + .contentSearchOneOffsTable { + border-top: solid 1px var(--newtab-border-color); + background-color: var(--newtab-background-color); + } + + .contentSearchSearchWithHeaderSearchText { + color: var(--newtab-text-primary-color); + } + + .contentSearchSuggestionRow { + &.selected { + background: var(--newtab-element-hover-color); + color: var(--newtab-text-primary-color); + + &:active { + background: var(--newtab-element-active-color); + } + + .historyIcon { + fill: var(--newtab-text-secondary-color); + } + } + } + + .contentSearchOneOffItem { + // Make the border slightly shorter by offsetting from the top and bottom + $border-offset: 18%; + + background-image: none; + border-image: linear-gradient(transparent $border-offset, var(--newtab-border-color) $border-offset, var(--newtab-border-color) 100% - $border-offset, transparent 100% - $border-offset) 1; + border-inline-end: 1px solid; + position: relative; + + &.selected { + background: var(--newtab-element-hover-color); + } + + &:active { + background: var(--newtab-element-active-color); + } + } + + .contentSearchSettingsButton { + &:hover { + background: var(--newtab-element-hover-color); + color: var(--newtab-text-primary-color); + } + } + } + + .contentSearchHeaderRow > td > img, + .contentSearchSuggestionRow > td > .historyIcon { + margin-inline-start: 7px; + margin-inline-end: 15px; + } +} -- cgit v1.2.3