Skip to content

Commit f364662

Browse files
committed
Merge branch 'william/try/make-scan-runnable-from-frontend-loaded-page' into william/integration/merge-recent-frontend-highlighter-and-scanner-change-branches
2 parents f2e9565 + 9832bae commit f364662

3 files changed

Lines changed: 202 additions & 37 deletions

File tree

includes/classes/class-enqueue-frontend.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,17 @@ public static function maybe_enqueue_frontend_highlighter() {
9797
'edac-frontend-highlighter-app',
9898
'edacFrontendHighlighterApp',
9999
[
100-
'postID' => $post_id,
101-
'nonce' => wp_create_nonce( 'ajax-nonce' ),
102-
'restNonce' => wp_create_nonce( 'wp_rest' ),
103-
'userCanFix' => current_user_can( apply_filters( 'edac_filter_settings_capability', 'manage_options' ) ),
104-
'edacUrl' => esc_url_raw( get_site_url() ),
105-
'ajaxurl' => admin_url( 'admin-ajax.php' ),
106-
'loggedIn' => is_user_logged_in(),
107-
'appCssUrl' => EDAC_PLUGIN_URL . 'build/css/frontendHighlighterApp.css?ver=' . EDAC_VERSION,
108-
'widgetPosition' => get_option( 'edac_frontend_highlighter_position', 'right' ),
109-
'editorLink' => get_edit_post_link( $post_id ),
100+
'postID' => $post_id,
101+
'nonce' => wp_create_nonce( 'ajax-nonce' ),
102+
'restNonce' => wp_create_nonce( 'wp_rest' ),
103+
'userCanFix' => current_user_can( apply_filters( 'edac_filter_settings_capability', 'manage_options' ) ),
104+
'edacUrl' => esc_url_raw( get_site_url() ),
105+
'ajaxurl' => admin_url( 'admin-ajax.php' ),
106+
'loggedIn' => is_user_logged_in(),
107+
'appCssUrl' => EDAC_PLUGIN_URL . 'build/css/frontendHighlighterApp.css?ver=' . EDAC_VERSION,
108+
'widgetPosition' => get_option( 'edac_frontend_highlighter_position', 'right' ),
109+
'editorLink' => get_edit_post_link( $post_id ),
110+
'scannerBundleUrl' => plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/pageScanner.bundle.js',
110111
]
111112
);
112113

src/frontendHighlighterApp/index.js

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class AccessibilityCheckerHighlight {
1919
};
2020

2121
this.settings = { ...defaultSettings, ...settings };
22+
this._scanAttempted = false;
2223

2324
this.highlightPanel = this.addHighlightPanel();
2425
this.nextButton = document.querySelector( '#edac-highlight-next' );
@@ -188,15 +189,21 @@ class AccessibilityCheckerHighlight {
188189
},
189190
);
190191
}
192+
} else if ( ! self._scanAttempted && response.data?.[ 0 ]?.code === -3 ) {
193+
// Only try kickoffScan once per highlightAjax call
194+
self._scanAttempted = true;
195+
self.kickoffScan();
196+
// After kickoffScan, try highlightAjax again, but only once
197+
setTimeout( () => {
198+
self.highlightAjax().then( resolve ).catch( reject );
199+
}, 5000 ); // Wait 5s for scan to complete.
191200
} else {
192-
resolve( [] );
193-
//console.log(response);
201+
// Default: resolve with empty issues/fixes
202+
resolve( { issues: [], fixes: [] } );
194203
}
195204
} else {
196205
self.showWait( false );
197206

198-
//console.log( 'Request failed. Returned status of ' + xhr.status );
199-
200207
reject( {
201208
status: xhr.status,
202209
statusText: xhr.statusText,
@@ -541,7 +548,11 @@ class AccessibilityCheckerHighlight {
541548
}
542549
}
543550
).catch( ( err ) => {
544-
//TODO:
551+
// Output a message that says that there are no issues or that the issues could not be loaded.
552+
const summary = document.querySelector( '.edac-highlight-panel-controls-summary' );
553+
if ( summary ) {
554+
summary.textContent = __( 'An error occurred when loading the issues.', 'accessibility-checker' );
555+
}
545556
} );
546557
}
547558

