зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1520389 - Implement the selector highlighter in the new rules view. r=rcaliman
This commit is contained in:
Родитель
e42f7abbcf
Коммит
a5f46f0927
|
@ -19,6 +19,9 @@ createEnum([
|
|||
// Toggles on or off the given pseudo class value for the current selected element.
|
||||
"TOGGLE_PSEUDO_CLASS",
|
||||
|
||||
// Updates the highlighted selector.
|
||||
"UPDATE_HIGHLIGHTED_SELECTOR",
|
||||
|
||||
// Updates the rules state with the new list of CSS rules for the selected element.
|
||||
"UPDATE_RULES",
|
||||
|
||||
|
|
|
@ -5,11 +5,25 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
UPDATE_HIGHLIGHTED_SELECTOR,
|
||||
UPDATE_RULES,
|
||||
} = require("./index");
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Updates the highlighted selector.
|
||||
*
|
||||
* @param {String} highlightedSelector
|
||||
* The selector of the element to be highlighted by the selector highlighter.
|
||||
*/
|
||||
updateHighlightedSelector(highlightedSelector) {
|
||||
return {
|
||||
type: UPDATE_HIGHLIGHTED_SELECTOR,
|
||||
highlightedSelector,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the rules state with the new list of CSS rules for the selected element.
|
||||
*
|
||||
|
|
|
@ -10,6 +10,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||
|
||||
const Declarations = createFactory(require("./Declarations"));
|
||||
const Selector = createFactory(require("./Selector"));
|
||||
const SelectorHighlighter = createFactory(require("./SelectorHighlighter"));
|
||||
const SourceLink = createFactory(require("./SourceLink"));
|
||||
|
||||
const Types = require("../types");
|
||||
|
@ -18,6 +19,7 @@ class Rule extends PureComponent {
|
|||
static get propTypes() {
|
||||
return {
|
||||
onToggleDeclaration: PropTypes.func.isRequired,
|
||||
onToggleSelectorHighlighter: PropTypes.func.isRequired,
|
||||
rule: PropTypes.shape(Types.rule).isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -25,6 +27,7 @@ class Rule extends PureComponent {
|
|||
render() {
|
||||
const {
|
||||
onToggleDeclaration,
|
||||
onToggleSelectorHighlighter,
|
||||
rule,
|
||||
} = this.props;
|
||||
const {
|
||||
|
@ -44,6 +47,13 @@ class Rule extends PureComponent {
|
|||
selector,
|
||||
type,
|
||||
}),
|
||||
type !== CSSRule.KEYFRAME_RULE ?
|
||||
SelectorHighlighter({
|
||||
onToggleSelectorHighlighter,
|
||||
selector,
|
||||
})
|
||||
:
|
||||
null,
|
||||
dom.span({ className: "ruleview-ruleopen" }, " {")
|
||||
),
|
||||
Declarations({
|
||||
|
|
|
@ -15,6 +15,7 @@ class Rules extends PureComponent {
|
|||
static get propTypes() {
|
||||
return {
|
||||
onToggleDeclaration: PropTypes.func.isRequired,
|
||||
onToggleSelectorHighlighter: PropTypes.func.isRequired,
|
||||
rules: PropTypes.arrayOf(PropTypes.shape(Types.rule)).isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -22,6 +23,7 @@ class Rules extends PureComponent {
|
|||
render() {
|
||||
const {
|
||||
onToggleDeclaration,
|
||||
onToggleSelectorHighlighter,
|
||||
rules,
|
||||
} = this.props;
|
||||
|
||||
|
@ -29,6 +31,7 @@ class Rules extends PureComponent {
|
|||
return Rule({
|
||||
key: rule.id,
|
||||
onToggleDeclaration,
|
||||
onToggleSelectorHighlighter,
|
||||
rule,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ class RulesApp extends PureComponent {
|
|||
return {
|
||||
onToggleDeclaration: PropTypes.func.isRequired,
|
||||
onTogglePseudoClass: PropTypes.func.isRequired,
|
||||
onToggleSelectorHighlighter: PropTypes.func.isRequired,
|
||||
rules: PropTypes.arrayOf(PropTypes.shape(Types.rule)).isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -53,6 +54,7 @@ class RulesApp extends PureComponent {
|
|||
|
||||
output.push(Rule({
|
||||
onToggleDeclaration: this.props.onToggleDeclaration,
|
||||
onToggleSelectorHighlighter: this.props.onToggleSelectorHighlighter,
|
||||
rule,
|
||||
}));
|
||||
}
|
||||
|
@ -80,6 +82,7 @@ class RulesApp extends PureComponent {
|
|||
component: Rules,
|
||||
componentProps: {
|
||||
onToggleDeclaration: this.props.onToggleDeclaration,
|
||||
onToggleSelectorHighlighter: this.props.onToggleSelectorHighlighter,
|
||||
rules: rules.filter(r => r.keyframesRule.id === lastKeyframes),
|
||||
},
|
||||
header: rule.keyframesRule.keyframesName,
|
||||
|
@ -100,6 +103,7 @@ class RulesApp extends PureComponent {
|
|||
|
||||
return Rules({
|
||||
onToggleDeclaration: this.props.onToggleDeclaration,
|
||||
onToggleSelectorHighlighter: this.props.onToggleSelectorHighlighter,
|
||||
rules,
|
||||
});
|
||||
}
|
||||
|
@ -114,6 +118,7 @@ class RulesApp extends PureComponent {
|
|||
component: Rules,
|
||||
componentProps: {
|
||||
onToggleDeclaration: this.props.onToggleDeclaration,
|
||||
onToggleSelectorHighlighter: this.props.onToggleSelectorHighlighter,
|
||||
rules,
|
||||
},
|
||||
header: getStr("rule.pseudoElement"),
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const Types = require("../types");
|
||||
|
||||
class SelectorHighlighter extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
highlightedSelector: PropTypes.string.isRequired,
|
||||
onToggleSelectorHighlighter: PropTypes.func.isRequired,
|
||||
selector: PropTypes.shape(Types.selector).isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// A unique selector to the current Rule. This is checked against the value
|
||||
// of any existing highlighted selector in order mark the toggled state of
|
||||
// the component.
|
||||
uniqueSelector: "",
|
||||
};
|
||||
|
||||
this.onToggleHighlighterClick = this.onToggleHighlighterClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Only fetch the unique selector if a selector is highlighted.
|
||||
if (this.props.highlightedSelector) {
|
||||
this.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
async onToggleHighlighterClick(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const {
|
||||
onToggleSelectorHighlighter,
|
||||
selector,
|
||||
} = this.props;
|
||||
|
||||
const uniqueSelector = await selector.getUniqueSelector();
|
||||
this.setState({ uniqueSelector });
|
||||
onToggleSelectorHighlighter(uniqueSelector);
|
||||
}
|
||||
|
||||
async updateState() {
|
||||
const uniqueSelector = await this.props.selector.getUniqueSelector();
|
||||
this.setState({ uniqueSelector });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { highlightedSelector } = this.props;
|
||||
const { uniqueSelector } = this.state;
|
||||
|
||||
return (
|
||||
dom.span({
|
||||
className: "ruleview-selectorhighlighter" +
|
||||
(highlightedSelector && highlightedSelector === uniqueSelector ?
|
||||
" highlighted" : ""),
|
||||
onClick: this.onToggleHighlighterClick,
|
||||
title: getStr("rule.selectorHighlighter.tooltip"),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
highlightedSelector: state.rules.highlightedSelector,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = connect(mapStateToProps)(SelectorHighlighter);
|
|
@ -12,6 +12,7 @@ DevToolsModules(
|
|||
'RulesApp.js',
|
||||
'SearchBox.js',
|
||||
'Selector.js',
|
||||
'SelectorHighlighter.js',
|
||||
'SourceLink.js',
|
||||
'Toolbar.js',
|
||||
)
|
||||
|
|
|
@ -57,6 +57,8 @@ function Rule(elementStyle, options) {
|
|||
// value, and add in any disabled properties from the store.
|
||||
this.textProps = this._getTextProperties();
|
||||
this.textProps = this.textProps.concat(this._getDisabledProperties());
|
||||
|
||||
this.getUniqueSelector = this.getUniqueSelector.bind(this);
|
||||
}
|
||||
|
||||
Rule.prototype = {
|
||||
|
@ -79,6 +81,7 @@ Rule.prototype = {
|
|||
|
||||
get selector() {
|
||||
return {
|
||||
getUniqueSelector: this.getUniqueSelector,
|
||||
matchedSelectors: this.matchedSelectors,
|
||||
selectors: this.domRule.selectors,
|
||||
selectorText: this.keyframes ? this.domRule.keyText : this.selectorText,
|
||||
|
@ -180,6 +183,27 @@ Rule.prototype = {
|
|||
return this.textProps.find(textProp => textProp.id === id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an unique selector for the CSS rule.
|
||||
*/
|
||||
async getUniqueSelector() {
|
||||
let selector = "";
|
||||
|
||||
if (this.domRule.selectors) {
|
||||
// This is a style rule with a selector.
|
||||
selector = this.domRule.selectors.join(", ");
|
||||
} else if (this.inherited) {
|
||||
// This is an inline style from an inherited rule. Need to resolve the unique
|
||||
// selector from the node which rule this is inherited from.
|
||||
selector = await this.inherited.getUniqueSelector();
|
||||
} else {
|
||||
// This is an inline style from the current node.
|
||||
selector = this.elementStyle.ruleView.inspector.selectionCssSelector;
|
||||
}
|
||||
|
||||
return selector;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the rule matches the creation options
|
||||
* specified.
|
||||
|
|
|
@ -8,13 +8,17 @@ const Services = require("Services");
|
|||
const ElementStyle = require("devtools/client/inspector/rules/models/element-style");
|
||||
const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const {
|
||||
disableAllPseudoClasses,
|
||||
setPseudoClassLocks,
|
||||
togglePseudoClass,
|
||||
} = require("./actions/pseudo-classes");
|
||||
const { updateRules } = require("./actions/rules");
|
||||
const {
|
||||
updateHighlightedSelector,
|
||||
updateRules,
|
||||
} = require("./actions/rules");
|
||||
|
||||
const RulesApp = createFactory(require("./components/RulesApp"));
|
||||
|
||||
|
@ -40,6 +44,7 @@ class RulesView {
|
|||
this.onSelection = this.onSelection.bind(this);
|
||||
this.onToggleDeclaration = this.onToggleDeclaration.bind(this);
|
||||
this.onTogglePseudoClass = this.onTogglePseudoClass.bind(this);
|
||||
this.onToggleSelectorHighlighter = this.onToggleSelectorHighlighter.bind(this);
|
||||
this.updateRules = this.updateRules.bind(this);
|
||||
|
||||
this.inspector.sidebar.on("select", this.onSelection);
|
||||
|
@ -47,6 +52,8 @@ class RulesView {
|
|||
this.selection.on("new-node-front", this.onSelection);
|
||||
|
||||
this.init();
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -57,6 +64,7 @@ class RulesView {
|
|||
const rulesApp = RulesApp({
|
||||
onToggleDeclaration: this.onToggleDeclaration,
|
||||
onTogglePseudoClass: this.onTogglePseudoClass,
|
||||
onToggleSelectorHighlighter: this.onToggleSelectorHighlighter,
|
||||
});
|
||||
|
||||
const provider = createElement(Provider, {
|
||||
|
@ -75,6 +83,11 @@ class RulesView {
|
|||
this.selection.off("detached-front", this.onSelection);
|
||||
this.selection.off("new-node-front", this.onSelection);
|
||||
|
||||
if (this._selectHighlighter) {
|
||||
this._selectorHighlighter.finalize();
|
||||
this._selectorHighlighter = null;
|
||||
}
|
||||
|
||||
if (this.elementStyle) {
|
||||
this.elementStyle.destroy();
|
||||
}
|
||||
|
@ -109,6 +122,41 @@ class RulesView {
|
|||
return this._dummyElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highlighters overlay from the Inspector.
|
||||
*
|
||||
* @return {HighlighterOverlay}.
|
||||
*/
|
||||
get highlighters() {
|
||||
return this.inspector.highlighters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of SelectorHighlighter (used to highlight nodes that match
|
||||
* selectors in the rule-view).
|
||||
*
|
||||
* @return {Promise} resolves to the instance of the highlighter.
|
||||
*/
|
||||
async getSelectorHighlighter() {
|
||||
if (!this.inspector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._selectorHighlighter) {
|
||||
return this._selectorHighlighter;
|
||||
}
|
||||
|
||||
try {
|
||||
const front = this.inspector.inspector;
|
||||
this._selectorHighlighter = await front.getHighlighterByType("SelectorHighlighter");
|
||||
return this._selectorHighlighter;
|
||||
} catch (e) {
|
||||
// The SelectorHighlighter type could not be created in the
|
||||
// current target. It could be an older server, or a XUL page.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the rules panel is visible, and false otherwise.
|
||||
*/
|
||||
|
@ -164,6 +212,45 @@ class RulesView {
|
|||
this.inspector.togglePseudoClass(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for toggling the selector highlighter for the given selector.
|
||||
* Highlight/unhighlight all the nodes that match a given set of selectors inside the
|
||||
* document of the current selected node. Only one selector can be highlighted at a
|
||||
* time, so calling the method a second time with a different selector will first
|
||||
* unhighlight the previously highlighted nodes. Calling the method a second time with
|
||||
* the same select will unhighlight the highlighted nodes.
|
||||
*
|
||||
* @param {String} selector
|
||||
* The selector used to find nodes in the page.
|
||||
*/
|
||||
async onToggleSelectorHighlighter(selector) {
|
||||
const highlighter = await this.getSelectorHighlighter();
|
||||
if (!highlighter) {
|
||||
return;
|
||||
}
|
||||
|
||||
await highlighter.hide();
|
||||
|
||||
if (selector !== this.highlighters.selectorHighlighterShown) {
|
||||
this.store.dispatch(updateHighlightedSelector(selector));
|
||||
|
||||
await highlighter.show(this.selection.nodeFront, {
|
||||
hideInfoBar: true,
|
||||
hideGuides: true,
|
||||
selector,
|
||||
});
|
||||
|
||||
this.highlighters.selectorHighlighterShown = selector;
|
||||
// This event is emitted for testing purposes.
|
||||
this.emit("ruleview-selectorhighlighter-toggled", true);
|
||||
} else {
|
||||
this.highlighters.selectorHighlighterShown = null;
|
||||
this.store.dispatch(updateHighlightedSelector(""));
|
||||
// This event is emitted for testing purposes.
|
||||
this.emit("ruleview-selectorhighlighter-toggled", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rules view by dispatching the new rules data of the newly selected
|
||||
* element. This is called when the rules view becomes visible or upon new node
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
|
||||
const {
|
||||
UPDATE_RULES,
|
||||
UPDATE_HIGHLIGHTED_SELECTOR,
|
||||
} = require("../actions/index");
|
||||
|
||||
const INITIAL_RULES = {
|
||||
// The selector of the node that is highlighted by the selector highlighter.
|
||||
highlightedSelector: "",
|
||||
// Array of CSS rules.
|
||||
rules: [],
|
||||
};
|
||||
|
@ -81,9 +84,17 @@ function getRuleState(rule) {
|
|||
|
||||
const reducers = {
|
||||
|
||||
[UPDATE_RULES](_, { rules }) {
|
||||
[UPDATE_HIGHLIGHTED_SELECTOR](rules, { highlightedSelector }) {
|
||||
return {
|
||||
rules: rules.map(rule => getRuleState(rule)),
|
||||
...rules,
|
||||
highlightedSelector,
|
||||
};
|
||||
},
|
||||
|
||||
[UPDATE_RULES](rules, { rules: newRules }) {
|
||||
return {
|
||||
highlightedSelector: rules.highlightedSelector,
|
||||
rules: newRules.map(rule => getRuleState(rule)),
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ exports.pseudoClasses = {
|
|||
* A CSS selector.
|
||||
*/
|
||||
const selector = exports.selector = {
|
||||
// Function that returns a Promise containing an unique CSS selector.
|
||||
getUniqueSelector: PropTypes.func,
|
||||
// Array of the selectors that match the selected element.
|
||||
matchedSelectors: PropTypes.arrayOf(PropTypes.string),
|
||||
// The CSS rule's selector text content.
|
||||
|
|
Загрузка…
Ссылка в новой задаче