From fba7d12f7f63b84fd8fc374770c0e6f1a16f89bc Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 21 May 2025 16:13:37 +0100 Subject: [PATCH 01/12] Move the tooLongTimeout to a higher scope so it can be managed across methods --- src/pageScanner/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index 5456a1c23..d829bf524 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -10,6 +10,9 @@ import { getPageDensity } from './helpers/density'; const SCAN_TIMEOUT_IN_SECONDS = 30; +// Hold the timeout for the scan so it can bail on long-running scans. +let tooLongTimeout; + // Read the data passed from the parent document. const body = document.querySelector( 'body' ); const iframeId = body.getAttribute( 'data-iframe-id' ); @@ -176,11 +179,6 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => { } }; -// Fire a failed event if the scan doesn't complete on time. -const tooLongTimeout = setTimeout( function() { - onDone( [], [ '***** axe scan took too long.' ], true ); -}, SCAN_TIMEOUT_IN_SECONDS * 1000 ); - // Start the scan. scan().then( ( results ) => { const violations = JSON.parse( JSON.stringify( results.violations ) ); From 1e0ab7fc041c1d4089db42e5ada9af5f16c2071a Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 21 May 2025 16:17:09 +0100 Subject: [PATCH 02/12] Add helpers for detecting and getting options when in iframe context --- src/pageScanner/index.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index d829bf524..bc09b36fe 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -19,6 +19,30 @@ const iframeId = body.getAttribute( 'data-iframe-id' ); const eventName = body.getAttribute( 'data-iframe-event-name' ); const postId = body.getAttribute( 'data-iframe-post-id' ); +/** + * Check if the current context the script is loaded in is a scanner iframe. + * + * @return {boolean} True if in iframe context, false otherwise. + */ +function isIframeContext() { + return !! ( body && body.hasAttribute( 'data-iframe-id' ) && body.hasAttribute( 'data-iframe-event-name' ) ); +} + +/** + * Get the iframe options from the body attributes/ + * + * @return {Object} {{configOptions: {}, runOptions: {}, iframeId: string | Attribute, eventName: string | Attribute, postId: string | Attribute}} + */ +function getIframeOptions() { + return { + configOptions: {}, + runOptions: {}, + iframeId: body.getAttribute( 'data-iframe-id' ), + eventName: body.getAttribute( 'data-iframe-event-name' ), + postId: body.getAttribute( 'data-iframe-post-id' ), + }; +} + const scan = async ( options = { configOptions: {}, runOptions: {} } ) => { From be4761eaad75ae71f2177332cd2846a6dbfb0172 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 21 May 2025 16:20:16 +0100 Subject: [PATCH 03/12] Create an axe runner method that lives on the window object and wrap the old scan runner in iframe checking contexts --- src/pageScanner/index.js | 43 +++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index bc09b36fe..1b1f061ae 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -203,10 +203,39 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => { } }; -// Start the scan. -scan().then( ( results ) => { - const violations = JSON.parse( JSON.stringify( results.violations ) ); - onDone( violations ); -} ).catch( ( err ) => { - onDone( [], [ err.message ], true ); -} ); +/** + * Attach an axe runner to the window object to allow for running the scan from + * the active document. + * + * @param {Object} options Options for the accessibility scan. + * @return {Promise} Promise resolving to the scan result. + */ +window.runAccessibilityScan = async function( options = {} ) { + return scan( options ) + .then( ( result ) => { + if ( typeof options.onComplete === 'function' ) { + options.onComplete( result ); + } + return result; + } ) + .catch( ( err ) => { + if ( typeof options.onComplete === 'function' ) { + options.onComplete( null, err ); + } + throw err; + } ); +}; + +// Auto-run scan and dispatch event to parent frame if in iframe context +if ( isIframeContext() ) { + const iframeOptions = getIframeOptions(); + + tooLongTimeout = setTimeout( () => { + dispatchDoneEvent( [], [ 'Scan timed out' ], 'timeout' ); + }, SCAN_TIMEOUT_IN_SECONDS * 1000 ); + + scan( iframeOptions ) + .then( ( result ) => onDone( result.violations, [], null ) ) + .catch( ( err ) => onDone( [], [ err.message || 'Unknown error' ], err.message ) ); +} + From c506e9d91d1d797fc7ab181970606e3b79b1035e Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 21 May 2025 16:25:01 +0100 Subject: [PATCH 04/12] Correct the passed type of error to the dispatchDoneEvent during cleanup --- src/pageScanner/index.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index 1b1f061ae..39f8a867e 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -180,26 +180,19 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => { function() { axe.teardown(); axe = null; - - dispatchDoneEvent( violations, errorMsgs, error ); + dispatchDoneEvent( violations, errorMsgs, '' ); }, function() { axe.teardown(); axe = null; - - // Create a custom event errorMsgs.push( '***** axe.cleanup() failed.' ); - - dispatchDoneEvent( violations, errorMsgs, error ); + dispatchDoneEvent( violations, errorMsgs, error ? 'cleanup-failed' : '' ); } ); } else { - error = true; - errorMsgs.push( '***** axe.cleanup() does not exist.' ); axe = null; - - dispatchDoneEvent( violations, errorMsgs, error ); + dispatchDoneEvent( violations, errorMsgs, 'cleanup-not-exists' ); } }; From 03cf745725d5a9428aee5450a1f16feabfb28ef8 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 21 May 2025 16:35:39 +0100 Subject: [PATCH 05/12] Avoid potential error for nodes that no longer exist when trying get their contents --- src/pageScanner/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index 39f8a867e..11bc8f2cd 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -85,9 +85,10 @@ const scan = async ( //Build an array of the dom selectors and ruleIDs for violations/failed tests item.violations.forEach( ( violation ) => { if ( violation.result === 'failed' ) { + const el = document.querySelector( violation.node.selector ); violations.push( { selector: violation.node.selector, - html: document.querySelector( violation.node.selector ).outerHTML, + html: el ? el.outerHTML : null, ruleId: item.id, impact: item.impact, tags: item.tags, @@ -98,9 +99,10 @@ const scan = async ( // Handle incomplete results for form-field-multiple-labels only. if ( item.id === 'form-field-multiple-labels' ) { // Allow incomplete results for this rule. item.incomplete.forEach( ( incompleteItem ) => { + const el = document.querySelector( incompleteItem.node.selector ); violations.push( { selector: incompleteItem.node.selector, - html: document.querySelector( incompleteItem.node.selector ).outerHTML, + html: el ? el.outerHTML : null, ruleId: item.id, impact: item.impact, tags: item.tags, @@ -231,4 +233,3 @@ if ( isIframeContext() ) { .then( ( result ) => onDone( result.violations, [], null ) ) .catch( ( err ) => onDone( [], [ err.message || 'Unknown error' ], err.message ) ); } - From fe2ce25356e1d52678e58044ea34b6333cd3433c Mon Sep 17 00:00:00 2001 From: William Patton Date: Tue, 3 Jun 2025 15:05:55 +0100 Subject: [PATCH 06/12] Always give a failed message if cleanup fails Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/pageScanner/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index 11bc8f2cd..27112f27e 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -188,7 +188,7 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => { axe.teardown(); axe = null; errorMsgs.push( '***** axe.cleanup() failed.' ); - dispatchDoneEvent( violations, errorMsgs, error ? 'cleanup-failed' : '' ); +dispatchDoneEvent( violations, errorMsgs, 'cleanup-failed' ); } ); } else { From 06488e911f5b65cbfe7a8bbcc966fdd0d114485a Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 3 Jun 2025 15:10:36 +0100 Subject: [PATCH 07/12] Fix some lint issues --- src/pageScanner/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pageScanner/index.js b/src/pageScanner/index.js index 27112f27e..e9aefa2be 100644 --- a/src/pageScanner/index.js +++ b/src/pageScanner/index.js @@ -172,6 +172,7 @@ function dispatchDoneEvent( violations, errorMsgs, error ) { top.dispatchEvent( customEvent ); } +// eslint-disable-next-line no-unused-vars const onDone = ( violations = [], errorMsgs = [], error = false ) => { // cleanup the timeout. clearTimeout( tooLongTimeout ); @@ -188,7 +189,7 @@ const onDone = ( violations = [], errorMsgs = [], error = false ) => { axe.teardown(); axe = null; errorMsgs.push( '***** axe.cleanup() failed.' ); -dispatchDoneEvent( violations, errorMsgs, 'cleanup-failed' ); + dispatchDoneEvent( violations, errorMsgs, 'cleanup-failed' ); } ); } else { From 37d17ebc722fe1573231f21bb99ec6551fb8c001 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Thu, 3 Jul 2025 13:50:27 +0100 Subject: [PATCH 08/12] Add scanner bundle URL to frontend highlighter app This change allows the frontend highlighter app to access the scanner bundle for enhanced functionality. --- includes/classes/class-enqueue-frontend.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/includes/classes/class-enqueue-frontend.php b/includes/classes/class-enqueue-frontend.php index 60efe7bf9..19ab8f20d 100644 --- a/includes/classes/class-enqueue-frontend.php +++ b/includes/classes/class-enqueue-frontend.php @@ -97,16 +97,17 @@ public static function maybe_enqueue_frontend_highlighter() { 'edac-frontend-highlighter-app', 'edacFrontendHighlighterApp', [ - 'postID' => $post_id, - 'nonce' => wp_create_nonce( 'ajax-nonce' ), - 'restNonce' => wp_create_nonce( 'wp_rest' ), - 'userCanFix' => current_user_can( apply_filters( 'edac_filter_settings_capability', 'manage_options' ) ), - 'edacUrl' => esc_url_raw( get_site_url() ), - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'loggedIn' => is_user_logged_in(), - 'appCssUrl' => EDAC_PLUGIN_URL . 'build/css/frontendHighlighterApp.css?ver=' . EDAC_VERSION, - 'widgetPosition' => get_option( 'edac_frontend_highlighter_position', 'right' ), - 'editorLink' => get_edit_post_link( $post_id ), + 'postID' => $post_id, + 'nonce' => wp_create_nonce( 'ajax-nonce' ), + 'restNonce' => wp_create_nonce( 'wp_rest' ), + 'userCanFix' => current_user_can( apply_filters( 'edac_filter_settings_capability', 'manage_options' ) ), + 'edacUrl' => esc_url_raw( get_site_url() ), + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'loggedIn' => is_user_logged_in(), + 'appCssUrl' => EDAC_PLUGIN_URL . 'build/css/frontendHighlighterApp.css?ver=' . EDAC_VERSION, + 'widgetPosition' => get_option( 'edac_frontend_highlighter_position', 'right' ), + 'editorLink' => get_edit_post_link( $post_id ), + 'scannerBundleUrl' => plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/pageScanner.bundle.js', ] ); From a9243645bf958d2a4c26b665db2fb507d93bd32c Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Thu, 3 Jul 2025 13:52:38 +0100 Subject: [PATCH 09/12] Implement scan initiation and result handling in frontend Refactor the scanning logic to improve readability and maintainability. Introduce helper methods for kicking off the scan, running the scan, and saving results. Enhance error handling and user feedback during the scanning process. --- src/frontendHighlighterApp/index.js | 127 ++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/src/frontendHighlighterApp/index.js b/src/frontendHighlighterApp/index.js index dc3eb474a..3bf50e478 100644 --- a/src/frontendHighlighterApp/index.js +++ b/src/frontendHighlighterApp/index.js @@ -185,15 +185,21 @@ class AccessibilityCheckerHighlight { }, ); } + } else if ( ! self._scanAttempted && response.data && response.data[ 0 ] && response.data[ 0 ].code === -3 ) { + // Only try kickoffScan once per highlightAjax call + self._scanAttempted = true; + self.kickoffScan(); + // After kickoffScan, try highlightAjax again, but only once + setTimeout( () => { + self.highlightAjax().then( resolve ).catch( reject ); + }, 5000 ); // Wait 5s for scan to complete. } else { - resolve( [] ); - //console.log(response); + // Default: resolve with empty issues/fixes + resolve( { issues: [], fixes: [] } ); } } else { self.showWait( false ); - //console.log( 'Request failed. Returned status of ' + xhr.status ); - reject( { status: xhr.status, statusText: xhr.statusText, @@ -535,7 +541,11 @@ class AccessibilityCheckerHighlight { } } ).catch( ( err ) => { - //TODO: + // Output a message that says that there are no issues or that the issues could not be loaded. + const summary = document.querySelector( '.edac-highlight-panel-controls-summary' ); + if ( summary ) { + summary.textContent = __( 'An error occurred when loading the issues.', 'accessibility-checker' ); + } } ); } @@ -889,6 +899,113 @@ class AccessibilityCheckerHighlight { div.textContent = textContent; } + + // Refactored: move scan and save logic to helper methods + kickoffScan() { + const getPageDensity = () => { + const elementCount = document.body.getElementsByTagName( '*' ).length; + const contentLength = document.body.innerText.length; + return { elementCount, contentLength }; + }; + const densityMetrics = getPageDensity(); + const self = this; + const scriptId = 'edac-accessibility-checker-scanner-script'; + if ( ! document.getElementById( scriptId ) ) { + const script = document.createElement( 'script' ); + script.src = window.edacFrontendHighlighterApp?.scannerBundleUrl || '/wp-content/plugins/accessibility-checker/build/pageScanner.bundle.js'; + script.id = scriptId; + script.onload = function() { + setTimeout( () => { + self._runScanOrShowError( densityMetrics ); + }, 100 ); + }; + script.onerror = function() { + self.showWait( false ); + self.showScanError( 'Failed to load scanner script.' ); + }; + document.head.appendChild( script ); + } else { + self._runScanOrShowError( densityMetrics ); + } + } + + _runScanOrShowError( densityMetrics ) { + if ( window.runAccessibilityScan ) { + this.runAccessibilityScanAndSave( densityMetrics ); + } else { + this.showWait( false ); + this.showScanError( 'Scanner function not found.' ); + } + } + + runAccessibilityScanAndSave( densityMetrics ) { + const self = this; + const summary = document.querySelector( '.edac-highlight-panel-controls-summary' ); + if ( summary ) { + summary.textContent = __( 'Scanning...', 'accessibility-checker' ); + summary.classList.remove( 'edac-error' ); + } + window.runAccessibilityScan().then( ( result ) => { + const postId = window.edacFrontendHighlighterApp && window.edacFrontendHighlighterApp.postID; + const nonce = window.edacFrontendHighlighterApp && window.edacFrontendHighlighterApp.restNonce; + if ( ! postId || ! nonce ) { + self.showWait( false ); + self.showScanError( 'Missing postId or nonce.' ); + return; + } + if ( ! result || ! result.violations || result.violations.length === 0 ) { + self.showWait( false ); + self.showScanError( 'No violations found, skipping save.' ); + return; + } + self.saveScanResults( postId, nonce, result.violations, densityMetrics ); + } ).catch( () => { + self.showWait( false ); + self.showScanError( 'Accessibility scan error.' ); + } ); + } + + saveScanResults( postId, nonce, violations, densityMetrics ) { + const self = this; + fetch( '/wp-json/accessibility-checker/v1/post-scan-results/' + postId, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': nonce, + }, + body: JSON.stringify( { + violations, + isSkipped: false, + isFailure: false, + densityMetrics, + } ), + } ) + .then( ( response ) => response.json() ) + .then( ( data ) => { + self.showWait( false ); + if ( data && data.success ) { + // Optionally show a success message or update UI + } else { + self.showScanError( 'Saving failed.' ); + } + } ) + .catch( () => { + self.showWait( false ); + self.showScanError( 'Error saving scan results.' ); + } ); + } + + /** + * Show an error message in the scan panel or as an alert fallback. + * @param {string} message + */ + showScanError( message ) { + const summary = document.querySelector( '.edac-highlight-panel-controls-summary' ); + if ( summary ) { + summary.textContent = message; + summary.classList.add( 'edac-error' ); + } + } } // Some systems (Cloudflare Rocket Loader) defers scripts for performance but that can From 8d4227fd2ff3b58768f9ef84c32ee1405fb62011 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 8 Jul 2025 13:38:14 +0100 Subject: [PATCH 10/12] Make the scan state feedback messages translatable --- src/frontendHighlighterApp/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/frontendHighlighterApp/index.js b/src/frontendHighlighterApp/index.js index 3bf50e478..86b5a5a99 100644 --- a/src/frontendHighlighterApp/index.js +++ b/src/frontendHighlighterApp/index.js @@ -900,7 +900,9 @@ class AccessibilityCheckerHighlight { div.textContent = textContent; } - // Refactored: move scan and save logic to helper methods + /** + * Kick off the accessibility scan. + */ kickoffScan() { const getPageDensity = () => { const elementCount = document.body.getElementsByTagName( '*' ).length; @@ -934,7 +936,7 @@ class AccessibilityCheckerHighlight { this.runAccessibilityScanAndSave( densityMetrics ); } else { this.showWait( false ); - this.showScanError( 'Scanner function not found.' ); + this.showScanError( __( 'Scanner function not found.', 'accessibility-checker' ) ); } } @@ -950,18 +952,18 @@ class AccessibilityCheckerHighlight { const nonce = window.edacFrontendHighlighterApp && window.edacFrontendHighlighterApp.restNonce; if ( ! postId || ! nonce ) { self.showWait( false ); - self.showScanError( 'Missing postId or nonce.' ); + self.showScanError( __( 'Missing postId or nonce.', 'accessibility-checker' ) ); return; } if ( ! result || ! result.violations || result.violations.length === 0 ) { self.showWait( false ); - self.showScanError( 'No violations found, skipping save.' ); + self.showScanError( __( 'No violations found, skipping save.', 'accessibility-checker' ) ); return; } self.saveScanResults( postId, nonce, result.violations, densityMetrics ); } ).catch( () => { self.showWait( false ); - self.showScanError( 'Accessibility scan error.' ); + self.showScanError( __( 'Accessibility scan error.', 'accessibility-checker' ) ); } ); } @@ -986,12 +988,12 @@ class AccessibilityCheckerHighlight { if ( data && data.success ) { // Optionally show a success message or update UI } else { - self.showScanError( 'Saving failed.' ); + self.showScanError( __( 'Saving failed.', 'accessibility-checker' ) ); } } ) .catch( () => { self.showWait( false ); - self.showScanError( 'Error saving scan results.' ); + self.showScanError( __( 'Error saving scan results.', 'accessibility-checker' ) ); } ); } From 8808e3d198996d9a42eb2870c6e3ff44fccd36e5 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 8 Jul 2025 13:54:16 +0100 Subject: [PATCH 11/12] Define default state for scanAttempted --- src/frontendHighlighterApp/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontendHighlighterApp/index.js b/src/frontendHighlighterApp/index.js index 86b5a5a99..f5d9934e3 100644 --- a/src/frontendHighlighterApp/index.js +++ b/src/frontendHighlighterApp/index.js @@ -19,6 +19,7 @@ class AccessibilityCheckerHighlight { }; this.settings = { ...defaultSettings, ...settings }; + this._scanAttempted = false; this.highlightPanel = this.addHighlightPanel(); this.nextButton = document.querySelector( '#edac-highlight-next' ); From 9832baec9d17aa9567df2e9b6c4049825a4cb99d Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 8 Jul 2025 13:54:51 +0100 Subject: [PATCH 12/12] Use more readable check for data type when kicking off scan --- src/frontendHighlighterApp/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontendHighlighterApp/index.js b/src/frontendHighlighterApp/index.js index f5d9934e3..2e28bf371 100644 --- a/src/frontendHighlighterApp/index.js +++ b/src/frontendHighlighterApp/index.js @@ -186,7 +186,7 @@ class AccessibilityCheckerHighlight { }, ); } - } else if ( ! self._scanAttempted && response.data && response.data[ 0 ] && response.data[ 0 ].code === -3 ) { + } else if ( ! self._scanAttempted && response.data?.[ 0 ]?.code === -3 ) { // Only try kickoffScan once per highlightAjax call self._scanAttempted = true; self.kickoffScan();