Bug 1518197 - handle paste events in search hand-off (#4646)
This commit is contained in:
Родитель
8b65268c67
Коммит
f1967e12e8
|
@ -13,7 +13,9 @@ export class _Search extends React.PureComponent {
|
|||
this.onSearchClick = this.onSearchClick.bind(this);
|
||||
this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
|
||||
this.onSearchHandoffKeyDown = this.onSearchHandoffKeyDown.bind(this);
|
||||
this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
|
||||
this.onInputMount = this.onInputMount.bind(this);
|
||||
this.onSearchHandoffButtonMount = this.onSearchHandoffButtonMount.bind(this);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
|
@ -53,7 +55,33 @@ export class _Search extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onSearchHandoffPaste(event) {
|
||||
if (!this._searchHandoffButton ||
|
||||
!this._searchHandoffButton.contains(global.document.activeElement)) {
|
||||
// Don't handle every paste on the document. Filter out those that are
|
||||
// not on the Search Hand-off button.
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const text = event.clipboardData.getData("Text");
|
||||
this.props.dispatch(ac.OnlyToMain({type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: {text}}));
|
||||
|
||||
// TODO: Send a telemetry ping. BUG 1514732
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (global.document) {
|
||||
// We need to listen to paste events that bubble up from the Search Hand-off
|
||||
// button. Adding the paste listener to the button itself or it's parent
|
||||
// doesn't work consistently until the page is clicked on.
|
||||
global.document.addEventListener("paste", this.onSearchHandoffPaste);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (global.document) {
|
||||
global.document.removeEventListener("paste", this.onDocumentPaste);
|
||||
}
|
||||
delete window.gContentSearchController;
|
||||
}
|
||||
|
||||
|
@ -85,6 +113,11 @@ export class _Search extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -129,10 +162,12 @@ export class _Search extends React.PureComponent {
|
|||
<div className="search-inner-wrapper">
|
||||
<button
|
||||
className="search-handoff-button"
|
||||
ref={this.onSearchHandoffButtonMount}
|
||||
onClick={this.onSearchHandoffClick}
|
||||
onKeyDown={this.onSearchHandoffKeyDown}
|
||||
title={this.props.intl.formatMessage({id: "search_web_placeholder"})}>
|
||||
<div className="fake-textbox">{this.props.intl.formatMessage({id: "search_web_placeholder"})}</div>
|
||||
<div className="fake-editable" tabIndex="-1" aria-hidden="true" contentEditable="" />
|
||||
<div className="fake-caret" />
|
||||
</button>
|
||||
{/*
|
||||
|
|
|
@ -175,6 +175,20 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.fake-editable:focus {
|
||||
outline: none;
|
||||
caret-color: transparent;
|
||||
}
|
||||
|
||||
.fake-editable {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.fake-textbox {
|
||||
opacity: 0.54;
|
||||
text-align: start;
|
||||
|
|
|
@ -297,17 +297,21 @@ class PlacesFeed {
|
|||
if (data.text) {
|
||||
// Pass the provided text to the awesomebar.
|
||||
urlBar.search(data.text);
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
|
||||
} else {
|
||||
// Focus the awesomebar without the style changes.
|
||||
urlBar.hiddenFocus();
|
||||
}
|
||||
|
||||
const onKeydown = () => {
|
||||
// Once the user starts typing, we want to hide the in content search box
|
||||
// and show the focus styles on the awesomebar.
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
|
||||
urlBar.removeHiddenFocus();
|
||||
urlBar.removeEventListener("keydown", onKeydown);
|
||||
const onKeydown = event => {
|
||||
// We only care about key strokes that will produce a character.
|
||||
if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
||||
// Once the user starts typing, we want to hide the in content search box
|
||||
// and show the focus styles on the awesomebar.
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
|
||||
urlBar.removeHiddenFocus();
|
||||
urlBar.removeEventListener("keydown", onKeydown);
|
||||
}
|
||||
};
|
||||
const onDone = () => {
|
||||
// When done, let's cleanup everything.
|
||||
|
|
|
@ -130,5 +130,21 @@ describe("<Search>", () => {
|
|||
wrapper.find(".search-handoff-button").simulate("keydown", {key: "f", metaKey: true});
|
||||
assert.notCalled(dispatch);
|
||||
});
|
||||
it("should hand-off search on paste", () => {
|
||||
const dispatch = sinon.spy();
|
||||
const wrapper = mountWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
|
||||
wrapper.instance()._searchHandoffButton = {contains: () => true};
|
||||
wrapper.instance().onSearchHandoffPaste({
|
||||
clipboardData: {
|
||||
getData: () => "some copied text",
|
||||
},
|
||||
preventDefault: () => {},
|
||||
});
|
||||
assert.calledWith(dispatch, {
|
||||
data: {text: "some copied text"},
|
||||
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
|
||||
type: "HANDOFF_SEARCH_TO_AWESOMEBAR",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -365,9 +365,21 @@ describe("PlacesFeed", () => {
|
|||
assert.notCalled(fakeUrlBar.focus);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
||||
// Now call keydown listener.
|
||||
// Now call keydown listener with "Ctrl".
|
||||
feed.store.dispatch.resetHistory();
|
||||
listeners.keydown();
|
||||
listeners.keydown({key: "Ctrl"});
|
||||
assert.notCalled(fakeUrlBar.removeHiddenFocus);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
||||
// Now call keydown listener with "Ctrl+f".
|
||||
feed.store.dispatch.resetHistory();
|
||||
listeners.keydown({key: "f", ctrlKey: true});
|
||||
assert.notCalled(fakeUrlBar.removeHiddenFocus);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
||||
// Now call keydown listener with "f".
|
||||
feed.store.dispatch.resetHistory();
|
||||
listeners.keydown({key: "f"});
|
||||
assert.calledOnce(fakeUrlBar.removeHiddenFocus);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
|
@ -406,7 +418,16 @@ describe("PlacesFeed", () => {
|
|||
assert.calledWith(fakeUrlBar.search, "f");
|
||||
assert.notCalled(fakeUrlBar.hiddenFocus);
|
||||
assert.notCalled(fakeUrlBar.focus);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
meta: {
|
||||
from: "ActivityStream:Main",
|
||||
skipMain: true,
|
||||
to: "ActivityStream:Content",
|
||||
toTarget: {},
|
||||
},
|
||||
type: "HIDE_SEARCH",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче