Skip to content

Commit

Permalink
update and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeyyy committed May 13, 2019
1 parent 5ca679f commit e9f5f0e
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 138 deletions.
51 changes: 51 additions & 0 deletions lib/core/utils/get-stylesheet-factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Function which converts given text to `CSSStyleSheet`
* - used in `CSSOM` computation.
* - factory (closure) function, initialized with `document.implementation.createHTMLDocument()`, which uses DOM API for creating `style` elements.
*
* @method axe.utils.getStyleSheetFactory
* @memberof axe.utils
* @param {Object} dynamicDoc `document.implementation.createHTMLDocument()
* @param {Object} options an object with properties to construct stylesheet
* @property {String} options.data text content of the stylesheet
* @property {Boolean} options.isCrossOrigin flag to notify if the resource was fetched from the network
* @property {String} options.shadowId (Optional) shadowId if shadowDOM
* @property {Object} options.root implementation document to create style elements
* @property {String} options.priority a number indicating the loaded priority of CSS, to denote specificity of styles contained in the sheet.
* @returns {Function}
*/
axe.utils.getStyleSheetFactory = function getStyleSheetFactory(dynamicDoc) {
if (!dynamicDoc) {
throw new Error(
'axe.utils.getStyleSheetFactory should be invoked with an argument'
);
}

return options => {
const {
data,
isCrossOrigin = false,
shadowId,
root,
priority,
isLink = false
} = options;
const style = dynamicDoc.createElement('style');
if (isLink) {
// as creating a stylesheet as link will need to be awaited
// till `onload`, it is wise to convert link href to @import statement
const text = dynamicDoc.createTextNode(`@import "${data.href}"`);
style.appendChild(text);
} else {
style.appendChild(dynamicDoc.createTextNode(data));
}
dynamicDoc.head.appendChild(style);
return {
sheet: style.sheet,
isCrossOrigin,
shadowId,
root,
priority
};
};
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/**
* Parse cross-origin stylesheets
*
* @method parseCrossOriginStylesheet
* @memberof axe.utils
* @param {String} url url from which to fetch stylesheet
* @param {Object} options options object from `axe.utils.parseStylesheets`
* @param {Object} options options object from `axe.utils.parseStylesheet`
* @param {Array<Number>} priority sheet priority
* @param {Array<String>} importedUrls urls of already imported stylesheets
* @param {Boolean} isCrossOrigin boolean denoting if a stylesheet is `cross-origin`
* @returns {Promise}
*/
axe.utils.parseCrossOriginStylesheets = function parseCrossOriginStylesheets(
axe.utils.parseCrossOriginStylesheet = function parseCrossOriginStylesheet(
url,
options,
priority,
Expand All @@ -20,7 +22,14 @@ axe.utils.parseCrossOriginStylesheets = function parseCrossOriginStylesheets(
url
};

/**
* Add `url` to `importedUrls`
*/
importedUrls.push(url);

/**
* Fetch `cross-origin stylesheet` via axios
*/
return axe.imports.axios(axiosOptions).then(({ data }) => {
const result = options.convertDataToStylesheet({
data,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/**
* Parse non cross-origin stylesheets
*
* @method parseSameOriginStylesheet
* @memberof axe.utils
* @param {Object} sheet CSSStylesheet object
* @param {Object} options options object from `axe.utils.parseStylesheets`
* @param {Object} options options object from `axe.utils.parseStylesheet`
* @param {Array<Number>} priority sheet priority
* @param {Array<String>} importedUrls urls of already imported stylesheets
* @param {Boolean} isCrossOrigin boolean denoting if a stylesheet is `cross-origin`
* @returns {Promise}
*/
axe.utils.parseSameOriginStylesheets = function parseSameOriginStylesheets(
axe.utils.parseSameOriginStylesheet = function parseSameOriginStylesheet(
sheet,
options,
priority,
Expand Down Expand Up @@ -59,7 +61,7 @@ axe.utils.parseSameOriginStylesheets = function parseSameOriginStylesheets(
const newPriority = [...priority, cssRuleIndex];
const isCrossOriginRequest = /^https?:\/\/|^\/\//i.test(importUrl);

return axe.utils.parseCrossOriginStylesheets(
return axe.utils.parseCrossOriginStylesheet(
importUrl,
options,
newPriority,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,8 @@
/**
* Returns all CSS stylesheets for a given root node
*
* @param {Object} options configuration options
* @property {Object} options.rootNode document or document fragment
* @property {Number} options.rootIndex a number representing the index of the document or document fragment, used for priority computation
* @property {String} options.shadowId an id if undefined denotes that given root is a document fragment/ shadowDOM
* @property {Number} options.timeout abort duration for network request
* @property {Function} options.convertDataToStylesheet a utility function to generate a style sheet from given data (text)
* @returns {Promise}
*/
axe.utils.parseStylesheets = function parseStylesheets(
sheets,
options,
importedUrls = []
) {
/**
* Note:
* `importedUrls` - keeps urls of already imported stylesheets, to prevent re-fetching
* eg: nested, cyclic or cross referenced `@import` urls
*/
const { rootIndex } = options;
return Promise.all(
sheets.map((sheet, sheetIndex) => {
const priority = [rootIndex, sheetIndex];
return axe.utils.parseStylesheet(sheet, options, priority, importedUrls);
})
);
};

/**
* Parses a given stylesheet
* Parse a given stylesheet
*
* @method parseStylesheet
* @memberof axe.utils
* @param {Object} sheet stylesheet to parse
* @param {Object} options configuration options object from `axe.utils.parseStylesheets`
* @param {Array<Number>} priority priority of stylesheet
Expand All @@ -50,7 +22,7 @@ axe.utils.parseStylesheet = function parseStylesheet(
/**
* resolve `same-origin` stylesheet
*/
return axe.utils.parseSameOriginStylesheets(
return axe.utils.parseSameOriginStylesheet(
sheet,
options,
priority,
Expand All @@ -62,7 +34,7 @@ axe.utils.parseStylesheet = function parseStylesheet(
/**
* resolve `cross-origin` stylesheet
*/
return axe.utils.parseCrossOriginStylesheets(
return axe.utils.parseCrossOriginStylesheet(
sheet.href,
options,
priority,
Expand Down
66 changes: 23 additions & 43 deletions lib/core/utils/preload-cssom.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ axe.utils.preloadCssom = function preloadCssom({ treeRoot = axe._tree[0] }) {
'Dynamic document for loading cssom'
);

const convertDataToStylesheet = getStyleSheetFactory(dynamicDoc);
const convertDataToStylesheet = axe.utils.getStyleSheetFactory(dynamicDoc);

return getCssomForAllRootNodes(rootNodes, convertDataToStylesheet).then(
assets => flattenAssets(assets)
Expand Down Expand Up @@ -65,46 +65,6 @@ function getAllRootNodesInTree(tree) {
return axe.utils.uniqueArray(rootNodes, []);
}

/**
* Convert text to CSSStyleSheet
* Is a factory (closure) function, initialized with `document.implementation.createHTMLDocument()` which surfaces DOM API for creating `style` elements.
*
* @param {Object} param `document.implementation.createHTMLDocument()
* @param {Object} arg an object with properties to construct stylesheet
* @property {String} arg.data text content of the stylesheet
* @property {Boolean} arg.isCrossOrigin flag to notify if the resource was fetched from the network
* @property {String} arg.shadowId (Optional) shadowId if shadowDOM
* @property {Object} arg.root implementation document to create style elements
* @property {String} arg.priority a number indicating the loaded priority of CSS, to denote specificity of styles contained in the sheet.
* @returns {Function}
*/
const getStyleSheetFactory = dynamicDoc => ({
data,
isCrossOrigin,
shadowId,
root,
priority,
isLink = false
}) => {
const style = dynamicDoc.createElement('style');
if (isLink) {
// as creating a stylesheet as link will need to be awaited
// till `onload`, it is wise to convert link href to @import statement
const text = dynamicDoc.createTextNode(`@import "${data.href}"`);
style.appendChild(text);
} else {
style.appendChild(dynamicDoc.createTextNode(data));
}
dynamicDoc.head.appendChild(style);
return {
sheet: style.sheet,
isCrossOrigin,
shadowId,
root,
priority
};
};

