зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
Коммит
b52345353d
|
@ -16,7 +16,7 @@ import {
|
|||
import { PROMISE } from "../utils/middleware/promise";
|
||||
import {
|
||||
getSymbols,
|
||||
getFirstVisibleBreakpointPosition,
|
||||
getFirstBreakpointPosition,
|
||||
getBreakpointPositionsForSource,
|
||||
getSourceFromId
|
||||
} from "../../selectors";
|
||||
|
@ -105,7 +105,7 @@ export function addBreakpoint(
|
|||
const { sourceId, column } = location;
|
||||
|
||||
if (column === undefined) {
|
||||
position = getFirstVisibleBreakpointPosition(getState(), location);
|
||||
position = getFirstBreakpointPosition(getState(), location);
|
||||
} else {
|
||||
const positions = getBreakpointPositionsForSource(getState(), sourceId);
|
||||
position = findPosition(positions, location);
|
||||
|
|
|
@ -18,7 +18,8 @@ import { getTextAtPosition } from "../../utils/source";
|
|||
import { comparePosition } from "../../utils/location";
|
||||
|
||||
import { originalToGeneratedId, isOriginalId } from "devtools-source-map";
|
||||
import { getSource } from "../../selectors";
|
||||
import { getSource, getBreakpointsList } from "../../selectors";
|
||||
import { removeBreakpoint } from ".";
|
||||
|
||||
import type { ThunkArgs, Action } from "../types";
|
||||
|
||||
|
@ -87,6 +88,19 @@ function createSyncData(
|
|||
return { breakpoint, previousLocation };
|
||||
}
|
||||
|
||||
// Look for an existing breakpoint at the specified generated location.
|
||||
function findExistingBreakpoint(state, generatedLocation) {
|
||||
const breakpoints = getBreakpointsList(state);
|
||||
|
||||
return breakpoints.find(bp => {
|
||||
return (
|
||||
bp.generatedLocation.sourceUrl == generatedLocation.sourceUrl &&
|
||||
bp.generatedLocation.line == generatedLocation.line &&
|
||||
bp.generatedLocation.column == generatedLocation.column
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// we have three forms of syncing: disabled syncing, existing server syncing
|
||||
// and adding a new breakpoint
|
||||
export async function syncBreakpointPromise(
|
||||
|
@ -94,7 +108,7 @@ export async function syncBreakpointPromise(
|
|||
sourceId: SourceId,
|
||||
pendingBreakpoint: PendingBreakpoint
|
||||
): Promise<?BreakpointSyncData> {
|
||||
const { getState, client } = thunkArgs;
|
||||
const { getState, client, dispatch } = thunkArgs;
|
||||
assertPendingBreakpoint(pendingBreakpoint);
|
||||
|
||||
const source = getSource(getState(), sourceId);
|
||||
|
@ -152,8 +166,11 @@ export async function syncBreakpointPromise(
|
|||
);
|
||||
}
|
||||
|
||||
// clear server breakpoints if they exist and we have moved
|
||||
await client.removeBreakpoint(generatedLocation);
|
||||
// Clear any breakpoint for the generated location.
|
||||
const bp = findExistingBreakpoint(getState(), generatedLocation);
|
||||
if (bp) {
|
||||
await dispatch(removeBreakpoint(bp));
|
||||
}
|
||||
|
||||
if (!newGeneratedLocation) {
|
||||
return { previousLocation, breakpoint: null };
|
||||
|
|
|
@ -13,7 +13,7 @@ import { generatedToOriginalId } from "devtools-source-map";
|
|||
import { flatten } from "lodash";
|
||||
|
||||
import { toggleBlackBox } from "./blackbox";
|
||||
import { syncBreakpoint } from "../breakpoints";
|
||||
import { syncBreakpoint, addBreakpoint } from "../breakpoints";
|
||||
import { loadSourceText } from "./loadSourceText";
|
||||
import { togglePrettyPrint } from "./prettyPrint";
|
||||
import { selectLocation } from "../sources";
|
||||
|
@ -186,8 +186,19 @@ function checkPendingBreakpoints(sourceId: string) {
|
|||
// load the source text if there is a pending breakpoint for it
|
||||
await dispatch(loadSourceText(source));
|
||||
|
||||
// Matching pending breakpoints could have either the same generated or the
|
||||
// same original source. We expect the generated source to appear first and
|
||||
// will add a breakpoint at that location initially. If the original source
|
||||
// appears later then we use syncBreakpoint to see if the generated location
|
||||
// changed and we need to remove the breakpoint we added earlier.
|
||||
await Promise.all(
|
||||
pendingBreakpoints.map(bp => dispatch(syncBreakpoint(sourceId, bp)))
|
||||
pendingBreakpoints.map(bp => {
|
||||
if (source.url == bp.location.sourceUrl) {
|
||||
return dispatch(syncBreakpoint(sourceId, bp));
|
||||
}
|
||||
const { line, column } = bp.generatedLocation;
|
||||
return dispatch(addBreakpoint({ sourceId, line, column }, bp.options));
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -161,9 +161,12 @@ export function getPendingBreakpointsForSource(
|
|||
return [];
|
||||
}
|
||||
|
||||
return getPendingBreakpointList(state).filter(
|
||||
pendingBreakpoint => pendingBreakpoint.location.sourceUrl === source.url
|
||||
);
|
||||
return getPendingBreakpointList(state).filter(pendingBreakpoint => {
|
||||
return (
|
||||
pendingBreakpoint.location.sourceUrl === source.url ||
|
||||
pendingBreakpoint.generatedLocation.sourceUrl == source.url
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default update;
|
||||
|
|
|
@ -173,19 +173,3 @@ export function getFirstBreakpointPosition(
|
|||
position => getSelectedLocation(position, source).line == line
|
||||
);
|
||||
}
|
||||
|
||||
export function getFirstVisibleBreakpointPosition(
|
||||
state: State,
|
||||
{ line }: SourceLocation
|
||||
) {
|
||||
const positions = getVisibleBreakpointPositions(state);
|
||||
const selectedSource = getSelectedSource(state);
|
||||
|
||||
if (!selectedSource || !positions) {
|
||||
return;
|
||||
}
|
||||
|
||||
return positions.find(
|
||||
position => getSelectedLocation(position, selectedSource).line == line
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,31 +6,34 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { createFactory, createRef, PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
|
||||
const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
|
||||
|
||||
loader.lazyGetter(this, "AutocompletePopup", function() {
|
||||
return createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
|
||||
});
|
||||
loader.lazyGetter(this, "MDNLink", function() {
|
||||
return createFactory(require("./MdnLink"));
|
||||
});
|
||||
|
||||
class SearchBox extends Component {
|
||||
loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
|
||||
|
||||
class SearchBox extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
autocompleteProvider: PropTypes.func,
|
||||
delay: PropTypes.number,
|
||||
keyShortcut: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
plainStyle: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
autocompleteProvider: PropTypes.func,
|
||||
learnMoreUrl: PropTypes.string,
|
||||
learnMoreTitle: PropTypes.string,
|
||||
learnMoreUrl: PropTypes.string,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
plainStyle: PropTypes.bool,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,10 +45,13 @@ class SearchBox extends Component {
|
|||
focused: false,
|
||||
};
|
||||
|
||||
this.autocompleteRef = createRef();
|
||||
this.inputRef = createRef();
|
||||
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onClearButtonClick = this.onClearButtonClick.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
|
@ -59,7 +65,7 @@ class SearchBox extends Component {
|
|||
});
|
||||
this.shortcuts.on(this.props.keyShortcut, event => {
|
||||
event.preventDefault();
|
||||
this.refs.input.focus();
|
||||
this.inputRef.current.focus();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -75,10 +81,10 @@ class SearchBox extends Component {
|
|||
}
|
||||
|
||||
onChange() {
|
||||
if (this.state.value !== this.refs.input.value) {
|
||||
if (this.state.value !== this.inputRef.current.value) {
|
||||
this.setState({
|
||||
focused: true,
|
||||
value: this.refs.input.value,
|
||||
value: this.inputRef.current.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -101,7 +107,7 @@ class SearchBox extends Component {
|
|||
}
|
||||
|
||||
onClearButtonClick() {
|
||||
this.refs.input.value = "";
|
||||
this.setState({ value: "" });
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
|
@ -126,7 +132,7 @@ class SearchBox extends Component {
|
|||
this.props.onKeyDown();
|
||||
}
|
||||
|
||||
const { autocomplete } = this.refs;
|
||||
const autocomplete = this.autocompleteRef.current;
|
||||
if (!autocomplete || autocomplete.state.list.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -164,55 +170,54 @@ class SearchBox extends Component {
|
|||
|
||||
render() {
|
||||
let {
|
||||
type = "search",
|
||||
placeholder,
|
||||
autocompleteProvider,
|
||||
plainStyle,
|
||||
learnMoreUrl,
|
||||
learnMoreTitle,
|
||||
learnMoreUrl,
|
||||
placeholder,
|
||||
plainStyle,
|
||||
type = "search",
|
||||
} = this.props;
|
||||
const { value } = this.state;
|
||||
const divClassList = ["devtools-searchbox", "has-clear-btn"];
|
||||
const showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
|
||||
|
||||
const inputClassList = [`devtools-${type}input`];
|
||||
if (plainStyle) {
|
||||
inputClassList.push("devtools-plaininput");
|
||||
}
|
||||
const showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
|
||||
|
||||
if (value !== "") {
|
||||
inputClassList.push("filled");
|
||||
learnMoreUrl = false;
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{ className: divClassList.join(" ") },
|
||||
{ className: "devtools-searchbox has-clear-btn" },
|
||||
dom.input({
|
||||
className: inputClassList.join(" "),
|
||||
onBlur: this.onBlur,
|
||||
onChange: this.onChange,
|
||||
onFocus: this.onFocus,
|
||||
onBlur: this.onBlur,
|
||||
onKeyDown: this.onKeyDown,
|
||||
placeholder,
|
||||
ref: "input",
|
||||
ref: this.inputRef,
|
||||
value,
|
||||
}),
|
||||
dom.button({
|
||||
className: "devtools-searchinput-clear",
|
||||
hidden: value == "",
|
||||
hidden: value === "",
|
||||
onClick: this.onClearButtonClick,
|
||||
}),
|
||||
learnMoreUrl && MDNLink({
|
||||
url: learnMoreUrl,
|
||||
title: learnMoreTitle,
|
||||
url: learnMoreUrl,
|
||||
}),
|
||||
showAutocomplete && AutocompletePopup({
|
||||
autocompleteProvider,
|
||||
filter: value,
|
||||
ref: "autocomplete",
|
||||
onItemSelected: (itemValue) => {
|
||||
this.setState({ value: itemValue });
|
||||
this.onChange();
|
||||
},
|
||||
ref: this.autocompleteRef,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3128,6 +3128,12 @@ nsIContent* nsFocusManager::GetNextTabbableContentInScope(
|
|||
break;
|
||||
}
|
||||
|
||||
// We've been just trying to find some focusable element, and haven't, so
|
||||
// bail out.
|
||||
if (aIgnoreTabIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue looking for next highest priority tabindex
|
||||
aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
|
||||
contentTraversal.Reset();
|
||||
|
|
|
@ -5,6 +5,17 @@
|
|||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script>
|
||||
|
||||
class TestNode extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const styles = "<style>:focus{background-color:yellow;}</style>";
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.innerHTML =
|
||||
`${styles}<div tabindex='-1'>test node</div> <slot></slot>`;
|
||||
}}
|
||||
|
||||
window.customElements.define('test-node', TestNode);
|
||||
|
||||
var lastFocusTarget;
|
||||
function focusLogger(event) {
|
||||
lastFocusTarget = event.target;
|
||||
|
@ -774,6 +785,52 @@
|
|||
input1.remove();
|
||||
}
|
||||
|
||||
function testDeeplyNestedShadowTree() {
|
||||
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||
var host1 = document.createElement("test-node");
|
||||
var lastHost = host1;
|
||||
for (var i = 0; i < 20; ++i) {
|
||||
lastHost.appendChild(document.createElement("test-node"));
|
||||
lastHost = lastHost.firstChild;
|
||||
}
|
||||
|
||||
var input = document.createElement("input");
|
||||
document.body.appendChild(host1);
|
||||
document.body.appendChild(input);
|
||||
document.body.offsetLeft;
|
||||
|
||||
// Test shadow tree which doesn't have anything tab-focusable.
|
||||
host1.shadowRoot.getElementsByTagName("div")[0].focus();
|
||||
synthesizeKey("KEY_Tab");
|
||||
is(document.activeElement, input, "Should have focused input element.");
|
||||
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||
|
||||
// Same test but with focusable elements in the tree...
|
||||
var input2 = document.createElement("input");
|
||||
var host2 = host1.firstChild;
|
||||
var host3 = host2.firstChild;
|
||||
host2.insertBefore(input2, host3);
|
||||
var input3 = document.createElement("input");
|
||||
lastHost.appendChild(input3);
|
||||
document.body.offsetLeft;
|
||||
host3.shadowRoot.getElementsByTagName("div")[0].focus();
|
||||
synthesizeKey("KEY_Tab");
|
||||
is(document.activeElement, input3, "Should have focused input3 element.");
|
||||
|
||||
// ...and backwards
|
||||
host3.shadowRoot.getElementsByTagName("div")[0].focus();
|
||||
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||
is(document.activeElement, input2, "Should have focused input2 element.");
|
||||
|
||||
// Remove elements added to body element.
|
||||
host1.remove();
|
||||
input.remove();
|
||||
|
||||
// Tests expect body.firstChild to have focus.
|
||||
document.body.firstChild.focus();
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
testTabbingThroughShadowDOMWithTabIndexes();
|
||||
|
@ -787,6 +844,7 @@
|
|||
testTabbingThroughSlotInLightDOM();
|
||||
testTabbingThroughFocusableSlotInLightDOM();
|
||||
testTabbingThroughScrollableShadowDOM();
|
||||
testDeeplyNestedShadowTree();
|
||||
|
||||
opener.didRunTests();
|
||||
window.close();
|
||||
|
|
Загрузка…
Ссылка в новой задаче