Bug 1615509: Address rule view change event. r=rcaliman

Differential Revision: https://phabricator.services.mozilla.com/D64310

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Daisuke Akatsuka 2020-03-12 14:32:35 +00:00
parent cc074c7456
commit e13707578e
5 changed files with 190 additions and 41 deletions

View File

@ -17,6 +17,7 @@ loader.lazyRequireGetter(
);
const {
updateNodes,
updateSelectedNode,
updateTargetBrowsers,
updateTopLevelTarget,
@ -28,6 +29,7 @@ const CompatibilityApp = createFactory(
class CompatibilityView {
constructor(inspector, window) {
this._onChangeAdded = this._onChangeAdded.bind(this);
this._onPanelSelected = this._onPanelSelected.bind(this);
this._onSelectedNodeChanged = this._onSelectedNodeChanged.bind(this);
this._onTopLevelTargetChanged = this._onTopLevelTargetChanged.bind(this);
@ -44,8 +46,11 @@ class CompatibilityView {
this._onPanelSelected
);
if (this._ruleView) {
this._ruleView.off("ruleview-changed", this._onNewNode);
const changesFront = this.inspector.toolbox.target.getCachedFront(
"changes"
);
if (changesFront) {
changesFront.off("add-change", this._onChangeAdded);
}
this.inspector = null;
@ -82,18 +87,6 @@ class CompatibilityView {
this._onPanelSelected
);
if (this._ruleView) {
this._ruleView.on("ruleview-changed", this._onSelectedNodeChanged);
} else {
this.inspector.on(
"ruleview-added",
() => {
this._ruleView.on("ruleview-changed", this._onSelectedNodeChanged);
},
{ once: true }
);
}
this._initTargetBrowsers();
}
@ -113,6 +106,26 @@ class CompatibilityView {
);
}
_onChangeAdded({ selector }) {
if (!this._isAvailable()) {
return;
}
// We need to debounce updating nodes since "add-change" event on changes actor is
// fired for every typed character until fixing bug 1503036.
if (this._previousChangedSelector === selector) {
clearTimeout(this._updateNodesTimeoutId);
}
this._previousChangedSelector = selector;
this._updateNodesTimeoutId = setTimeout(() => {
// TODO: In case of keyframes changes, the selector given from changes actor is
// keyframe-selector such as "from" and "100%", not selector for node. Thus,
// we need to address this case.
this.inspector.store.dispatch(updateNodes(selector));
}, 500);
}
_onPanelSelected() {
this._onSelectedNodeChanged();
this._onTopLevelTargetChanged();
@ -128,7 +141,7 @@ class CompatibilityView {
);
}
_onTopLevelTargetChanged() {
async _onTopLevelTargetChanged() {
if (!this._isAvailable()) {
return;
}
@ -136,13 +149,20 @@ class CompatibilityView {
this.inspector.store.dispatch(
updateTopLevelTarget(this.inspector.toolbox.target)
);
}
get _ruleView() {
return (
this.inspector.hasPanel("ruleview") &&
this.inspector.getPanel("ruleview").view
const changesFront = await this.inspector.toolbox.target.getFront(
"changes"
);
try {
// Call allChanges() in order to get the add-change qevent.
await changesFront.allChanges();
} catch (e) {
// The connection to the server may have been cut, for example during test teardown.
// Here we just catch the error and silently ignore it.
}
changesFront.on("add-change", this._onChangeAdded);
}
}

View File

@ -14,6 +14,11 @@ loader.lazyGetter(this, "mdnCompatibility", () => {
const {
COMPATIBILITY_APPEND_NODE,
COMPATIBILITY_UPDATE_NODE,
COMPATIBILITY_UPDATE_NODES_START,
COMPATIBILITY_UPDATE_NODES_SUCCESS,
COMPATIBILITY_UPDATE_NODES_FAILURE,
COMPATIBILITY_UPDATE_NODES_COMPLETE,
COMPATIBILITY_UPDATE_SELECTED_NODE_START,
COMPATIBILITY_UPDATE_SELECTED_NODE_SUCCESS,
COMPATIBILITY_UPDATE_SELECTED_NODE_FAILURE,
@ -29,6 +34,43 @@ const {
COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE,
} = require("devtools/client/inspector/compatibility/actions/index");
function updateNodes(selector) {
return async ({ dispatch, getState }) => {
dispatch({ type: COMPATIBILITY_UPDATE_NODES_START });
try {
const {
selectedNode,
topLevelTarget,
targetBrowsers,
} = getState().compatibility;
const { walker } = await topLevelTarget.getFront("inspector");
const nodeList = await walker.querySelectorAll(walker.rootNode, selector);
for (const node of await nodeList.items()) {
if (selectedNode.actorID === node.actorID) {
await _updateSelectedNodeIssues(node, targetBrowsers, dispatch);
}
const issues = await _getNodeIssues(node, targetBrowsers);
dispatch({
type: COMPATIBILITY_UPDATE_NODE,
node,
issues,
});
}
dispatch({ type: COMPATIBILITY_UPDATE_NODES_SUCCESS });
} catch (error) {
dispatch({
type: COMPATIBILITY_UPDATE_NODES_FAILURE,
error,
});
}
dispatch({ type: COMPATIBILITY_UPDATE_NODES_COMPLETE });
};
}
function updateSelectedNode(node) {
return async ({ dispatch, getState }) => {
dispatch({ type: COMPATIBILITY_UPDATE_SELECTED_NODE_START });
@ -165,6 +207,7 @@ async function _updateTopLevelTargetIssues(target, targetBrowsers, dispatch) {
}
module.exports = {
updateNodes,
updateSelectedNode,
updateTargetBrowsers,
updateTopLevelTarget,

View File

@ -11,6 +11,15 @@ createEnum(
// Append node that caused issues.
"COMPATIBILITY_APPEND_NODE",
// Updates a node.
"COMPATIBILITY_UPDATE_NODE",
// Updates nodes.
"COMPATIBILITY_UPDATE_NODES_START",
"COMPATIBILITY_UPDATE_NODES_SUCCESS",
"COMPATIBILITY_UPDATE_NODES_FAILURE",
"COMPATIBILITY_UPDATE_NODES_COMPLETE",
// Updates the selected node.
"COMPATIBILITY_UPDATE_SELECTED_NODE_START",
"COMPATIBILITY_UPDATE_SELECTED_NODE_SUCCESS",

View File

@ -6,6 +6,8 @@
const {
COMPATIBILITY_APPEND_NODE,
COMPATIBILITY_UPDATE_NODE,
COMPATIBILITY_UPDATE_NODES_FAILURE,
COMPATIBILITY_UPDATE_SELECTED_NODE_SUCCESS,
COMPATIBILITY_UPDATE_SELECTED_NODE_FAILURE,
COMPATIBILITY_UPDATE_SELECTED_NODE_ISSUES,
@ -26,22 +28,25 @@ const INITIAL_STATE = {
const reducers = {
[COMPATIBILITY_APPEND_NODE](state, { node, issues }) {
const topLevelTargetIssues = [...state.topLevelTargetIssues];
for (const issue of issues) {
const index = topLevelTargetIssues.findIndex(
i => i.type === issue.type && i.property === issue.property
);
if (index < 0) {
issue.nodes = [node];
topLevelTargetIssues.push(issue);
} else {
const topLevelTargetIssue = topLevelTargetIssues[index];
topLevelTargetIssue.nodes = [...topLevelTargetIssue.nodes, node];
}
}
const topLevelTargetIssues = _appendTopLevelTargetIssues(
state.topLevelTargetIssues,
node,
issues
);
return Object.assign({}, state, { topLevelTargetIssues });
},
[COMPATIBILITY_UPDATE_NODE](state, { node, issues }) {
const topLevelTargetIssues = _updateTopLebelTargetIssues(
state.topLevelTargetIssues,
node,
issues
);
return Object.assign({}, state, { topLevelTargetIssues });
},
[COMPATIBILITY_UPDATE_NODES_FAILURE](state, { error }) {
_showError(COMPATIBILITY_UPDATE_NODES_FAILURE, error);
return state;
},
[COMPATIBILITY_UPDATE_SELECTED_NODE_SUCCESS](state, { node }) {
return Object.assign({}, state, { selectedNode: node });
},
@ -71,6 +76,70 @@ const reducers = {
},
};
function _appendTopLevelTargetIssues(targetIssues, node, issues) {
targetIssues = [...targetIssues];
for (const issue of issues) {
const index = _indexOfIssue(targetIssues, issue);
if (index < 0) {
issue.nodes = [node];
targetIssues.push(issue);
continue;
}
const targetIssue = targetIssues[index];
targetIssue.nodes = [...targetIssue.nodes, node];
}
return targetIssues;
}
function _indexOfIssue(issues, issue) {
return issues.findIndex(
i => i.type === issue.type && i.property === issue.property
);
}
function _indexOfNode(issue, node) {
return issue.nodes.findIndex(n => n.actorID === node.actorID);
}
function _updateTopLebelTargetIssues(targetIssues, node, issues) {
// Remove issues or node.
targetIssues = targetIssues.reduce((newIssues, targetIssue) => {
if (_indexOfIssue(issues, targetIssue) >= 0) {
// The targetIssue is still in the node.
return [...newIssues, targetIssue];
}
const indexOfNodeInTarget = _indexOfNode(targetIssue, node);
if (indexOfNodeInTarget < 0) {
// The targetIssue does not have the node to remove.
return [...newIssues, targetIssue];
}
// This issue on the updated node is gone.
if (targetIssue.nodes.length === 1) {
// Remove issue.
return newIssues;
}
// Remove node from the nodes.
targetIssue.nodes = [
...targetIssue.nodes.splice(0, indexOfNodeInTarget),
...targetIssue.nodes.splice(indexOfNodeInTarget + 1),
];
return [...newIssues, targetIssue];
}, []);
// Append issues or node.
const appendables = issues.filter(issue => {
const indexOfIssue = _indexOfIssue(targetIssues, issue);
return (
indexOfIssue < 0 || _indexOfNode(targetIssues[indexOfIssue], node) < 0
);
});
return _appendTopLevelTargetIssues(targetIssues, node, appendables);
}
function _showError(action, error) {
console.error(`[${action}] ${error.message}`);
console.error(error.stack);

View File

@ -8,6 +8,10 @@
const TEST_URI = `<div style="border-block-color: lime;"></div>`;
const {
COMPATIBILITY_UPDATE_NODES_COMPLETE,
} = require("devtools/client/inspector/compatibility/actions/index");
add_task(async function() {
info("Enable 3 pane mode");
await pushPref("devtools.inspector.three-pane-enabled", true);
@ -23,20 +27,24 @@ add_task(async function() {
]);
info("Check the issue after toggling the property");
const view = inspector.getPanel("ruleview").view;
const rule = getRuleViewRuleEditor(view, 0).rule;
await _togglePropStatus(view, rule.textProps[0]);
await _togglePropRule(inspector, 0, 0);
await assertIssueList(selectedElementPane, []);
info("Check the issue after toggling the property again");
await _togglePropStatus(view, rule.textProps[0]);
await _togglePropRule(inspector, 0, 0);
await assertIssueList(selectedElementPane, [
{ property: "border-block-color" },
]);
});
async function _togglePropStatus(view, textProp) {
const onRuleViewRefreshed = view.once("ruleview-changed");
async function _togglePropRule(inspector, ruleIndex, propIndex) {
const ruleView = inspector.getPanel("ruleview").view;
const onNodesUpdated = waitForDispatch(
inspector.store,
COMPATIBILITY_UPDATE_NODES_COMPLETE
);
const rule = getRuleViewRuleEditor(ruleView, ruleIndex).rule;
const textProp = rule.textProps[propIndex];
textProp.editor.enable.click();
await onRuleViewRefreshed;
await onNodesUpdated;
}