Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rule,check): add new apis to run a rule synchronously #1467

Merged
merged 7 commits into from
May 10, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions lib/core/base/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,54 @@ Check.prototype.run = function(node, options, context, resolve, reject) {
}
};

/**
* Run the check's evaluate function (call `this.evaluate(node, options)`) synchronously
* @param {HTMLElement} node The node to test
* @param {Object} options The options that override the defaults and provide additional
* information for the check
*/
Check.prototype.runSync = function(node, options, context) {
options = options || {};
const { enabled = this.enabled } = options;

if (!enabled) {
return null;
}

const checkOptions = options.options || this.options;
const checkResult = new CheckResult(this);
const checkHelper = axe.utils.checkHelper(checkResult, options);

// throw error if a check is run that requires async behavior
checkHelper.async = function() {
throw new Error('Cannot run async check while in a synchronous run');
};

let result;

try {
result = this.evaluate.call(
checkHelper,
node.actualNode,
checkOptions,
node,
context
);
} catch (e) {
// In the "Audit#run: should run all the rules" test, there is no `node` here. I do
// not know if this is intentional or not, so to be safe, we guard against the
// possible reference error.
if (node && node.actualNode) {
// Save a reference to the node we errored on for futher debugging.
e.errorNode = new DqElement(node.actualNode).toJSON();
}
throw e;
}

checkResult.result = result;
return checkResult;
};

/**
* Override a check's settings after construction to allow for changing options
* without having to implement the entire check
Expand Down
191 changes: 152 additions & 39 deletions lib/core/base/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,41 @@ Rule.prototype.runChecks = function(
.catch(reject);
};

/**
* Run a check for a rule synchronously.
*/
Rule.prototype.runChecksSync = function(type, node, options, context) {
'use strict';

const self = this;
let results = [];

this[type].forEach(function(c) {
const check = self._audit.checks[c.id || c];
const option = axe.utils.getCheckOption(check, self.id, options);
results.push(check.runSync(node, option, context));
});

results = results.filter(function(check) {
straker marked this conversation as resolved.
Show resolved Hide resolved
return check;
});

return { type: type, results: results };
};

/**
* Runs the Rule's `evaluate` function
* @param {Context} context The resolved Context object
* @param {Mixed} options Options specific to this rule
* @param {Function} callback Function to call when evaluate is complete; receives a RuleResult instance
*/
Rule.prototype.run = function(context, options, resolve, reject) {
Rule.prototype.run = function(context, options = {}, resolve, reject) {
if (options.performanceTimer) {
this._trackPerformance();
}

const q = axe.utils.queue();
const ruleResult = new RuleResult(this);
const markStart = 'mark_rule_start_' + this.id;
const markEnd = 'mark_rule_end_' + this.id;
const markChecksStart = 'mark_runchecks_start_' + this.id;
const markChecksEnd = 'mark_runchecks_end_' + this.id;

let nodes;

try {
Expand All @@ -192,14 +213,7 @@ Rule.prototype.run = function(context, options, resolve, reject) {
}

if (options.performanceTimer) {
axe.log(
this.id,
'gather (',
nodes.length,
'):',
axe.utils.performanceTimer.timeElapsed() + 'ms'
);
axe.utils.performanceTimer.mark(markChecksStart);
logGatherPerformance(this, nodes);
}

nodes.forEach(node => {
Expand All @@ -214,22 +228,10 @@ Rule.prototype.run = function(context, options, resolve, reject) {

checkQueue
.then(function(results) {
if (results.length) {
var hasResults = false,
result = {};
results.forEach(function(r) {
var res = r.results.filter(function(result) {
return result;
});
result[r.type] = res;
if (res.length) {
hasResults = true;
}
});
if (hasResults) {
result.node = new axe.utils.DqElement(node.actualNode, options);
ruleResult.nodes.push(result);
}
const result = getResult(results);
if (result) {
result.node = new axe.utils.DqElement(node.actualNode, options);
ruleResult.nodes.push(result);
}
resolveNode();
})
Expand All @@ -242,20 +244,131 @@ Rule.prototype.run = function(context, options, resolve, reject) {
q.defer(resolve => setTimeout(resolve, 0));

if (options.performanceTimer) {
axe.utils.performanceTimer.mark(markChecksEnd);
axe.utils.performanceTimer.mark(markEnd);
axe.utils.performanceTimer.measure(
'rule_' + this.id + '#runchecks',
markChecksStart,
markChecksEnd
);

axe.utils.performanceTimer.measure('rule_' + this.id, markStart, markEnd);
logRulePerformance(this);
}

q.then(() => resolve(ruleResult)).catch(error => reject(error));
};

/**
* Runs the Rule's `evaluate` function synchronously
* @param {Context} context The resolved Context object
* @param {Mixed} options Options specific to this rule
*/
Rule.prototype.runSync = function(context, options = {}) {
if (options.performanceTimer) {
this._trackPerformance();
}

const ruleResult = new RuleResult(this);
let nodes;

try {
nodes = this.gatherAndMatchNodes(context, options);
} catch (error) {
// Exit the rule execution if matches fails
throw new SupportError({ cause: error, ruleId: this.id });
}

if (options.performanceTimer) {
logGatherPerformance(this, nodes);
}

nodes.forEach(node => {
let results = [];
['any', 'all', 'none'].forEach(type => {
results.push(this.runChecksSync(type, node, options, context));
});

const result = getResult(results);
if (result) {
result.node = new axe.utils.DqElement(node.actualNode, options);
ruleResult.nodes.push(result);
}
});

if (options.performanceTimer) {
logRulePerformance(this);
}

return ruleResult;
};

/**
* Add performance tracking properties to the rule
* @private
*/
Rule.prototype._trackPerformance = function() {
straker marked this conversation as resolved.
Show resolved Hide resolved
this._markStart = 'mark_rule_start_' + this.id;
this._markEnd = 'mark_rule_end_' + this.id;
this._markChecksStart = 'mark_runchecks_start_' + this.id;
this._markChecksEnd = 'mark_runchecks_end_' + this.id;
};

/**
* Log performance of rule.gather
* @param {Rule} rule The rule to log
* @param {Array} nodes Result of rule.gather
*/
function logGatherPerformance(rule, nodes) {
straker marked this conversation as resolved.
Show resolved Hide resolved
axe.log(
'gather (',
nodes.length,
'):',
axe.utils.performanceTimer.timeElapsed() + 'ms'
);
axe.utils.performanceTimer.mark(rule._markChecksStart);
}

/**
* Log performance of the rule
* @param {Rule} rule The rule to log
*/
function logRulePerformance(rule) {
axe.utils.performanceTimer.mark(rule._markChecksEnd);
axe.utils.performanceTimer.mark(rule._markEnd);
axe.utils.performanceTimer.measure(
'runchecks_' + rule.id,
rule._markChecksStart,
rule._markChecksEnd
);

axe.utils.performanceTimer.measure(
'rule_' + rule.id,
rule._markStart,
rule._markEnd
);
}

/**
* Process the results of each check and return the result if a check
* has a result
* @private
* @param {Array} results Array of each check result
* @returns {Object|null}
*/
function getResult(results) {
if (results.length) {
let hasResults = false,
result = {};
results.forEach(function(r) {
const res = r.results.filter(function(result) {
return result;
});
result[r.type] = res;
if (res.length) {
hasResults = true;
}
});

if (hasResults) {
return result;
}

return null;
}
}

/**
* Selects `HTMLElement`s based on configured selector and filters them based on
* the rules matches function
Expand Down
Loading