Skip to content

Commit

Permalink
feat(extension): add pre-compiled browser extension
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez committed May 18, 2023
1 parent 45d6a7b commit db48160
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 40 deletions.
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,8 @@ kayle_innate/package.json
kayle_innate/dll
kayle/screenshot.png

# testing
chrome-extension
# custom built
chrome-extension

# build custom extension
build-extension.js
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,21 @@ One of the main goals was to have the audit run quickly since we noticed some of
complete. The performance increases we made to the project were not only done at edge cases that would scale beyond
make the ability of auditing at the MS level for almost any website. Right now, the project is moving forward based on performance and accuracy for ensuring minimal to no false positives.

## Extension
## Browser Extension

We are packaging an extension that allows the script to be pre-loaded into the browser for the crawl.
If you want to compile a chrome extension for preloading scripts without needing to worry about bandwidth cost use the following to generate a custom extension to use.

First build the extension with the command:

1. `yarn build:extension`

Copy the contents into your directory to load using chromes `--load-extension` and enable the flag `--extensions-on-chrome-urls`.

View the [extension-test](kayle/tests/extension.ts) for an example on how to setup chrome with the generated extension.

Currently we only have english support for extension. We can add different locales for the generated scripts by manually adjusting the targets.

If you want to test the extension use `yarn test:puppeteer:extension`.

## Discord

Expand Down
1 change: 0 additions & 1 deletion kayle/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ _tests
./test-results
_data

tests/chrome-extension
./screenshot.png
86 changes: 86 additions & 0 deletions kayle/build-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// compiler the runners into a valid chrome extension
import { writeFileSync, mkdirSync, existsSync } from "fs";
import { runnersJavascript } from "./build/runner-js";
import { cwd } from "process";
import { join } from "path";

const ext = join(cwd(), "chrome-extension");

// if the chrome directory does not exist create
if (!existsSync(ext)) {
mkdirSync(ext);
}

const extensionRunner = runnersJavascript["kayle"];

// load basic extensions - TODO: allow creating extensions from languages
const extensionAxe = `function ar() {
${runnersJavascript["axe"]}
}`;

const extensionHtmlcs = `function hr() {
${runnersJavascript["htmlcs"]}
}`;

// the parts that allow extension to run and send
const extensionRawEnd = `
let axeLoaded = false;
let htmlcsLoaded = false;
// receiving audit request
window.addEventListener("kayle_send", async (event) => {
for (const option of event.detail.options.runners) {
if (option === "axe" && !axeLoaded) {
ar()
axeLoaded = true;
}
if (option === "htmlcs" && !htmlcsLoaded) {
hr()
htmlcsLoaded = true;
}
}
// send reqeust data of audit
window.dispatchEvent(new CustomEvent("kayle_receive", {
detail: {
name: 'kayle',
data: await window.__a11y.run(event.detail.options)
},
}))
});
`;

writeFileSync(
`${ext}/content-script.js`,
`${extensionRunner}\n${extensionAxe}\n${extensionHtmlcs}\n${extensionRawEnd}`
);

const extensionManifest = `{
"name": "Kayle",
"version": "1.0.0",
"description": "A web accessibility extension that can perform full audits and fast",
"manifest_version": 2,
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"permissions": [ "tabs" , "identity","http://localhost:9222/*"],
"browser_action": {
"default_title": "Kayle Accessibility",
"default_popup": "popup.html"
},
"externally_connectable": {
"matches": ["http://*/*", "https://*/*"]
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"run_at": "document_start",
"js": [
"content-script.js"
]
}
]
}`;

writeFileSync(`${ext}/manifest.json`, extensionManifest);
2 changes: 2 additions & 0 deletions kayle/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export type RunnerConfig = {
language?: string;
// prevent auto intercept request to prevent fetching resources.
noIntercept?: boolean;
// extension only run if accesibility extensions loaded: Experimental.
_browserExtension?: boolean;
};