/**
* Process CSSOM on all root nodes
*
Expand All @@ -125,13 +85,33 @@ function getCssomForAllRootNodes(rootNodes, convertDataToStylesheet) {
return Promise.all(promises);
}

const rootIndex = index + 1;
const parseOptions = {
rootNode,
shadowId,
convertDataToStylesheet,
rootIndex: index + 1
rootIndex
};
const p = axe.utils.parseStylesheets(sheets, parseOptions);
/**
* Note:
* `importedUrls` - keeps urls of already imported stylesheets, to prevent re-fetching
* eg: nested, cyclic or cross referenced `@import` urls
*/
const importedUrls = [];

const p = Promise.all(
sheets.map((sheet, sheetIndex) => {
const priority = [rootIndex, sheetIndex];

return axe.utils.parseStylesheet(
sheet,
parseOptions,
priority,
importedUrls
);
})
);

promises.push(p);
});

Expand Down
42 changes: 42 additions & 0 deletions test/core/utils/get-stylesheet-factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
describe('axe.utils.getStyleSheetFactory', function() {
'use strict';

var dynamicDoc = document.implementation.createHTMLDocument(
'Dynamic document for testing axe.utils.getStyleSheetFactory'
);

it('throws if there is no argument of dynamicDocument', function() {
assert.throws(function() {
axe.utils.getStyleSheetFactory();
});
});

it('returns a function when passed argument of dynamicDocument', function() {
const actual = axe.utils.getStyleSheetFactory(dynamicDoc);
assert.isFunction(actual);
});

it('returns a CSSOM stylesheet, when invoked with data (text)', function() {
const stylesheetFactory = axe.utils.getStyleSheetFactory(dynamicDoc);
const actual = stylesheetFactory({
data: `.someStyle{background-color:red;}`,
root: document,
priority: [1, 0]
});

assert.isDefined(actual);
assert.hasAllKeys(actual, [
'sheet',
'isCrossOrigin',
'shadowId',
'root',
'priority'
]);
assert.deepEqual(actual.priority, [1, 0]);
axe.testUtils.assertStylesheet(
actual.sheet,
'.someStyle',
'.someStyle{background-color:red;}'
);
});
});
Loading

0 comments on commit e9f5f0e

Please sign in to comment.