From 43aa3cbe447cd367c2e1651d4debf2064bc0fa4c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 7 Jan 2020 17:28:10 +0800 Subject: [PATCH] MDL-59817 atto_accessibilitychecker: Handle transparency properly Some browsers, notably Firefox, do not return the computed style for background colour in a computed RGB format. Instead they return the RGBA where the alpha channel is set to fully transparent. To solve this we need to work up the hierarchy and compute the background colour for each parent node until we reach full alpha (1). We can use a standard calculation to approximate the value for the resultant element background by multiplying the alpha of the current transparent (or semi-transparent) node with the R, G, or B channel in question, and that of the parent node's background colour. There are cases where this will not be 100% accurate - notably where there is some additional content in addition to the parent background, but this gives us a reasoable approximation for the majority of cases. Additionally the code has never considered the full set of node content when calculating this information. --- ...oodle-atto_accessibilitychecker-button-debug.js | 47 ++++++++++++++++++++-- .../moodle-atto_accessibilitychecker-button-min.js | 2 +- .../moodle-atto_accessibilitychecker-button.js | 47 ++++++++++++++++++++-- .../yui/src/button/js/button.js | 47 ++++++++++++++++++++-- 4 files changed, 133 insertions(+), 10 deletions(-) rewrite lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js (66%) diff --git a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-debug.js b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-debug.js index cdf67fada87..c8d7bb63bce 100644 --- a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-debug.js +++ b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-debug.js @@ -129,8 +129,11 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. // Check for non-empty text. if (Y.Lang.trim(node.get('text')) !== '') { - foreground = node.getComputedStyle('color'); - background = node.getComputedStyle('backgroundColor'); + foreground = Y.Color.fromArray( + this._getComputedBackgroundColor(node, node.getComputedStyle('color')), + Y.Color.TYPES.RGBA + ); + background = Y.Color.fromArray(this._getComputedBackgroundColor(node), Y.Color.TYPES.RGBA); lum1 = this._getLuminanceFromCssColor(foreground); lum2 = this._getLuminanceFromCssColor(background); @@ -239,7 +242,7 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. * Generate the HTML that lists the found warnings. * * @method _addWarnings - * @param {Node} A Node to append the html to. + * @param {Node} list Node to append the html to. * @param {String} description Description of this failure. * @param {array} nodes An array of failing nodes. * @param {boolean} imagewarnings true if the warnings are related to images, false if text. @@ -309,6 +312,44 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. b1 = part1(color[2]); return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; + }, + + /** + * Get the computed RGB converted to full alpha value, considering the node hierarchy. + * + * @method _getComputedBackgroundColor + * @param {Node} node + * @param {String} color The initial colour. If not specified, fetches the backgroundColor from the node. + * @return {Array} Colour in Array form (RGBA) + * @private + */ + _getComputedBackgroundColor: function(node, color) { + color = color || node.getComputedStyle('backgroundColor'); + + if (color.toLowerCase() === 'transparent') { + // Y.Color doesn't handle 'transparent' properly. + color = 'rgba(1, 1, 1, 0)'; + } + + // Convert the colour to its constituent parts in RGBA format, then fetch the alpha. + var colorParts = Y.Color.toArray(color); + var alpha = colorParts[3]; + + if (alpha === 1) { + // If the alpha of the background is already 1, then the parent background colour does not change anything. + return colorParts; + } + + // Fetch the computed background colour of the parent and use it to calculate the RGB of this item. + var parentColor = this._getComputedBackgroundColor(node.get('parentNode')); + return [ + // RGB = (alpha * R|G|B) + (1 - alpha * solid parent colour). + (1 - alpha) * parentColor[0] + alpha * colorParts[0], + (1 - alpha) * parentColor[1] + alpha * colorParts[1], + (1 - alpha) * parentColor[2] + alpha * colorParts[2], + // We always return a colour with full alpha. + 1 + ]; } }); diff --git a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js dissimilarity index 66% index 294089029df..e4a9c3bc743 100644 --- a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js +++ b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js @@ -1 +1 @@ -YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){var n="atto_accessibilitychecker";e.namespace("M.atto_accessibilitychecker").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addButton({icon:"e/accessibility_checker",callback:this._displayDialogue})},_displayDialogue:function(){var e=this.getDialogue({headerContent:M.util.get_string("pluginname",n),width:"500px",focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()},_getDialogueContent:function(){var t=e.Node.create('
');return t.append(this._getWarnings()),t.delegate("click",function(e){e.preventDefault();var t=this.get("host"),n=e.currentTarget.getData("sourceNode"),r=this.getDialogue();n?(r.set("focusAfterHide",this.editor).hide(),t.setSelection(t.getSelectionFromNode(n))):r.hide()},"a",this),t},_getWarnings:function(){var t,r=e.Node.create("
");return t=[],this.editor.all("img").each(function(e){var n=e.getAttribute("alt");(typeof n=="undefined"||n==="")&&e.getAttribute("role")!=="presentation"&&t.push(e)},this),this._addWarnings(r,M.util.get_string("imagesmissingalt",n),t,!0),t=[],this.editor.all("*").each(function(n){var r,i,s,o,u;if(e.Lang.trim(n.get("text"))!==""){r=n.getComputedStyle("color"),i=n.getComputedStyle("backgroundColor"),o=this._getLuminanceFromCssColor(r),u=this._getLuminanceFromCssColor(i),o>u?s=(o+.05)/(u+.05):s=(u+.05)/(o+.05);if(s<=4.5){var a=0,f=!1;for(a=0;a1e3&&!this.editor.one("h3, h4, h5")&&this._addWarnings(r,M.util.get_string("needsmoreheadings",n),[this.editor],!1),t=[],this.editor.all("table").each(function(e){var n=e.one("caption");(n===null||n.get("text").trim()==="")&&t.push(e)},this),this._addWarnings(r,M.util.get_string("tablesmissingcaption",n),t,!1),t=[],this.editor.all("table").each(function(e){var n=e.one("[colspan],[rowspan]");n!==null&&t.push(e)},this),this._addWarnings(r,M.util.get_string("tableswithmergedcells",n),t,!1),t=[],this.editor.all("table").each(function(e){if(e.one("tr").one("td"))e.all("tr").some(function(n){var r=n.one("th");return!r||r.get("text").trim()===""?(t.push(e),!0):!1},this);else{var n=!1;e.one("tr").all("th").some(function(r){return n=!0,r.get("text").trim()===""?(t.push(e),!0):!1}),n||t.push(e)}},this),this._addWarnings(r,M.util.get_string("tablesmissingheaders",n),t,!1),r.hasChildNodes()||r.append("

"+M.util.get_string("nowarnings",n)+"

"),r},_addWarnings:function(t,r,i,s){var o,u,a,f,l,c,h,p;if(i.length>0){o=e.Node.create("

"+r+"

"),u=e.Node.create('
    '),a=0;for(a=0;a"),s?(f=i[a].getAttribute("src"),h=e.Node.create(' '+f+"")):(l="innerText"in i[a]?"innerText":"textContent",p=i[a].get(l).trim(),p===""&&(p=M.util.get_string("emptytext",n)),i[a]===this.editor&&(p=M.util.get_string("entiredocument",n)),h=e.Node.create(''+p+"")),h.setData("sourceNode",i[a]),c.append(h),u.append(c);o.append(u),t.append(o)}},_getLuminanceFromCssColor:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o}})},"@VERSION@",{requires:["color-base","moodle-editor_atto-plugin"]}); +YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){var n="atto_accessibilitychecker";e.namespace("M.atto_accessibilitychecker").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addButton({icon:"e/accessibility_checker",callback:this._displayDialogue})},_displayDialogue:function(){var e=this.getDialogue({headerContent:M.util.get_string("pluginname",n),width:"500px",focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()},_getDialogueContent:function(){var t=e.Node.create('
    ');return t.append(this._getWarnings()),t.delegate("click",function(e){e.preventDefault();var t=this.get("host"),n=e.currentTarget.getData("sourceNode"),r=this.getDialogue();n?(r.set("focusAfterHide",this.editor).hide(),t.setSelection(t.getSelectionFromNode(n))):r.hide()},"a",this),t},_getWarnings:function(){var t,r=e.Node.create("
    ");return t=[],this.editor.all("img").each(function(e){var n=e.getAttribute("alt");(typeof n=="undefined"||n==="")&&e.getAttribute("role")!=="presentation"&&t.push(e)},this),this._addWarnings(r,M.util.get_string("imagesmissingalt",n),t,!0),t=[],this.editor.all("*").each(function(n){var r,i,s,o,u;if(e.Lang.trim(n.get("text"))!==""){r=e.Color.fromArray(this._getComputedBackgroundColor(n,n.getComputedStyle("color")),e.Color.TYPES.RGBA),i=e.Color.fromArray(this._getComputedBackgroundColor(n),e.Color.TYPES.RGBA),o=this._getLuminanceFromCssColor(r),u=this._getLuminanceFromCssColor(i),o>u?s=(o+.05)/(u+.05):s=(u+.05)/(o+.05);if(s<=4.5){var a=0,f=!1;for(a=0;a1e3&&!this.editor.one("h3, h4, h5")&&this._addWarnings(r,M.util.get_string("needsmoreheadings",n),[this.editor],!1),t=[],this.editor.all("table").each(function(e){var n=e.one("caption");(n===null||n.get("text").trim()==="")&&t.push(e)},this),this._addWarnings(r,M.util.get_string("tablesmissingcaption",n),t,!1),t=[],this.editor.all("table").each(function(e){var n=e.one("[colspan],[rowspan]");n!==null&&t.push(e)},this),this._addWarnings(r,M.util.get_string("tableswithmergedcells",n),t,!1),t=[],this.editor.all("table").each(function(e){if(e.one("tr").one("td"))e.all("tr").some(function(n){var r=n.one("th");return!r||r.get("text").trim()===""?(t.push(e),!0):!1},this);else{var n=!1;e.one("tr").all("th").some(function(r){return n=!0,r.get("text").trim()===""?(t.push(e),!0):!1}),n||t.push(e)}},this),this._addWarnings(r,M.util.get_string("tablesmissingheaders",n),t,!1),r.hasChildNodes()||r.append("

    "+M.util.get_string("nowarnings",n)+"

    "),r},_addWarnings:function(t,r,i,s){var o,u,a,f,l,c,h,p;if(i.length>0){o=e.Node.create("

    "+r+"

    "),u=e.Node.create('
      '),a=0;for(a=0;a"),s?(f=i[a].getAttribute("src"),h=e.Node.create(' '+f+"")):(l="innerText"in i[a]?"innerText":"textContent",p=i[a].get(l).trim(),p===""&&(p=M.util.get_string("emptytext",n)),i[a]===this.editor&&(p=M.util.get_string("entiredocument",n)),h=e.Node.create(''+p+"")),h.setData("sourceNode",i[a]),c.append(h),u.append(c);o.append(u),t.append(o)}},_getLuminanceFromCssColor:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o},_getComputedBackgroundColor:function(t,n){n=n||t.getComputedStyle("backgroundColor"),n.toLowerCase()==="transparent"&&(n="rgba(1, 1, 1, 0)");var r=e.Color.toArray(n),i=r[3];if(i===1)return r;var s=this._getComputedBackgroundColor(t.get("parentNode"));return[(1-i)*s[0]+i*r[0],(1-i)*s[1]+i*r[1],(1-i)*s[2]+i*r[2],1]}})},"@VERSION@",{requires:["color-base","moodle-editor_atto-plugin"]}); diff --git a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button.js b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button.js index 7c149988df8..89d365cda5b 100644 --- a/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button.js +++ b/lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button.js @@ -129,8 +129,11 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. // Check for non-empty text. if (Y.Lang.trim(node.get('text')) !== '') { - foreground = node.getComputedStyle('color'); - background = node.getComputedStyle('backgroundColor'); + foreground = Y.Color.fromArray( + this._getComputedBackgroundColor(node, node.getComputedStyle('color')), + Y.Color.TYPES.RGBA + ); + background = Y.Color.fromArray(this._getComputedBackgroundColor(node), Y.Color.TYPES.RGBA); lum1 = this._getLuminanceFromCssColor(foreground); lum2 = this._getLuminanceFromCssColor(background); @@ -234,7 +237,7 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. * Generate the HTML that lists the found warnings. * * @method _addWarnings - * @param {Node} A Node to append the html to. + * @param {Node} list Node to append the html to. * @param {String} description Description of this failure. * @param {array} nodes An array of failing nodes. * @param {boolean} imagewarnings true if the warnings are related to images, false if text. @@ -304,6 +307,44 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. b1 = part1(color[2]); return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; + }, + + /** + * Get the computed RGB converted to full alpha value, considering the node hierarchy. + * + * @method _getComputedBackgroundColor + * @param {Node} node + * @param {String} color The initial colour. If not specified, fetches the backgroundColor from the node. + * @return {Array} Colour in Array form (RGBA) + * @private + */ + _getComputedBackgroundColor: function(node, color) { + color = color || node.getComputedStyle('backgroundColor'); + + if (color.toLowerCase() === 'transparent') { + // Y.Color doesn't handle 'transparent' properly. + color = 'rgba(1, 1, 1, 0)'; + } + + // Convert the colour to its constituent parts in RGBA format, then fetch the alpha. + var colorParts = Y.Color.toArray(color); + var alpha = colorParts[3]; + + if (alpha === 1) { + // If the alpha of the background is already 1, then the parent background colour does not change anything. + return colorParts; + } + + // Fetch the computed background colour of the parent and use it to calculate the RGB of this item. + var parentColor = this._getComputedBackgroundColor(node.get('parentNode')); + return [ + // RGB = (alpha * R|G|B) + (1 - alpha * solid parent colour). + (1 - alpha) * parentColor[0] + alpha * colorParts[0], + (1 - alpha) * parentColor[1] + alpha * colorParts[1], + (1 - alpha) * parentColor[2] + alpha * colorParts[2], + // We always return a colour with full alpha. + 1 + ]; } }); diff --git a/lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js b/lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js index 1634ac7649a..ce6a4e20212 100644 --- a/lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js @@ -127,8 +127,11 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. // Check for non-empty text. if (Y.Lang.trim(node.get('text')) !== '') { - foreground = node.getComputedStyle('color'); - background = node.getComputedStyle('backgroundColor'); + foreground = Y.Color.fromArray( + this._getComputedBackgroundColor(node, node.getComputedStyle('color')), + Y.Color.TYPES.RGBA + ); + background = Y.Color.fromArray(this._getComputedBackgroundColor(node), Y.Color.TYPES.RGBA); lum1 = this._getLuminanceFromCssColor(foreground); lum2 = this._getLuminanceFromCssColor(background); @@ -237,7 +240,7 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. * Generate the HTML that lists the found warnings. * * @method _addWarnings - * @param {Node} A Node to append the html to. + * @param {Node} list Node to append the html to. * @param {String} description Description of this failure. * @param {array} nodes An array of failing nodes. * @param {boolean} imagewarnings true if the warnings are related to images, false if text. @@ -307,5 +310,43 @@ Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M. b1 = part1(color[2]); return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; + }, + + /** + * Get the computed RGB converted to full alpha value, considering the node hierarchy. + * + * @method _getComputedBackgroundColor + * @param {Node} node + * @param {String} color The initial colour. If not specified, fetches the backgroundColor from the node. + * @return {Array} Colour in Array form (RGBA) + * @private + */ + _getComputedBackgroundColor: function(node, color) { + color = color || node.getComputedStyle('backgroundColor'); + + if (color.toLowerCase() === 'transparent') { + // Y.Color doesn't handle 'transparent' properly. + color = 'rgba(1, 1, 1, 0)'; + } + + // Convert the colour to its constituent parts in RGBA format, then fetch the alpha. + var colorParts = Y.Color.toArray(color); + var alpha = colorParts[3]; + + if (alpha === 1) { + // If the alpha of the background is already 1, then the parent background colour does not change anything. + return colorParts; + } + + // Fetch the computed background colour of the parent and use it to calculate the RGB of this item. + var parentColor = this._getComputedBackgroundColor(node.get('parentNode')); + return [ + // RGB = (alpha * R|G|B) + (1 - alpha * solid parent colour). + (1 - alpha) * parentColor[0] + alpha * colorParts[0], + (1 - alpha) * parentColor[1] + alpha * colorParts[1], + (1 - alpha) * parentColor[2] + alpha * colorParts[2], + // We always return a colour with full alpha. + 1 + ]; } }); -- 2.11.4.GIT