// log singleton
Expand Down
58 changes: 49 additions & 9 deletions kayle/lib/kayle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,26 @@ async function runActionsList(config: RunnerConfig) {

// inject runners
async function injectRunners(config: RunnerConfig) {
// load axe first to avoid conflictions axe indexed as first item in array when multiple items exist
return await Promise.all([
config.page.evaluate(runnersJavascript["kayle"]),
config.page.evaluate(getRunner(config.language, config.runners[0])),
config.runners.length === 2
? config.page.evaluate(getRunner(config.language, config.runners[1]))
: undefined,
]);
if (!config._browserExtension) {
return await Promise.all([
config.page.evaluate(runnersJavascript["kayle"]),
config.page.evaluate(getRunner(config.language, config.runners[0])),
config.runners.length === 2
? config.page.evaluate(getRunner(config.language, config.runners[1]))
: undefined,
]);
}
}

// perform audit
async function audit(config: RunnerConfig): Promise<Audit> {
// perform audit as extension
if (config._browserExtension) {
return await auditExtension(config);
}

return await config.page.evaluate(
(runOptions) => {
// set top level app origin replicate
if (runOptions.origin && window.origin === "null") {
window.origin = runOptions.origin;
}
Expand All @@ -92,6 +97,41 @@ async function audit(config: RunnerConfig): Promise<Audit> {
);
}

// perform an audit using browser extension - only used if extension is configured on browser
export async function auditExtension(config: RunnerConfig): Promise<Audit> {
return await config.page.evaluate(
(runOptions): Promise<Audit> => {
return new Promise((resolve) => {
if (runOptions.origin && window.origin === "null") {
window.origin = runOptions.origin;
}

window.addEventListener("kayle_receive", (event: CustomEvent) =>
resolve(event.detail.data)
);

window.dispatchEvent(
new CustomEvent("kayle_send", {
detail: {
name: "kayle",
options: runOptions,
},
})
);
});
},
{
hideElements: config.hideElements,
ignore: config.ignore || [],
rootElement: config.rootElement,
rules: config.rules || [],
runners: config.runners,
standard: config.standard,
origin: config.origin,
language: config.language,
}
);
}
/**
* Run accessibility tests for page.
* @param {Object} [config={}] config - Options to change the way tests run.
Expand Down
12 changes: 5 additions & 7 deletions kayle/lib/option.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// handle configuration for methods
export function extractArgs(o) {
const options = {
actions: o.actions || [],
browser: o.browser,
page: o.page,
timeout: o.timeout || 60000,
// private
_browserExtension: o._browserExtension,
// sent to browser
actions: o.actions || [],
hideElements: o.hideElements,
ignore: o.ignore || [],
includeNotices: o.includeNotices,
Expand All @@ -12,7 +16,6 @@ export function extractArgs(o) {
rules: o.rules || [],
runners: o.runners || ["htmlcs"],
standard: o.standard || "WCAG2AA",
timeout: o.timeout || 60000,
origin: o.origin,
language: o.language || "en",
};
Expand All @@ -39,11 +42,6 @@ export function extractArgs(o) {
options.runners.push("htmlcs");
}

// swap axe position for conflictions on script eval order
if (options.runners.length === 2 && options.runners[1] === "axe") {
options.runners[1] = options.runners[0];
options.runners[0] = "axe";
}
// todo: validate all options
return options;
}
6 changes: 4 additions & 2 deletions kayle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kayle",
"version": "0.4.31",
"version": "0.4.32",
"description": "Extremely fast accessibility evaluation for nodejs",
"main": "./build/index.js",
"keywords": [
Expand All @@ -18,6 +18,8 @@
"prepare": "tsc",
"build": "tsc && yarn swc:dist",
"compile:test": "yarn build && tsc --project tsconfig.test.json",
"compile:extension": "tsc build-extension.ts",
"build:extension": "yarn compile:extension && node build-extension.js",
"lint": "eslint .",
"fix": "prettier --write '**/*.{js,jsx,ts,tsx}'",
"swc:dist": "npx swc --copy-files --config-file .swcrc ./build -d ./build",
Expand All @@ -34,7 +36,7 @@
"test:playwright:axe": "npm run compile:test && npx playwright test ./tests/basic-axe-playwright.spec.ts",
"test:puppeteer:wasm": "npm run compile:test && node _tests/tests/wasm.js",
"test:puppeteer:automa": "npm run compile:test && node _tests/tests/automa.js",
"test:puppeteer:extension": "npm run compile:test && node _tests/tests/extension.js",
"test:puppeteer:extension": "npm run compile:test && yarn build:extension && node _tests/tests/extension.js",
"test:lint": "node build/lint.js",
"test:unit:unique-selector": "npm run compile:test && node _tests/tests/unit/unique-selector.js",
"publish": "yarn prepare && yarn npm publish"
Expand Down
2 changes: 1 addition & 1 deletion kayle/tests/basic-axe-playwright.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { performance } from "perf_hooks";
import { test } from "@playwright/test";

test("fast_axecore audit drakeMock", async ({ page, browser }, testInfo) => {
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));
// page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));
const startTime = performance.now();
const results = await kayle({
page,
Expand Down
18 changes: 2 additions & 16 deletions kayle/tests/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import { performance } from "perf_hooks";
{
page,
browser,
runners: ["htmlcs"],
runners: ["htmlcs", "axe"],
includeWarnings: true,
html: jmendezMock,
origin: "https://jeffmendez.com", // origin is the fake url in place of the raw content
_browserExtension: false, // enable the extension
},
true
);
Expand All @@ -46,20 +47,5 @@ import { performance } from "perf_hooks";
// valid list
assert(Array.isArray(issues));

// chrome extension adds script to execute
const data = await page.evaluate(
(runOptions) => {
// @ts-ignore injected after navigate
return window.__kayle.random(runOptions);
},
{
origin: "https://jeffmendez.com",
}
);

console.log(data);

await page.screenshot({ path: "./screenshot.png" });

await browser.close();
})();

0 comments on commit db48160

Please sign in to comment.