зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1582865 - Don't use document/window in tree component. r=yzen.
When using the ObjectInspector in an inspector extension sidebar, document is not an HTMLDocument but a Sandbox, which means we don't have access to the same properties than in a document. Since the Tree and the ObjectInspector components do use `document` and `window`, this means some interaction would lead to the sidebar crashing. To prevent this, we retrieve the document and/or the window from a node reference instead. Differential Revision: https://phabricator.services.mozilla.com/D47271 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
836f56e2ad
Коммит
97ff4e89aa
|
@ -90,7 +90,8 @@ class TreeNode extends Component {
|
|||
// focusable element inside.
|
||||
const elms = this.getFocusableElements();
|
||||
if (this.props.active) {
|
||||
if (elms.length > 0 && !elms.includes(document.activeElement)) {
|
||||
const doc = this.treeNodeRef.current.ownerDocument;
|
||||
if (elms.length > 0 && !elms.includes(doc.activeElement)) {
|
||||
elms[0].focus();
|
||||
}
|
||||
} else {
|
||||
|
@ -232,9 +233,12 @@ const TreeNodeFactory = createFactory(TreeNode);
|
|||
* frame.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} options: object that contains the following properties:
|
||||
* - {Function} getDocument: A function that return the document
|
||||
* the component is rendered in.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function oncePerAnimationFrame(fn) {
|
||||
function oncePerAnimationFrame(fn, { getDocument }) {
|
||||
let animationId = null;
|
||||
let argsToPass = null;
|
||||
return function(...args) {
|
||||
|
@ -243,7 +247,12 @@ function oncePerAnimationFrame(fn) {
|
|||
return;
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(() => {
|
||||
const doc = getDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
animationId = doc.defaultView.requestAnimationFrame(() => {
|
||||
fn.call(this, ...argsToPass);
|
||||
animationId = null;
|
||||
argsToPass = null;
|
||||
|
@ -493,17 +502,19 @@ class Tree extends Component {
|
|||
|
||||
this.treeRef = React.createRef();
|
||||
|
||||
this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
|
||||
this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
|
||||
this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
|
||||
this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
|
||||
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(
|
||||
this
|
||||
);
|
||||
this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(
|
||||
this
|
||||
);
|
||||
this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
|
||||
const opaf = fn =>
|
||||
oncePerAnimationFrame(fn, {
|
||||
getDocument: () =>
|
||||
this.treeRef.current && this.treeRef.current.ownerDocument,
|
||||
});
|
||||
|
||||
this._onExpand = opaf(this._onExpand).bind(this);
|
||||
this._onCollapse = opaf(this._onCollapse).bind(this);
|
||||
this._focusPrevNode = opaf(this._focusPrevNode).bind(this);
|
||||
this._focusNextNode = opaf(this._focusNextNode).bind(this);
|
||||
this._focusParentNode = opaf(this._focusParentNode).bind(this);
|
||||
this._focusFirstNode = opaf(this._focusFirstNode).bind(this);
|
||||
this._focusLastNode = opaf(this._focusLastNode).bind(this);
|
||||
|
||||
this._autoExpand = this._autoExpand.bind(this);
|
||||
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
|
||||
|
@ -711,7 +722,8 @@ class Tree extends Component {
|
|||
|
||||
if (this.props.active != undefined) {
|
||||
this._activate(undefined);
|
||||
if (this.treeRef.current !== document.activeElement) {
|
||||
const doc = this.treeRef.current && this.treeRef.current.ownerDocument;
|
||||
if (this.treeRef.current !== doc.activeElement) {
|
||||
this.treeRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
@ -748,7 +760,8 @@ class Tree extends Component {
|
|||
_scrollNodeIntoView(item, options = {}) {
|
||||
if (item !== undefined) {
|
||||
const treeElement = this.treeRef.current;
|
||||
const element = document.getElementById(this.props.getKey(item));
|
||||
const doc = treeElement && treeElement.ownerDocument;
|
||||
const element = doc.getElementById(this.props.getKey(item));
|
||||
|
||||
if (element) {
|
||||
const { top, bottom } = element.getBoundingClientRect();
|
||||
|
@ -813,6 +826,7 @@ class Tree extends Component {
|
|||
}
|
||||
|
||||
this._preventArrowKeyScrolling(e);
|
||||
const doc = this.treeRef.current && this.treeRef.current.ownerDocument;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
|
@ -855,7 +869,7 @@ class Tree extends Component {
|
|||
|
||||
case "Enter":
|
||||
case " ":
|
||||
if (this.treeRef.current === document.activeElement) {
|
||||
if (this.treeRef.current === doc.activeElement) {
|
||||
this._preventEvent(e);
|
||||
if (this.props.active !== this.props.focused) {
|
||||
this._activate(this.props.focused);
|
||||
|
@ -869,7 +883,7 @@ class Tree extends Component {
|
|||
this._activate(undefined);
|
||||
}
|
||||
|
||||
if (this.treeRef.current !== document.activeElement) {
|
||||
if (this.treeRef.current !== doc.activeElement) {
|
||||
this.treeRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,8 +246,9 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
// image, clicking on it does not remove any existing text selection.
|
||||
// So we need to also check if the arrow was clicked.
|
||||
if (
|
||||
Utils.selection.documentHasSelection() &&
|
||||
!(e.target && e.target.matches && e.target.matches(".arrow"))
|
||||
e.target &&
|
||||
Utils.selection.documentHasSelection(e.target.ownerDocument) &&
|
||||
!(e.target.matches && e.target.matches(".arrow"))
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
@ -283,7 +284,9 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
event.stopPropagation();
|
||||
|
||||
// If the user selected text, bail out.
|
||||
if (Utils.selection.documentHasSelection()) {
|
||||
if (
|
||||
Utils.selection.documentHasSelection(event.target.ownerDocument)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* 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/>. */
|
||||
|
||||
function documentHasSelection(): boolean {
|
||||
const selection = getSelection();
|
||||
function documentHasSelection(doc = document): boolean {
|
||||
const selection = doc.defaultView.getSelection();
|
||||
if (!selection) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -214,7 +214,9 @@ class TreeNode extends Component {
|
|||
const elms = this.getFocusableElements();
|
||||
|
||||
if (this.props.active) {
|
||||
if (elms.length > 0 && !elms.includes(document.activeElement)) {
|
||||
const doc = this.treeNodeRef.current.ownerDocument;
|
||||
|
||||
if (elms.length > 0 && !elms.includes(doc.activeElement)) {
|
||||
elms[0].focus();
|
||||
}
|
||||
} else {
|
||||
|
@ -345,10 +347,15 @@ const TreeNodeFactory = createFactory(TreeNode);
|
|||
* frame.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} options: object that contains the following properties:
|
||||
* - {Function} getDocument: A function that return the document
|
||||
* the component is rendered in.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
function oncePerAnimationFrame(fn) {
|
||||
function oncePerAnimationFrame(fn, {
|
||||
getDocument
|
||||
}) {
|
||||
let animationId = null;
|
||||
let argsToPass = null;
|
||||
return function (...args) {
|
||||
|
@ -358,7 +365,13 @@ function oncePerAnimationFrame(fn) {
|
|||
return;
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(() => {
|
||||
const doc = getDocument();
|
||||
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
animationId = doc.defaultView.requestAnimationFrame(() => {
|
||||
fn.call(this, ...argsToPass);
|
||||
animationId = null;
|
||||
argsToPass = null;
|
||||
|
@ -592,13 +605,18 @@ class Tree extends Component {
|
|||
autoExpanded: new Set()
|
||||
};
|
||||
this.treeRef = _react.default.createRef();
|
||||
this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
|
||||
this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
|
||||
this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
|
||||
this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
|
||||
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
|
||||
this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this);
|
||||
this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
|
||||
|
||||
const opaf = fn => oncePerAnimationFrame(fn, {
|
||||
getDocument: () => this.treeRef.current && this.treeRef.current.ownerDocument
|
||||
});
|
||||
|
||||
this._onExpand = opaf(this._onExpand).bind(this);
|
||||
this._onCollapse = opaf(this._onCollapse).bind(this);
|
||||
this._focusPrevNode = opaf(this._focusPrevNode).bind(this);
|
||||
this._focusNextNode = opaf(this._focusNextNode).bind(this);
|
||||
this._focusParentNode = opaf(this._focusParentNode).bind(this);
|
||||
this._focusFirstNode = opaf(this._focusFirstNode).bind(this);
|
||||
this._focusLastNode = opaf(this._focusLastNode).bind(this);
|
||||
this._autoExpand = this._autoExpand.bind(this);
|
||||
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
|
||||
this._preventEvent = this._preventEvent.bind(this);
|
||||
|
@ -823,7 +841,9 @@ class Tree extends Component {
|
|||
if (this.props.active != undefined) {
|
||||
this._activate(undefined);
|
||||
|
||||
if (this.treeRef.current !== document.activeElement) {
|
||||
const doc = this.treeRef.current && this.treeRef.current.ownerDocument;
|
||||
|
||||
if (this.treeRef.current !== doc.activeElement) {
|
||||
this.treeRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
@ -862,7 +882,8 @@ class Tree extends Component {
|
|||
_scrollNodeIntoView(item, options = {}) {
|
||||
if (item !== undefined) {
|
||||
const treeElement = this.treeRef.current;
|
||||
const element = document.getElementById(this.props.getKey(item));
|
||||
const doc = treeElement && treeElement.ownerDocument;
|
||||
const element = doc.getElementById(this.props.getKey(item));
|
||||
|
||||
if (element) {
|
||||
const {
|
||||
|
@ -934,6 +955,8 @@ class Tree extends Component {
|
|||
|
||||
this._preventArrowKeyScrolling(e);
|
||||
|
||||
const doc = this.treeRef.current && this.treeRef.current.ownerDocument;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
this._focusPrevNode();
|
||||
|
@ -975,7 +998,7 @@ class Tree extends Component {
|
|||
|
||||
case "Enter":
|
||||
case " ":
|
||||
if (this.treeRef.current === document.activeElement) {
|
||||
if (this.treeRef.current === doc.activeElement) {
|
||||
this._preventEvent(e);
|
||||
|
||||
if (this.props.active !== this.props.focused) {
|
||||
|
@ -992,7 +1015,7 @@ class Tree extends Component {
|
|||
this._activate(undefined);
|
||||
}
|
||||
|
||||
if (this.treeRef.current !== document.activeElement) {
|
||||
if (this.treeRef.current !== doc.activeElement) {
|
||||
this.treeRef.current.focus();
|
||||
}
|
||||
|
||||
|
@ -8250,7 +8273,7 @@ class ObjectInspectorItem extends Component {
|
|||
// So we need to also check if the arrow was clicked.
|
||||
|
||||
|
||||
if (Utils.selection.documentHasSelection() && !(e.target && e.target.matches && e.target.matches(".arrow"))) {
|
||||
if (e.target && Utils.selection.documentHasSelection(e.target.ownerDocument) && !(e.target.matches && e.target.matches(".arrow"))) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
@ -8288,7 +8311,7 @@ class ObjectInspectorItem extends Component {
|
|||
onClick: onLabelClick ? event => {
|
||||
event.stopPropagation(); // If the user selected text, bail out.
|
||||
|
||||
if (Utils.selection.documentHasSelection()) {
|
||||
if (Utils.selection.documentHasSelection(event.target.ownerDocument)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -8347,8 +8370,8 @@ module.exports = ObjectInspectorItem;
|
|||
/* 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/>. */
|
||||
function documentHasSelection() {
|
||||
const selection = getSelection();
|
||||
function documentHasSelection(doc = document) {
|
||||
const selection = doc.defaultView.getSelection();
|
||||
|
||||
if (!selection) {
|
||||
return false;
|
||||
|
|
Загрузка…
Ссылка в новой задаче