@@ -1088,6 +1099,115 @@ class AccessibilityCheckerHighlight {
10881099
element.removeAttribute( 'data-edac-landmark-label-id' );
10891100
} );
10901101
}
1102+
1103+
/**
1104+
* Kick off the accessibility scan.
1105+
*/
1106+
kickoffScan() {
1107+
const getPageDensity = () => {
1108+
const elementCount = document.body.getElementsByTagName( '*' ).length;
1109+
const contentLength = document.body.innerText.length;
1110+
return { elementCount, contentLength };
1111+
};
1112+
const densityMetrics = getPageDensity();
1113+
const self = this;
1114+
const scriptId = 'edac-accessibility-checker-scanner-script';
1115+
if ( ! document.getElementById( scriptId ) ) {
1116+
const script = document.createElement( 'script' );
1117+
script.src = window.edacFrontendHighlighterApp?.scannerBundleUrl || '/wp-content/plugins/accessibility-checker/build/pageScanner.bundle.js';
1118+
script.id = scriptId;
1119+
script.onload = function() {
1120+
setTimeout( () => {
1121+
self._runScanOrShowError( densityMetrics );
1122+
}, 100 );
1123+
};
1124+
script.onerror = function() {
1125+
self.showWait( false );
1126+
self.showScanError( 'Failed to load scanner script.' );
1127+
};
1128+
document.head.appendChild( script );
1129+
} else {
1130+
self._runScanOrShowError( densityMetrics );
1131+
}
1132+
}
1133+
1134+
_runScanOrShowError( densityMetrics ) {
1135+
if ( window.runAccessibilityScan ) {
1136+
this.runAccessibilityScanAndSave( densityMetrics );
1137+
} else {
1138+
this.showWait( false );
1139+
this.showScanError( __( 'Scanner function not found.', 'accessibility-checker' ) );
1140+
}
1141+
}
1142+
1143+
runAccessibilityScanAndSave( densityMetrics ) {
1144+
const self = this;
1145+
const summary = document.querySelector( '.edac-highlight-panel-controls-summary' );
1146+
if ( summary ) {
1147+
summary.textContent = __( 'Scanning...', 'accessibility-checker' );
1148+
summary.classList.remove( 'edac-error' );
1149+
}
1150+
window.runAccessibilityScan().then( ( result ) => {
1151+
const postId = window.edacFrontendHighlighterApp && window.edacFrontendHighlighterApp.postID;
1152+
const nonce = window.edacFrontendHighlighterApp && window.edacFrontendHighlighterApp.restNonce;
1153+
if ( ! postId || ! nonce ) {
1154+
self.showWait( false );
1155+
self.showScanError( __( 'Missing postId or nonce.', 'accessibility-checker' ) );
1156+
return;
1157+
}
1158+
if ( ! result || ! result.violations || result.violations.length === 0 ) {
1159+
self.showWait( false );
1160+
self.showScanError( __( 'No violations found, skipping save.', 'accessibility-checker' ) );
1161+
return;
1162+
}
1163+
self.saveScanResults( postId, nonce, result.violations, densityMetrics );
1164+
} ).catch( () => {
1165+
self.showWait( false );
1166+
self.showScanError( __( 'Accessibility scan error.', 'accessibility-checker' ) );
1167+
} );
1168+
}
1169+
1170+
saveScanResults( postId, nonce, violations, densityMetrics ) {
1171+
const self = this;
1172+
fetch( '/wp-json/accessibility-checker/v1/post-scan-results/' + postId, {
1173+
method: 'POST',
1174+
headers: {
1175+
'Content-Type': 'application/json',
1176+
'X-WP-Nonce': nonce,
1177+
},
1178+
body: JSON.stringify( {
1179+
violations,
1180+
isSkipped: false,
1181+
isFailure: false,
1182+
densityMetrics,
1183+
} ),
1184+
} )
1185+
.then( ( response ) => response.json() )
1186+
.then( ( data ) => {
1187+
self.showWait( false );
1188+
if ( data && data.success ) {
1189+
// Optionally show a success message or update UI
1190+
} else {
1191+
self.showScanError( __( 'Saving failed.', 'accessibility-checker' ) );
1192+
}
1193+
} )
1194+
.catch( () => {
1195+
self.showWait( false );
1196+
self.showScanError( __( 'Error saving scan results.', 'accessibility-checker' ) );
1197+
} );
1198+
}
1199+
1200+
/**
1201+
* Show an error message in the scan panel or as an alert fallback.
1202+
* @param {string} message
1203+
*/
1204+
showScanError( message ) {
1205+
const summary = document.querySelector( '.edac-highlight-panel-controls-summary' );
1206+
if ( summary ) {
1207+
summary.textContent = message;
1208+
summary.classList.add( 'edac-error' );
1209+
}
1210+
}
10911211
}
10921212

10931213
// Some systems (Cloudflare Rocket Loader) defers scripts for performance but that can

src/pageScanner/index.js

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { getPageDensity } from './helpers/density';
1010

1111
const SCAN_TIMEOUT_IN_SECONDS = 30;
1212

13+
// Hold the timeout for the scan so it can bail on long-running scans.
14+
let tooLongTimeout;
15+
1316
// Landmark tags for semantic regions
1417
const LANDMARK_TAGS = [ 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE' ];
1518
const LANDMARK_ROLES = [
@@ -168,6 +171,30 @@ const iframeId = body.getAttribute( 'data-iframe-id' );
168171
const eventName = body.getAttribute( 'data-iframe-event-name' );
169172
const postId = body.getAttribute( 'data-iframe-post-id' );
170173

174+
/**
175+
* Check if the current context the script is loaded in is a scanner iframe.
176+
*
177+
* @return {boolean} True if in iframe context, false otherwise.
178+
*/
179+
function isIframeContext() {
180+
return !! ( body && body.hasAttribute( 'data-iframe-id' ) && body.hasAttribute( 'data-iframe-event-name' ) );
181+
}
182+
183+
/**
184+
* Get the iframe options from the body attributes/
185+
*
186+
* @return {Object} {{configOptions: {}, runOptions: {}, iframeId: string | Attribute, eventName: string | Attribute, postId: string | Attribute}}
187+
*/
188+
function getIframeOptions() {
189+
return {
190+
configOptions: {},
191+
runOptions: {},
192+
iframeId: body.getAttribute( 'data-iframe-id' ),
193+
eventName: body.getAttribute( 'data-iframe-event-name' ),
194+
postId: body.getAttribute( 'data-iframe-post-id' ),
195+
};
196+
}
197+
171198
const scan = async (
172199
options = { configOptions: {}, runOptions: {} }
173200
) => {
@@ -283,6 +310,7 @@ function dispatchDoneEvent( violations, errorMsgs, error ) {
283310
top.dispatchEvent( customEvent );
284311
}
285312

313+
// eslint-disable-next-line no-unused-vars
286314
const onDone = ( violations = [], errorMsgs = [], error = false ) => {
287315
// cleanup the timeout.
288316
clearTimeout( tooLongTimeout );
@@ -293,41 +321,57 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => {
293321
function() {
294322
axe.teardown();
295323
axe = null;
296-
297-
dispatchDoneEvent( violations, errorMsgs, error );
324+
dispatchDoneEvent( violations, errorMsgs, '' );
298325
},
299326
function() {
300327
axe.teardown();
301328
axe = null;
302-
303-
// Create a custom event
304329
errorMsgs.push( '***** axe.cleanup() failed.' );
305-
306-
dispatchDoneEvent( violations, errorMsgs, error );
330+
dispatchDoneEvent( violations, errorMsgs, 'cleanup-failed' );
307331
}
308332
);
309333
} else {
310-
error = true;
311-
312334
errorMsgs.push( '***** axe.cleanup() does not exist.' );
313335
axe = null;
314-
315-
dispatchDoneEvent( violations, errorMsgs, error );
336+
dispatchDoneEvent( violations, errorMsgs, 'cleanup-not-exists' );
316337
}
317338
};
318339

319-
// Fire a failed event if the scan doesn't complete on time.
320-
const tooLongTimeout = setTimeout( function() {
321-
onDone( [], [ '***** axe scan took too long.' ], true );
322-
}, SCAN_TIMEOUT_IN_SECONDS * 1000 );
323-
324-
// Start the scan.
325-
scan().then( ( results ) => {
326-
const violations = JSON.parse( JSON.stringify( results.violations ) );
327-
onDone( violations );
328-
} ).catch( ( err ) => {
329-
onDone( [], [ err.message ], true );
330-
} );
340+
/**
341+
* Attach an axe runner to the window object to allow for running the scan from
342+
* the active document.
343+
*
344+
* @param {Object} options Options for the accessibility scan.
345+
* @return {Promise<Object>} Promise resolving to the scan result.
346+
*/
347+
window.runAccessibilityScan = async function( options = {} ) {
348+
return scan( options )
349+
.then( ( result ) => {
350+
if ( typeof options.onComplete === 'function' ) {
351+
options.onComplete( result );
352+
}
353+
return result;
354+
} )
355+
.catch( ( err ) => {
356+
if ( typeof options.onComplete === 'function' ) {
357+
options.onComplete( null, err );
358+
}
359+
throw err;
360+
} );
361+
};
362+
363+
// Auto-run scan and dispatch event to parent frame if in iframe context
364+
if ( isIframeContext() ) {
365+
const iframeOptions = getIframeOptions();
366+
367+
tooLongTimeout = setTimeout( () => {
368+
dispatchDoneEvent( [], [ 'Scan timed out' ], 'timeout' );
369+
}, SCAN_TIMEOUT_IN_SECONDS * 1000 );
370+
371+
scan( iframeOptions )
372+
.then( ( result ) => onDone( result.violations, [], null ) )
373+
.catch( ( err ) => onDone( [], [ err.message || 'Unknown error' ], err.message ) );
374+
}
331375

332376
// Helper to process a violation and return the formatted object
333377
function processViolation( violation, item ) {

0 commit comments

Comments
 (0)