mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
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.
This commit is contained in:
parent
77d1c41502
commit
43aa3cbe44
@ -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
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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('<div style="word-wrap: break-word;"></div>');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("<div></div>");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;a<t.length;a++){if(n.ancestors("*").indexOf(t[a])!==-1){f=!0;break}if(t[a].ancestors("*").indexOf(n)!==-1){t[a]=n,f=!0;break}}f||t.push(n)}}},this),this._addWarnings(r,M.util.get_string("needsmorecontrast",n),t,!1),this.editor.get("text").length>1e3&&!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("<p>"+M.util.get_string("nowarnings",n)+"</p>"),r},_addWarnings:function(t,r,i,s){var o,u,a,f,l,c,h,p;if(i.length>0){o=e.Node.create("<p>"+r+"</p>"),u=e.Node.create('<ol class="accessibilitywarnings"></ol>'),a=0;for(a=0;a<i.length;a++)c=e.Node.create("<li></li>"),s?(f=i[a].getAttribute("src"),h=e.Node.create('<a href="#"><img src="'+f+'" /> '+f+"</a>")):(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('<a href="#">'+p+"</a>")),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('<div style="word-wrap: break-word;"></div>');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("<div></div>");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;a<t.length;a++){if(n.ancestors("*").indexOf(t[a])!==-1){f=!0;break}if(t[a].ancestors("*").indexOf(n)!==-1){t[a]=n,f=!0;break}}f||t.push(n)}}},this),this._addWarnings(r,M.util.get_string("needsmorecontrast",n),t,!1),this.editor.get("text").length>1e3&&!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("<p>"+M.util.get_string("nowarnings",n)+"</p>"),r},_addWarnings:function(t,r,i,s){var o,u,a,f,l,c,h,p;if(i.length>0){o=e.Node.create("<p>"+r+"</p>"),u=e.Node.create('<ol class="accessibilitywarnings"></ol>'),a=0;for(a=0;a<i.length;a++)c=e.Node.create("<li></li>"),s?(f=i[a].getAttribute("src"),h=e.Node.create('<a href="#"><img src="'+f+'" /> '+f+"</a>")):(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('<a href="#">'+p+"</a>")),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"]});
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user