Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE

This commit is contained in:
Razvan Maries 2019-04-28 00:51:21 +03:00
Родитель 0e926d34f6 a85880b516
Коммит 8ed4c0a1dd
49 изменённых файлов: 354 добавлений и 248 удалений

Просмотреть файл

@ -37,6 +37,8 @@
iframe.src = `data:text/html,<html><body>hey</body></html>`; iframe.src = `data:text/html,<html><body>hey</body></html>`;
iframe.onload = () => resolve(iframe.contentDocument); iframe.onload = () => resolve(iframe.contentDocument);
document.body.appendChild(iframe); document.body.appendChild(iframe);
document.body.offsetTop; // We rely on the a11y tree being created
// already, and it's created off layout.
}); });
} }
@ -118,7 +120,7 @@
]; ];
break; break;
default: default:
ok(false, "Unexpected amount of states"); ok(false, "Unexpected amount of states: " + JSON.stringify(anode.states));
} }
if (states) { if (states) {
for (let i = 0; i < states.length; i++) { for (let i = 0; i < states.length; i++) {

Просмотреть файл

@ -18,8 +18,9 @@
"chrome_settings_overrides": { "chrome_settings_overrides": {
"search_provider": { "search_provider": {
"name": "Prisjakt", "name": "Prisjakt",
"search_url": "https://www.prisjakt.nu/#rparams=ss={searchTerms}", "search_url": "https://www.prisjakt.nu/search",
"search_form": "https://www.prisjakt.nu/#rparams=ss={searchTerms}", "search_url_get_params": "search={searchTerms}",
"search_form": "https://www.prisjakt.nu/search?search={searchTerms}",
"suggest_url": "https://www.prisjakt.nu/plugins/opensearch/suggestions.php", "suggest_url": "https://www.prisjakt.nu/plugins/opensearch/suggestions.php",
"suggest_url_get_params": "search={searchTerms}" "suggest_url_get_params": "search={searchTerms}"
} }

Просмотреть файл

@ -56,10 +56,12 @@ class AccessibilityStartup {
this._supports.relations, this._supports.relations,
this._supports.snapshot, this._supports.snapshot,
this._supports.audit, this._supports.audit,
this._supports.hydration,
] = await Promise.all([ ] = await Promise.all([
this.target.actorHasMethod("accessible", "getRelations"), this.target.actorHasMethod("accessible", "getRelations"),
this.target.actorHasMethod("accessible", "snapshot"), this.target.actorHasMethod("accessible", "snapshot"),
this.target.actorHasMethod("accessible", "audit"), this.target.actorHasMethod("accessible", "audit"),
this.target.actorHasMethod("accessible", "hydrate"),
])); ]));
await this._accessibility.bootstrap(); await this._accessibility.bootstrap();

Просмотреть файл

@ -79,8 +79,12 @@ AccessibilityView.prototype = {
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED); window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
}, },
async selectNodeAccessible(walker, node) { async selectNodeAccessible(walker, node, supports) {
let accessible = await walker.getAccessibleFor(node); let accessible = await walker.getAccessibleFor(node);
if (accessible && supports.hydration) {
await accessible.hydrate();
}
// If node does not have an accessible object, try to find node's child text node and // If node does not have an accessible object, try to find node's child text node and
// try to retrieve an accessible object for that child instead. This is the best // try to retrieve an accessible object for that child instead. This is the best
// effort approach until there's accessibility API to retrieve accessible object at // effort approach until there's accessibility API to retrieve accessible object at
@ -90,7 +94,13 @@ AccessibilityView.prototype = {
for (const child of children) { for (const child of children) {
if (child.nodeType === nodeConstants.TEXT_NODE) { if (child.nodeType === nodeConstants.TEXT_NODE) {
accessible = await walker.getAccessibleFor(child); accessible = await walker.getAccessibleFor(child);
if (accessible && accessible.indexInParent >= 0) { // indexInParent property is only available with additional request
// for data (hydration) about the accessible object.
if (accessible && supports.hydration) {
await accessible.hydrate();
}
if (accessible.indexInParent >= 0) {
break; break;
} }
} }

Просмотреть файл

@ -17,5 +17,6 @@ exports.updateDetails = (domWalker, accessible, supports) =>
domWalker.getNodeFromActor(accessible.actorID, ["rawAccessible", "DOMNode"]), domWalker.getNodeFromActor(accessible.actorID, ["rawAccessible", "DOMNode"]),
supports.relations ? accessible.getRelations() : [], supports.relations ? accessible.getRelations() : [],
supports.audit ? accessible.audit() : {}, supports.audit ? accessible.audit() : {},
supports.hydration ? accessible.hydrate() : null,
]).then(response => dispatch({ accessible, type: UPDATE_DETAILS, response })) ]).then(response => dispatch({ accessible, type: UPDATE_DETAILS, response }))
.catch(error => dispatch({ accessible, type: UPDATE_DETAILS, error })); .catch(error => dispatch({ accessible, type: UPDATE_DETAILS, error }));

Просмотреть файл

@ -8,19 +8,12 @@ const React = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux"); const { connect } = require("devtools/client/shared/vendor/react-redux");
const { getContrastRatioScore } = require("./ColorContrastAccessibility");
const { isFiltered } = require("../utils/audit"); const { isFiltered } = require("../utils/audit");
const { FILTERS } = require("../constants"); const { FILTERS } = require("../constants");
const { accessibility: { AUDIT_TYPE, ColorContrastScores } } = const { accessibility: { AUDIT_TYPE, SCORES } } = require("devtools/shared/constants");
require("devtools/shared/constants");
function validateContrast({ error, value, min, isLargeText }) { function validateContrast({ error, score }) {
if (error) { return !error && score === SCORES.FAIL;
return false;
}
const score = getContrastRatioScore(value || min, isLargeText);
return score === ColorContrastScores.FAIL;
} }
const AUDIT_TYPE_TO_FILTER = { const AUDIT_TYPE_TO_FILTER = {

Просмотреть файл

@ -11,7 +11,6 @@ const LearnMoreLink = createFactory(require("./LearnMoreLink"));
const { A11Y_CONTRAST_LEARN_MORE_LINK } = require("../constants"); const { A11Y_CONTRAST_LEARN_MORE_LINK } = require("../constants");
const { L10N } = require("../utils/l10n"); const { L10N } = require("../utils/l10n");
const { accessibility: { ColorContrastScores } } = require("devtools/shared/constants");
/** /**
* Component that renders a colour contrast value along with a swatch preview of what the * Component that renders a colour contrast value along with a swatch preview of what the
@ -22,8 +21,8 @@ class ContrastValueClass extends Component {
return { return {
backgroundColor: PropTypes.array.isRequired, backgroundColor: PropTypes.array.isRequired,
color: PropTypes.array.isRequired, color: PropTypes.array.isRequired,
isLargeText: PropTypes.bool.isRequired,
value: PropTypes.number.isRequired, value: PropTypes.number.isRequired,
score: PropTypes.string,
}; };
} }
@ -31,13 +30,13 @@ class ContrastValueClass extends Component {
const { const {
backgroundColor, backgroundColor,
color, color,
isLargeText,
value, value,
score,
} = this.props; } = this.props;
const className = [ const className = [
"accessibility-contrast-value", "accessibility-contrast-value",
getContrastRatioScore(value, isLargeText), score,
].join(" "); ].join(" ");
return ( return (
@ -71,6 +70,9 @@ class ColorContrastAccessibilityClass extends Component {
backgroundColor: PropTypes.array, backgroundColor: PropTypes.array,
backgroundColorMin: PropTypes.array, backgroundColorMin: PropTypes.array,
backgroundColorMax: PropTypes.array, backgroundColorMax: PropTypes.array,
score: PropTypes.string,
scoreMin: PropTypes.string,
scoreMax: PropTypes.string,
}; };
} }
@ -79,9 +81,9 @@ class ColorContrastAccessibilityClass extends Component {
error, error,
isLargeText, isLargeText,
color, color,
value, backgroundColor, value, backgroundColor, score,
min, backgroundColorMin, min, backgroundColorMin, scoreMin,
max, backgroundColorMax, max, backgroundColorMax, scoreMax,
} = this.props; } = this.props;
const children = []; const children = [];
@ -99,17 +101,17 @@ class ColorContrastAccessibilityClass extends Component {
} }
if (value) { if (value) {
children.push(ContrastValue({ isLargeText, color, backgroundColor, value })); children.push(ContrastValue({ score, color, backgroundColor, value }));
} else { } else {
children.push( children.push(
ContrastValue( ContrastValue(
{ isLargeText, color, backgroundColor: backgroundColorMin, value: min }), { score: scoreMin, color, backgroundColor: backgroundColorMin, value: min }),
div({ div({
role: "presentation", role: "presentation",
className: "accessibility-color-contrast-separator", className: "accessibility-color-contrast-separator",
}), }),
ContrastValue( ContrastValue(
{ isLargeText, color, backgroundColor: backgroundColorMax, value: max }), { score: scoreMax, color, backgroundColor: backgroundColorMax, value: max }),
); );
} }
@ -140,15 +142,12 @@ const ColorContrastAccessibility = createFactory(ColorContrastAccessibilityClass
class ContrastAnnotationClass extends Component { class ContrastAnnotationClass extends Component {
static get propTypes() { static get propTypes() {
return { return {
isLargeText: PropTypes.bool.isRequired, score: PropTypes.string,
value: PropTypes.number,
min: PropTypes.number,
}; };
} }
render() { render() {
const { isLargeText, min, value } = this.props; const { score } = this.props;
const score = getContrastRatioScore(value || min, isLargeText);
return ( return (
LearnMoreLink( LearnMoreLink(
@ -191,31 +190,7 @@ class ColorContrastCheck extends Component {
} }
} }
/**
* Get contrast ratio score.
* ratio.
* @param {Number} value
* Value of the contrast ratio for a given accessible object.
* @param {Boolean} isLargeText
* True if the accessible object contains large text.
* @return {String}
* Represents the appropriate contrast ratio score.
*/
function getContrastRatioScore(value, isLargeText) {
const levels = isLargeText ? { AA: 3, AAA: 4.5 } : { AA: 4.5, AAA: 7 };
let score = ColorContrastScores.FAIL;
if (value >= levels.AAA) {
score = ColorContrastScores.AAA;
} else if (value >= levels.AA) {
score = ColorContrastScores.AA;
}
return score;
}
module.exports = { module.exports = {
ColorContrastAccessibility: ColorContrastAccessibilityClass, ColorContrastAccessibility: ColorContrastAccessibilityClass,
ColorContrastCheck, ColorContrastCheck,
getContrastRatioScore,
}; };

Просмотреть файл

@ -9,8 +9,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { L10N } = require("../utils/l10n"); const { L10N } = require("../utils/l10n");
const { getContrastRatioScore } = require("./ColorContrastAccessibility"); const { accessibility: { SCORES } } = require("devtools/shared/constants");
const { accessibility: { ColorContrastScores } } = require("devtools/shared/constants");
loader.lazyGetter(this, "Badge", () => createFactory(require("./Badge"))); loader.lazyGetter(this, "Badge", () => createFactory(require("./Badge")));
@ -26,21 +25,18 @@ class ContrastBadge extends Component {
static get propTypes() { static get propTypes() {
return { return {
error: PropTypes.string, error: PropTypes.string,
isLargeText: PropTypes.bool.isRequired, score: PropTypes.string,
value: PropTypes.number,
min: PropTypes.number,
walker: PropTypes.object.isRequired, walker: PropTypes.object.isRequired,
}; };
} }
render() { render() {
const { error, value, min, isLargeText, walker } = this.props; const { error, score, walker } = this.props;
if (error) { if (error) {
return null; return null;
} }
const score = getContrastRatioScore(value || min, isLargeText); if (score !== SCORES.FAIL) {
if (score !== ColorContrastScores.FAIL) {
return null; return null;
} }

Просмотреть файл

@ -154,7 +154,8 @@ AccessibilityPanel.prototype = {
"devtools.accessibility.select_accessible_for_node", reason, 1); "devtools.accessibility.select_accessible_for_node", reason, 1);
} }
this.postContentMessage("selectNodeAccessible", this.walker, nodeFront); this.postContentMessage("selectNodeAccessible", this.walker, nodeFront,
this.supports);
}, },
highlightAccessible(accessibleFront) { highlightAccessible(accessibleFront) {

Просмотреть файл

@ -43,6 +43,7 @@ const tests = [{
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}, },
}, },
}, },
@ -59,6 +60,7 @@ const tests = [{
"color": [0, 0, 255, 1], "color": [0, 0, 255, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "AAA",
}, },
}, },
}, },

Просмотреть файл

@ -62,6 +62,7 @@ describe("AuditController component:", () => {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "AA",
}, },
}, },
}, span()))); }, span())));
@ -79,6 +80,7 @@ describe("AuditController component:", () => {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}; };
const wrapper = mount(Provider({store}, AuditFilter({ const wrapper = mount(Provider({store}, AuditFilter({
@ -102,6 +104,7 @@ describe("AuditController component:", () => {
"backgroundColorMin": [219, 106, 116, 1], "backgroundColorMin": [219, 106, 116, 1],
"backgroundColorMax": [156, 145, 211, 1], "backgroundColorMax": [156, 145, 211, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}; };
const wrapper = mount(Provider({store}, AuditFilter({ const wrapper = mount(Provider({store}, AuditFilter({

Просмотреть файл

@ -51,6 +51,7 @@ describe("Badges component:", () => {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "AA",
}, },
}, },
}))); })));
@ -64,6 +65,7 @@ describe("Badges component:", () => {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}; };
const wrapper = mount(Provider({ store }, Badges({ checks: { CONTRAST }}))); const wrapper = mount(Provider({ store }, Badges({ checks: { CONTRAST }})));
@ -81,6 +83,7 @@ describe("Badges component:", () => {
"backgroundColorMin": [219, 106, 116, 1], "backgroundColorMin": [219, 106, 116, 1],
"backgroundColorMax": [156, 145, 211, 1], "backgroundColorMax": [156, 145, 211, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}; };
const wrapper = mount(Provider({ store }, Badges({ checks: { CONTRAST }}))); const wrapper = mount(Provider({ store }, Badges({ checks: { CONTRAST }})));

Просмотреть файл

@ -29,6 +29,7 @@ describe("ContrastBadge component:", () => {
const wrapper = shallow(ContrastBadge({ const wrapper = shallow(ContrastBadge({
value: 5.11, value: 5.11,
isLargeText: false, isLargeText: false,
score: "AA",
})); }));
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.isEmptyRender()).toBe(true); expect(wrapper.isEmptyRender()).toBe(true);
@ -39,6 +40,7 @@ describe("ContrastBadge component:", () => {
min: 5.11, min: 5.11,
max: 6.25, max: 6.25,
isLargeText: false, isLargeText: false,
score: "AA",
})); }));
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.isEmptyRender()).toBe(true); expect(wrapper.isEmptyRender()).toBe(true);
@ -48,6 +50,7 @@ describe("ContrastBadge component:", () => {
const wrapper = shallow(ContrastBadge({ const wrapper = shallow(ContrastBadge({
value: 3.77, value: 3.77,
isLargeText: true, isLargeText: true,
score: "AA",
})); }));
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.isEmptyRender()).toBe(true); expect(wrapper.isEmptyRender()).toBe(true);
@ -57,6 +60,7 @@ describe("ContrastBadge component:", () => {
const wrapper = mount(Provider({ store }, ContrastBadge({ const wrapper = mount(Provider({ store }, ContrastBadge({
value: 3.77, value: 3.77,
isLargeText: false, isLargeText: false,
score: "fail",
}))); })));
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();

Просмотреть файл

@ -41,6 +41,7 @@ window.onload = async function() {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
}) })
); );
@ -52,6 +53,9 @@ window.onload = async function() {
"backgroundColorMin": [219, 106, 116, 1], "backgroundColorMin": [219, 106, 116, 1],
"backgroundColorMax": [156, 145, 211, 1], "backgroundColorMax": [156, 145, 211, 1],
"isLargeText": false, "isLargeText": false,
"score": "fail",
"scoreMin": "fail",
"scoreMax": "fail",
}) })
); );
@ -61,6 +65,7 @@ window.onload = async function() {
"color": [255, 0, 0, 1], "color": [255, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": true, "isLargeText": true,
"score": "AA",
}) })
); );
} catch (e) { } catch (e) {

Просмотреть файл

@ -150,7 +150,7 @@ async function testClickInInnerIframe(doc) {
const onFrameLoad = new Promise(r => { const onFrameLoad = new Promise(r => {
iframe.addEventListener("load", r, true); iframe.addEventListener("load", r, true);
}); });
iframe.src = "data:text/html,<div id=test style='height:50px;'></div>"; iframe.srcdoc = "<div id=test style='height:50px;'></div>";
await onFrameLoad; await onFrameLoad;
tooltip.setContentSize({width: 100, height: 50}); tooltip.setContentSize({width: 100, height: 50});

Просмотреть файл

@ -366,16 +366,29 @@ const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
actor: this.actorID, actor: this.actorID,
role: this.role, role: this.role,
name: this.name, name: this.name,
childCount: this.childCount,
checks: this._lastAudit,
};
},
/**
* Provide additional (full) information about the accessible object that is
* otherwise missing from the form.
*
* @return {Object}
* Object that contains accessible object information such as states,
* actions, attributes, etc.
*/
hydrate() {
return {
value: this.value, value: this.value,
description: this.description, description: this.description,
keyboardShortcut: this.keyboardShortcut, keyboardShortcut: this.keyboardShortcut,
childCount: this.childCount,
domNodeType: this.domNodeType, domNodeType: this.domNodeType,
indexInParent: this.indexInParent, indexInParent: this.indexInParent,
states: this.states, states: this.states,
actions: this.actions, actions: this.actions,
attributes: this.attributes, attributes: this.attributes,
checks: this._lastAudit,
}; };
}, },

Просмотреть файл

@ -11,6 +11,7 @@ loader.lazyRequireGetter(this, "getCurrentZoom", "devtools/shared/layout/utils",
loader.lazyRequireGetter(this, "addPseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true); loader.lazyRequireGetter(this, "addPseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true);
loader.lazyRequireGetter(this, "removePseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true); loader.lazyRequireGetter(this, "removePseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true);
loader.lazyRequireGetter(this, "DevToolsWorker", "devtools/shared/worker/worker", true); loader.lazyRequireGetter(this, "DevToolsWorker", "devtools/shared/worker/worker", true);
loader.lazyRequireGetter(this, "accessibility", "devtools/shared/constants", true);
const WORKER_URL = "resource://devtools/server/actors/accessibility/worker.js"; const WORKER_URL = "resource://devtools/server/actors/accessibility/worker.js";
const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted"; const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
@ -109,6 +110,29 @@ function getImageCtx(win, bounds, zoom, scale, node) {
return ctx; return ctx;
} }
/**
* Get contrast ratio score based on WCAG criteria.
* @param {Number} ratio
* Value of the contrast ratio for a given accessible object.
* @param {Boolean} isLargeText
* True if the accessible object contains large text.
* @return {String}
* Value that represents calculated contrast ratio score.
*/
function getContrastRatioScore(ratio, isLargeText) {
const { SCORES: { FAIL, AA, AAA } } = accessibility;
const levels = isLargeText ? { AA: 3, AAA: 4.5 } : { AA: 4.5, AAA: 7 };
let score = FAIL;
if (ratio >= levels.AAA) {
score = AAA;
} else if (ratio >= levels.AA) {
score = AA;
}
return score;
}
/** /**
* Calculates the contrast ratio of the referenced DOM node. * Calculates the contrast ratio of the referenced DOM node.
* *
@ -177,11 +201,13 @@ async function getContrastRatioFor(node, options = {}) {
} }
if (rgba.value) { if (rgba.value) {
const value = colorUtils.calculateContrastRatio(rgba.value, color);
return { return {
value: colorUtils.calculateContrastRatio(rgba.value, color), value,
color, color,
backgroundColor: rgba.value, backgroundColor: rgba.value,
isLargeText, isLargeText,
score: getContrastRatioScore(value, isLargeText),
}; };
} }
@ -194,6 +220,8 @@ async function getContrastRatioFor(node, options = {}) {
[rgba.min, rgba.max] = [rgba.max, rgba.min]; [rgba.min, rgba.max] = [rgba.max, rgba.min];
} }
const score = getContrastRatioScore(min, isLargeText);
return { return {
min, min,
max, max,
@ -201,6 +229,9 @@ async function getContrastRatioFor(node, options = {}) {
backgroundColorMin: rgba.min, backgroundColorMin: rgba.min,
backgroundColorMax: rgba.max, backgroundColorMax: rgba.max,
isLargeText, isLargeText,
score,
scoreMin: score,
scoreMax: getContrastRatioScore(max, isLargeText),
}; };
} }

Просмотреть файл

@ -22,6 +22,7 @@ loader.lazyRequireGetter(this, "isXUL", "devtools/server/actors/highlighters/uti
loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true); loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "register", "devtools/server/actors/highlighters", true); loader.lazyRequireGetter(this, "register", "devtools/server/actors/highlighters", true);
loader.lazyRequireGetter(this, "removeSheet", "devtools/shared/layout/utils", true); loader.lazyRequireGetter(this, "removeSheet", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "accessibility", "devtools/shared/constants", true);
const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER
@ -405,7 +406,10 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
const ancestries = []; const ancestries = [];
for (const [acc, audit] of report.entries()) { for (const [acc, audit] of report.entries()) {
if (audit && Object.values(audit).filter(check => check != null).length > 0) { // Filter out audits that have no failing checks.
if (audit &&
Object.values(audit).some(check => check != null && !check.error &&
check.score === accessibility.SCORES.FAIL)) {
ancestries.push(this.getAncestry(acc)); ancestries.push(this.getAncestry(acc));
} }
} }

Просмотреть файл

@ -9,7 +9,7 @@ const { getCurrentZoom, getViewportDimensions } = require("devtools/shared/layou
const { moveInfobar, createNode } = require("./markup"); const { moveInfobar, createNode } = require("./markup");
const { truncateString } = require("devtools/shared/inspector/utils"); const { truncateString } = require("devtools/shared/inspector/utils");
const { accessibility: { ColorContrastScores } } = require("devtools/shared/constants"); const { accessibility: { SCORES } } = require("devtools/shared/constants");
const STRINGS_URI = "devtools/shared/locales/accessibility.properties"; const STRINGS_URI = "devtools/shared/locales/accessibility.properties";
loader.lazyRequireGetter(this, "LocalizationHelper", "devtools/shared/l10n", true); loader.lazyRequireGetter(this, "LocalizationHelper", "devtools/shared/l10n", true);
@ -527,11 +527,10 @@ class ContrastRatio extends AuditReport {
}); });
} }
_fillAndStyleContrastValue(el, { value, isLargeText, color, backgroundColor }) { _fillAndStyleContrastValue(el, { value, className, color, backgroundColor }) {
value = value.toFixed(2); value = value.toFixed(2);
const style = getContrastRatioScoreStyle(value, isLargeText);
this.setTextContent(el, value); this.setTextContent(el, value);
el.classList.add(style); el.classList.add(className);
el.setAttribute("style", el.setAttribute("style",
`--accessibility-highlighter-contrast-ratio-color: rgba(${color});` + `--accessibility-highlighter-contrast-ratio-color: rgba(${color});` +
`--accessibility-highlighter-contrast-ratio-bg: rgba(${backgroundColor});`); `--accessibility-highlighter-contrast-ratio-bg: rgba(${backgroundColor});`);
@ -551,7 +550,7 @@ class ContrastRatio extends AuditReport {
for (const key of ["label", "min", "max", "error", "separator"]) { for (const key of ["label", "min", "max", "error", "separator"]) {
const el = els[key] = this.getElement(`contrast-ratio-${key}`); const el = els[key] = this.getElement(`contrast-ratio-${key}`);
if (["min", "max"].includes(key)) { if (["min", "max"].includes(key)) {
Object.values(ColorContrastScores).forEach( Object.values(SCORES).forEach(
className => el.classList.remove(className)); className => el.classList.remove(className));
this.setTextContent(el, ""); this.setTextContent(el, "");
} }
@ -574,18 +573,20 @@ class ContrastRatio extends AuditReport {
} }
if (contrastRatio.value) { if (contrastRatio.value) {
const { value, color, backgroundColor } = contrastRatio; const { value, color, score, backgroundColor } = contrastRatio;
this._fillAndStyleContrastValue(els.min, this._fillAndStyleContrastValue(els.min,
{ value, isLargeText, color, backgroundColor }); { value, className: score, color, backgroundColor });
return true; return true;
} }
const { min, max, color, backgroundColorMin, backgroundColorMax } = contrastRatio; const {
min, max, color, backgroundColorMin, backgroundColorMax, scoreMin, scoreMax,
} = contrastRatio;
this._fillAndStyleContrastValue(els.min, this._fillAndStyleContrastValue(els.min,
{ value: min, isLargeText, color, backgroundColor: backgroundColorMin }); { value: min, className: scoreMin, color, backgroundColor: backgroundColorMin });
els.separator.removeAttribute("hidden"); els.separator.removeAttribute("hidden");
this._fillAndStyleContrastValue(els.max, this._fillAndStyleContrastValue(els.max,
{ value: max, isLargeText, color, backgroundColor: backgroundColorMax }); { value: max, className: scoreMax, color, backgroundColor: backgroundColorMax });
return true; return true;
} }
@ -646,29 +647,6 @@ function getBounds(win, { x, y, w, h, zoom }) {
return { left, right, top, bottom, width, height }; return { left, right, top, bottom, width, height };
} }
/**
* Get contrast ratio score styling to be applied on the element that renders the contrast
* ratio.
* @param {Number} ratio
* Value of the contrast ratio for a given accessible object.
* @param {Boolean} isLargeText
* True if the accessible object contains large text.
* @return {String}
* CSS class that represents the appropriate contrast ratio score styling.
*/
function getContrastRatioScoreStyle(ratio, isLargeText) {
const levels = isLargeText ? { AA: 3, AAA: 4.5 } : { AA: 4.5, AAA: 7 };
let style = ColorContrastScores.FAIL;
if (ratio >= levels.AAA) {
style = ColorContrastScores.AAA;
} else if (ratio >= levels.AA) {
style = ColorContrastScores.AA;
}
return style;
}
exports.MAX_STRING_LENGTH = MAX_STRING_LENGTH; exports.MAX_STRING_LENGTH = MAX_STRING_LENGTH;
exports.getBounds = getBounds; exports.getBounds = getBounds;
exports.Infobar = Infobar; exports.Infobar = Infobar;

Просмотреть файл

@ -16,6 +16,14 @@ add_task(async function() {
const buttonNode = await walker.querySelector(walker.rootNode, "#button"); const buttonNode = await walker.querySelector(walker.rootNode, "#button");
const accessibleFront = await a11yWalker.getAccessibleFor(buttonNode); const accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
checkA11yFront(accessibleFront, {
name: "Accessible Button",
role: "pushbutton",
childCount: 1,
});
await accessibleFront.hydrate();
checkA11yFront(accessibleFront, { checkA11yFront(accessibleFront, {
name: "Accessible Button", name: "Accessible Button",
role: "pushbutton", role: "pushbutton",

Просмотреть файл

@ -37,6 +37,7 @@ add_task(async function() {
"color": [0, 0, 0, 1], "color": [0, 0, 0, 1],
"backgroundColor": [255, 255, 255, 1], "backgroundColor": [255, 255, 255, 1],
"isLargeText": true, "isLargeText": true,
score: "AAA",
}, },
}); });

Просмотреть файл

@ -21,6 +21,14 @@ add_task(async function() {
const accessibleSliderFront = await a11yWalker.getAccessibleFor(sliderNode); const accessibleSliderFront = await a11yWalker.getAccessibleFor(sliderNode);
const browser = gBrowser.selectedBrowser; const browser = gBrowser.selectedBrowser;
checkA11yFront(accessibleFront, {
name: "Accessible Button",
role: "pushbutton",
childCount: 1,
});
await accessibleFront.hydrate();
checkA11yFront(accessibleFront, { checkA11yFront(accessibleFront, {
name: "Accessible Button", name: "Accessible Button",
role: "pushbutton", role: "pushbutton",
@ -92,6 +100,7 @@ add_task(async function() {
content.document.getElementById("button").setAttribute("aria-live", "polite"))); content.document.getElementById("button").setAttribute("aria-live", "polite")));
info("Value change event"); info("Value change event");
await accessibleSliderFront.hydrate();
checkA11yFront(accessibleSliderFront, { value: "5" }); checkA11yFront(accessibleSliderFront, { value: "5" });
await emitA11yEvent(accessibleSliderFront, "value-change", await emitA11yEvent(accessibleSliderFront, "value-change",
() => checkA11yFront(accessibleSliderFront, { value: "6" }), () => checkA11yFront(accessibleSliderFront, { value: "6" }),
@ -101,6 +110,7 @@ add_task(async function() {
info("Reorder event"); info("Reorder event");
is(accessibleSliderFront.childCount, 1, "Slider has only 1 child"); is(accessibleSliderFront.childCount, 1, "Slider has only 1 child");
const [firstChild ] = await accessibleSliderFront.children(); const [firstChild ] = await accessibleSliderFront.children();
await firstChild.hydrate();
is(firstChild.indexInParent, 0, "Slider's first child has correct index in parent"); is(firstChild.indexInParent, 0, "Slider's first child has correct index in parent");
await emitA11yEvent(accessibleSliderFront, "reorder", await emitA11yEvent(accessibleSliderFront, "reorder",
childCount => { childCount => {

Просмотреть файл

@ -12,27 +12,7 @@ add_task(async function() {
const accessibles = [{ const accessibles = [{
name: "", name: "",
role: "document", role: "document",
value: "",
description: "",
keyboardShortcut: "",
childCount: 2, childCount: 2,
domNodeType: 9,
indexInParent: 0,
states: [
"focused", "readonly", "focusable", "active", "opaque", "enabled", "sensitive",
],
actions: [],
attributes: {
display: "block",
"explicit-name": "true",
"margin-bottom": "8px",
"margin-left": "8px",
"margin-right": "8px",
"margin-top": "8px",
tag: "body",
"text-align": "start",
"text-indent": "0px",
},
checks: { checks: {
"CONTRAST": null, "CONTRAST": null,
}, },
@ -40,27 +20,7 @@ add_task(async function() {
name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
"eiusmod tempor incididunt ut labore et dolore magna aliqua.", "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
role: "heading", role: "heading",
value: "",
description: "",
keyboardShortcut: "",
childCount: 1, childCount: 1,
domNodeType: 1,
indexInParent: 0,
states: [ "selectable text", "opaque", "enabled", "sensitive" ],
actions: [],
attributes: {
display: "block",
formatting: "block",
id: "h1",
level: "1",
"margin-bottom": "21.4333px",
"margin-left": "0px",
"margin-right": "0px",
"margin-top": "21.4333px",
tag: "h1",
"text-align": "start",
"text-indent": "0px",
},
checks: { checks: {
"CONTRAST": null, "CONTRAST": null,
}, },
@ -68,15 +28,7 @@ add_task(async function() {
name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
"eiusmod tempor incididunt ut labore et dolore magna aliqua.", "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
role: "text leaf", role: "text leaf",
value: "",
description: "",
keyboardShortcut: "",
childCount: 0, childCount: 0,
domNodeType: 3,
indexInParent: 0,
states: [ "opaque", "enabled", "sensitive" ],
actions: [],
attributes: { "explicit-name": "true" },
checks: { checks: {
"CONTRAST": { "CONTRAST": {
"value": 21, "value": 21,
@ -88,41 +40,14 @@ add_task(async function() {
}, { }, {
name: "", name: "",
role: "paragraph", role: "paragraph",
value: "",
description: "",
keyboardShortcut: "",
childCount: 1, childCount: 1,
domNodeType: 1,
indexInParent: 1,
states: [ "selectable text", "opaque", "enabled", "sensitive" ],
actions: [ "Press" ],
attributes: {
display: "block",
formatting: "block",
id: "p",
"margin-bottom": "16px",
"margin-left": "0px",
"margin-right": "0px",
"margin-top": "16px",
tag: "p",
"text-align": "start",
"text-indent": "0px",
},
checks: { checks: {
"CONTRAST": null, "CONTRAST": null,
}, },
}, { }, {
name: "Accessible Paragraph", name: "Accessible Paragraph",
role: "text leaf", role: "text leaf",
value: "",
description: "",
keyboardShortcut: "",
childCount: 0, childCount: 0,
domNodeType: 3,
indexInParent: 0,
states: [ "opaque", "enabled", "sensitive" ],
actions: [],
attributes: { "explicit-name": "true" },
checks: { checks: {
"CONTRAST": { "CONTRAST": {
"value": 21, "value": 21,

Просмотреть файл

@ -17,7 +17,7 @@ exports.accessibility = {
}, },
// Constants associated with WCAG guidelines score system: // Constants associated with WCAG guidelines score system:
// failing -> AA -> AAA; // failing -> AA -> AAA;
ColorContrastScores: { SCORES: {
FAIL: "fail", FAIL: "fail",
AA: "AA", AA: "AA",
AAA: "AAA", AAA: "AAA",

Просмотреть файл

@ -85,7 +85,8 @@ class AccessibleFront extends FrontClassWithSpec(accessibleSpec) {
form(form) { form(form) {
this.actorID = form.actor; this.actorID = form.actor;
this._form = form; this._form = this._form || {};
Object.assign(this._form, form);
} }
nameChange(name, parent, walker) { nameChange(name, parent, walker) {
@ -145,6 +146,12 @@ class AccessibleFront extends FrontClassWithSpec(accessibleSpec) {
audited(checks) { audited(checks) {
this._form.checks = checks; this._form.checks = checks;
} }
hydrate() {
return super.hydrate().then(properties => {
Object.assign(this._form, properties);
});
}
} }
class AccessibleWalkerFront extends FrontClassWithSpec(accessibleWalkerSpec) { class AccessibleWalkerFront extends FrontClassWithSpec(accessibleWalkerSpec) {

Просмотреть файл

@ -102,6 +102,12 @@ const accessibleSpec = generateActorSpec({
relations: RetVal("array:accessibleRelation"), relations: RetVal("array:accessibleRelation"),
}, },
}, },
hydrate: {
request: {},
response: {
properties: RetVal("json"),
},
},
snapshot: { snapshot: {
request: {}, request: {},
response: { response: {

Просмотреть файл

@ -117,6 +117,9 @@
l3.detach(); l3.detach();
l4.detach(); l4.detach();
// swapDocShells reflows asynchronously, ensure layout is
// clean so that the viewport of f1 is the right size.
$("f1").getBoundingClientRect();
var s1_new = snapshotWindow($("f1").contentWindow); var s1_new = snapshotWindow($("f1").contentWindow);
var [same, first, second] = compareSnapshots(s1_new, s2, true); var [same, first, second] = compareSnapshots(s1_new, s2, true);
ok(same, "Should reflow on swap. Expected " + second + " but got " + first); ok(same, "Should reflow on swap. Expected " + second + " but got " + first);

Просмотреть файл

@ -7,14 +7,29 @@
<script src="/tests/SimpleTest/EventUtils.js"></script> <script src="/tests/SimpleTest/EventUtils.js"></script>
</head> </head>
<script type="application/javascript"> <script type="application/javascript">
function navigateAway() {
// Anchor clicks are only handled if we have a pres-context, and we may not
// have one yet by the time this runs, and getBoundingClientRect() won't
// construct it after bug 1440537.
//
// So we may need to wait a few frames to make this reliable.
//
// FIXME(emilio, bug 1218456): This dance shouldn't be needed.
let anchor = document.getElementById("anchor");
if (anchor.getBoundingClientRect().width > 0)
anchor.click();
else
requestAnimationFrame(navigateAway);
}
function doTest() { function doTest() {
// this should fail the first time, but work the second
try { try {
// this should fail the first time, but work the second
window.parent.ok_wrapper(true, "a document that was loaded, navigated to another document, had 'allow-same-origin' added and then was" + window.parent.ok_wrapper(true, "a document that was loaded, navigated to another document, had 'allow-same-origin' added and then was" +
" navigated back should be same-origin with its parent"); " navigated back should be same-origin with its parent");
} } catch (e) {
catch (e) { navigateAway();
sendMouseEvent({type:'click'}, 'anchor');
} }
} }

Просмотреть файл

@ -19,11 +19,13 @@
#include "mozilla/SVGContextPaint.h" #include "mozilla/SVGContextPaint.h"
#include "mozilla/TextUtils.h" #include "mozilla/TextUtils.h"
#include "nsComputedDOMStyle.h" #include "nsComputedDOMStyle.h"
#include "nsContainerFrame.h"
#include "nsFontMetrics.h" #include "nsFontMetrics.h"
#include "nsIFrame.h" #include "nsIFrame.h"
#include "nsIScriptError.h" #include "nsIScriptError.h"
#include "nsLayoutUtils.h" #include "nsLayoutUtils.h"
#include "nsMathUtils.h" #include "nsMathUtils.h"
#include "nsSVGUtils.h"
#include "nsWhitespaceTokenizer.h" #include "nsWhitespaceTokenizer.h"
#include "SVGAnimationElement.h" #include "SVGAnimationElement.h"
#include "SVGAnimatedPreserveAspectRatio.h" #include "SVGAnimatedPreserveAspectRatio.h"
@ -466,15 +468,37 @@ SVGViewportElement* SVGContentUtils::GetNearestViewportElement(
static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM, static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM,
bool aHaveRecursed) { bool aHaveRecursed) {
gfxMatrix matrix = aElement->PrependLocalTransformsTo( auto getLocalTransformHelper =
gfxMatrix(), aHaveRecursed ? eAllTransforms : eUserSpaceToParent); [](SVGElement const* e, bool shouldIncludeChildToUserSpace) -> gfxMatrix {
gfxMatrix ret;
if (auto* f = e->GetPrimaryFrame()) {
ret = nsSVGUtils::GetTransformMatrixInUserSpace(f);
} else {
// FIXME: Ideally we should also return the correct matrix
// for display:none, but currently transform related code relies
// heavily on the present of a frame.
// For now we just fall back to |PrependLocalTransformsTo| which
// doesn't account for CSS transform.
ret = e->PrependLocalTransformsTo({}, eUserSpaceToParent);
}
if (shouldIncludeChildToUserSpace) {
ret = e->PrependLocalTransformsTo({}, eChildToUserSpace) * ret;
}
return ret;
};
gfxMatrix matrix = getLocalTransformHelper(aElement, aHaveRecursed);
SVGElement* element = aElement; SVGElement* element = aElement;
nsIContent* ancestor = aElement->GetFlattenedTreeParent(); nsIContent* ancestor = aElement->GetFlattenedTreeParent();
while (ancestor && ancestor->IsSVGElement() && while (ancestor && ancestor->IsSVGElement() &&
!ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) {
element = static_cast<SVGElement*>(ancestor); element = static_cast<SVGElement*>(ancestor);
matrix *= element->PrependLocalTransformsTo(gfxMatrix()); // i.e. *A*ppend matrix *= getLocalTransformHelper(element, true);
if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) { if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) {
if (!element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG) && if (!element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG) &&
!element->NodeInfo()->Equals(nsGkAtoms::symbol, kNameSpaceID_SVG)) { !element->NodeInfo()->Equals(nsGkAtoms::symbol, kNameSpaceID_SVG)) {
@ -501,7 +525,7 @@ static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM,
// transforms in this case since that's what we've been doing for // transforms in this case since that's what we've been doing for
// a while, and it keeps us consistent with WebKit and Opera (if not // a while, and it keeps us consistent with WebKit and Opera (if not
// really with the ambiguous spec). // really with the ambiguous spec).
matrix = aElement->PrependLocalTransformsTo(gfxMatrix()); matrix = getLocalTransformHelper(aElement, true);
} }
if (auto* f = element->GetPrimaryFrame()) { if (auto* f = element->GetPrimaryFrame()) {

Просмотреть файл

@ -9,6 +9,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=366697
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style> <style>
#padsvg1 { padding-left: 27px; padding-top: 43px; } #padsvg1 { padding-left: 27px; padding-top: 43px; }
#transrect1 { transform: scale(2,3); }
</style> </style>
</head> </head>
<body> <body>
@ -19,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=366697
<iframe id="svg" src="getCTM-helper.svg"></iframe> <iframe id="svg" src="getCTM-helper.svg"></iframe>
<svg id="padsvg1" width="100" height="100"> <svg id="padsvg1" width="100" height="100">
<rect width="10" height="10" /> <rect id="transrect1" width="10" height="10" />
</svg> </svg>
<pre id="test"> <pre id="test">
@ -34,6 +35,10 @@ function runTest() {
is(buggy.getCTM().e, 30, "buggy.getCTM().e"); is(buggy.getCTM().e, 30, "buggy.getCTM().e");
is(buggy.getCTM().f, 40, "buggy.getCTM().f"); is(buggy.getCTM().f, 40, "buggy.getCTM().f");
var transrect1 = document.getElementById("transrect1");
is(transrect1.getCTM().a, 2, "transrect1.getCTM().a");
is(transrect1.getCTM().d, 3, "transrect1.getCTM().d");
var padsvg1 = document.getElementById("padsvg1"); var padsvg1 = document.getElementById("padsvg1");
is(padsvg1.getScreenCTM().e - padsvg1.getBoundingClientRect().x, 27, "padsvg1.getScreenCTM().e"); is(padsvg1.getScreenCTM().e - padsvg1.getBoundingClientRect().x, 27, "padsvg1.getScreenCTM().e");
is(padsvg1.getScreenCTM().f - padsvg1.getBoundingClientRect().y, 43, "padsvg1.getScreenCTM().f"); is(padsvg1.getScreenCTM().f - padsvg1.getBoundingClientRect().y, 43, "padsvg1.getScreenCTM().f");

Просмотреть файл

@ -0,0 +1,3 @@
<!doctype html>
<img src="data:image/svg+xml,<svg width='90px' xmlns='http://www.w3.org/2000/svg'></svg>" style="position: absolute;">
<img src="data:image/svg+xml,<svg height='90px' xmlns='http://www.w3.org/2000/svg'></svg>" style="position: absolute;">

Просмотреть файл

@ -568,4 +568,4 @@ pref(layout.css.column-span.enabled,true) load 1534146.html
pref(layout.css.column-span.enabled,true) load 1539017.html pref(layout.css.column-span.enabled,true) load 1539017.html
load 1539303.html load 1539303.html
pref(layout.css.column-span.enabled,true) load 1541679.html pref(layout.css.column-span.enabled,true) load 1541679.html
load 1547261.html

Просмотреть файл

@ -2410,7 +2410,7 @@ nsIFrame::LogicalSides nsImageFrame::GetLogicalSkipSides(
} }
nsresult nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) { nsresult nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) {
if (mIntrinsicSize.width && mIntrinsicSize.width) { if (mIntrinsicSize.width && mIntrinsicSize.height) {
aSize.SizeTo(*mIntrinsicSize.width, *mIntrinsicSize.height); aSize.SizeTo(*mIntrinsicSize.width, *mIntrinsicSize.height);
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -1,7 +1,8 @@
<svg style="width: 500px; height: 500px; border: 1px solid green;"> <svg style="width: 500px; height: 500px; border: 1px solid green;">
<defs> <defs>
<mask id="mask"> <mask id="mask">
<rect id="square" x="200px" y="250px" width="100px" height="150px" fill="#ffffff" /> <rect x="200px" y="250px" width="100px" height="150px" fill="#ffffff" />
<rect x="50px" y="105px" width="110px" height="195px" fill="blue" />
</mask> </mask>
</defs> </defs>
<rect mask="url(#mask)" width="500px" height="500px" fill="red" /> <rect mask="url(#mask)" width="500px" height="500px" fill="red" />

До

Ширина:  |  Высота:  |  Размер: 287 B

После

Ширина:  |  Высота:  |  Размер: 350 B

Просмотреть файл

@ -1,12 +1,18 @@
<style> <style>
#square{ #square1 {
transform: translate(100px, 100px) scale(2,3); transform: translate(100px, 100px) scale(2,3);
} }
#square2 {
transform: scale(2,3);
}
</style> </style>
<svg style="width: 500px; height: 500px; border: 1px solid green;"> <svg style="width: 500px; height: 500px; border: 1px solid green;">
<defs> <defs>
<mask id="mask"> <mask id="mask">
<rect id="square" x="50px" y="50px" width="50px" height="50px" fill="#ffffff" /> <rect id="square1" x="50px" y="50px" width="50px" height="50px" fill="#ffffff" />
<svg viewBox="0 0 100 100">
<rect id="square2" x="5" y="7" width="11" height="13" fill="blue" />
</svg>
</mask> </mask>
</defs> </defs>
<rect mask="url(#mask)" width="500px" height="500px" fill="red" /> <rect mask="url(#mask)" width="500px" height="500px" fill="red" />

Просмотреть файл

@ -156,11 +156,9 @@ function run() {
var sheeturl = "data:text/css," + escape(sheet); var sheeturl = "data:text/css," + escape(sheet);
var link = "<link rel='stylesheet' href='" + sheeturl + "'>"; var link = "<link rel='stylesheet' href='" + sheeturl + "'>";
var htmldoc = "<!DOCTYPE HTML>" + link + link + "<body>"; var htmldoc = "<!DOCTYPE HTML>" + link + link + "<body>";
var docurl = "data:text/html," + escape(htmldoc); post_clone_test(htmldoc, function() {
post_clone_test(docurl, function() { var clonedoc = iframe.contentDocument;
var wrappedFrame = SpecialPowers.wrap(iframe); var clonewin = iframe.contentWindow;
var clonedoc = wrappedFrame.contentDocument;
var clonewin = wrappedFrame.contentWindow;
var links = clonedoc.getElementsByTagName("link"); var links = clonedoc.getElementsByTagName("link");
// cause a clone // cause a clone
var clonedsheet = links[1].sheet; var clonedsheet = links[1].sheet;
@ -891,9 +889,9 @@ function change_state(func)
posted_items.push({state: func}); posted_items.push({state: func});
} }
function post_clone_test(docurl, testfunc) function post_clone_test(srcdoc, testfunc)
{ {
posted_items.push({docurl: docurl, testfunc: testfunc}); posted_items.push({srcdoc, testfunc});
} }
function handle_posted_items() function handle_posted_items()
@ -910,9 +908,9 @@ function handle_posted_items()
return; return;
} }
var docurl = posted_items[0].docurl; var srcdoc = posted_items[0].srcdoc;
iframe.onload = handle_iframe_onload; iframe.onload = handle_iframe_onload;
iframe.src = docurl; iframe.srcdoc = srcdoc;
} }
function handle_iframe_onload(event) function handle_iframe_onload(event)

Просмотреть файл

@ -59,7 +59,7 @@ void nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
static_cast<SVGGeometryElement*>(pathFrame->GetContent()); static_cast<SVGGeometryElement*>(pathFrame->GetContent());
gfxMatrix toChildsUserSpace = gfxMatrix toChildsUserSpace =
nsSVGUtils::GetTransformMatrixInUserSpace(pathFrame, this) * nsSVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
(GetClipPathTransform(aClippedFrame) * aMatrix); (GetClipPathTransform(aClippedFrame) * aMatrix);
gfxMatrix newMatrix = aContext.CurrentMatrixDouble() gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
@ -220,8 +220,7 @@ void nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
nsIContent* childContent = child->GetContent(); nsIContent* childContent = child->GetContent();
if (childContent->IsSVGElement()) { if (childContent->IsSVGElement()) {
toChildsUserSpace = toChildsUserSpace =
nsSVGUtils::GetTransformMatrixInUserSpace(child, child->GetParent()) * nsSVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
mMatrixForChildren;
} }
// clipPath does not result in any image rendering, so we just use a dummy // clipPath does not result in any image rendering, so we just use a dummy
@ -302,7 +301,7 @@ bool nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
if (SVGFrame) { if (SVGFrame) {
gfxPoint pointForChild = point; gfxPoint pointForChild = point;
gfxMatrix m = nsSVGUtils::GetTransformMatrixInUserSpace(kid, this); gfxMatrix m = nsSVGUtils::GetTransformMatrixInUserSpace(kid);
if (!m.IsIdentity()) { if (!m.IsIdentity()) {
if (!m.Invert()) { if (!m.Invert()) {
return false; return false;
@ -428,9 +427,8 @@ gfxMatrix nsSVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
gfxMatrix nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) { gfxMatrix nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent()); SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
gfxMatrix tm = gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) *
content->PrependLocalTransformsTo({}, eChildToUserSpace) * nsSVGUtils::GetTransformMatrixInUserSpace(this);
nsSVGUtils::GetTransformMatrixInUserSpace(this, this->GetParent());
SVGAnimatedEnumeration* clipPathUnits = SVGAnimatedEnumeration* clipPathUnits =
&content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
@ -464,7 +462,7 @@ SVGBBox nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
nsSVGDisplayableFrame* svg = do_QueryFrame(frame); nsSVGDisplayableFrame* svg = do_QueryFrame(frame);
if (svg) { if (svg) {
gfxMatrix matrix = gfxMatrix matrix =
nsSVGUtils::GetTransformMatrixInUserSpace(frame, this) * aMatrix; nsSVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(matrix), tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(matrix),
nsSVGUtils::eBBoxIncludeFill); nsSVGUtils::eBBoxIncludeFill);
nsSVGClipPathFrame* clipPathFrame; nsSVGClipPathFrame* clipPathFrame;

Просмотреть файл

@ -261,7 +261,7 @@ void nsSVGDisplayContainerFrame::PaintSVG(gfxContext& aContext,
continue; // nothing to paint for kid continue; // nothing to paint for kid
} }
m = nsSVGUtils::GetTransformMatrixInUserSpace(kid, this) * m; m = nsSVGUtils::GetTransformMatrixInUserSpace(kid) * m;
if (m.IsSingular()) { if (m.IsSingular()) {
continue; continue;
} }
@ -392,8 +392,7 @@ SVGBBox nsSVGDisplayContainerFrame::GetBBoxContribution(
if (content->IsSVGElement()) { if (content->IsSVGElement()) {
transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo( transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
{}, eChildToUserSpace) * {}, eChildToUserSpace) *
nsSVGUtils::GetTransformMatrixInUserSpace(kid, this) * nsSVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
transform;
} }
// We need to include zero width/height vertical/horizontal lines, so we // We need to include zero width/height vertical/horizontal lines, so we
// have to use UnionEdges. // have to use UnionEdges.

Просмотреть файл

@ -118,7 +118,7 @@ already_AddRefed<SourceSurface> nsSVGMaskFrame::GetMaskForMaskedFrame(
nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
if (SVGFrame) { if (SVGFrame) {
SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED); SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
m = nsSVGUtils::GetTransformMatrixInUserSpace(kid, this) * m; m = nsSVGUtils::GetTransformMatrixInUserSpace(kid) * m;
} }
nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams); nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams);

Просмотреть файл

@ -361,9 +361,7 @@ already_AddRefed<SourceSurface> nsSVGPatternFrame::PaintPattern(
nsSVGDisplayableFrame *SVGFrame = do_QueryFrame(kid); nsSVGDisplayableFrame *SVGFrame = do_QueryFrame(kid);
if (SVGFrame) { if (SVGFrame) {
SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED); SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
tm = nsSVGUtils::GetTransformMatrixInUserSpace(kid, tm = nsSVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
patternWithChildren) *
tm;
} }
nsSVGUtils::PaintFrameWithEffects(kid, *ctx, tm, aImgParams); nsSVGUtils::PaintFrameWithEffects(kid, *ctx, tm, aImgParams);

Просмотреть файл

@ -103,7 +103,7 @@ void nsSVGSwitchFrame::PaintSVG(gfxContext& aContext,
if (kid) { if (kid) {
gfxMatrix tm = aTransform; gfxMatrix tm = aTransform;
if (kid->GetContent()->IsSVGElement()) { if (kid->GetContent()->IsSVGElement()) {
tm = nsSVGUtils::GetTransformMatrixInUserSpace(kid, this) * tm; tm = nsSVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
} }
nsSVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams, nsSVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams,
aDirtyRect); aDirtyRect);
@ -255,8 +255,7 @@ SVGBBox nsSVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
if (content->IsSVGElement()) { if (content->IsSVGElement()) {
transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo( transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
{}, eChildToUserSpace) * {}, eChildToUserSpace) *
nsSVGUtils::GetTransformMatrixInUserSpace(kid, this) * nsSVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
transform;
} }
return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags); return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags);
} }

Просмотреть файл

@ -28,6 +28,7 @@
#include "nsPresContext.h" #include "nsPresContext.h"
#include "nsStyleCoord.h" #include "nsStyleCoord.h"
#include "nsStyleStruct.h" #include "nsStyleStruct.h"
#include "nsStyleTransformMatrix.h"
#include "SVGAnimatedLength.h" #include "SVGAnimatedLength.h"
#include "nsSVGClipPathFrame.h" #include "nsSVGClipPathFrame.h"
#include "nsSVGContainerFrame.h" #include "nsSVGContainerFrame.h"
@ -1094,9 +1095,8 @@ gfxRect nsSVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
matrix = gfxMatrix(); matrix = gfxMatrix();
} }
matrix = nsSVGUtils::GetTransformMatrixInUserSpace( matrix =
clipPathFrame, clipPathFrame->GetParent()) * nsSVGUtils::GetTransformMatrixInUserSpace(clipPathFrame) * matrix;
matrix;
bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags) bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
.ToThebesRect(); .ToThebesRect();
@ -1251,8 +1251,8 @@ bool nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame* aFrame,
MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element"); MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
nsSVGOuterSVGFrame* outer = nsSVGUtils::GetOuterSVGFrame(aFrame); *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
*aUserToOuterSVG = nsSVGUtils::GetTransformMatrixInUserSpace(aFrame, outer); static_cast<SVGElement*>(aFrame->GetContent()), true));
return aUserToOuterSVG->HasNonTranslation(); return aUserToOuterSVG->HasNonTranslation();
} }
@ -1642,7 +1642,7 @@ void nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
if (frame->GetContent()->IsSVGElement()) { if (frame->GetContent()->IsSVGElement()) {
// PaintSVG() expects the passed transform to be the transform to its own // PaintSVG() expects the passed transform to be the transform to its own
// SVG user space, so we need to account for any 'transform' attribute: // SVG user space, so we need to account for any 'transform' attribute:
m = nsSVGUtils::GetTransformMatrixInUserSpace(frame, frame->GetParent()); m = nsSVGUtils::GetTransformMatrixInUserSpace(frame);
} }
// SVG-in-OpenType is not allowed to paint external resources, so we can // SVG-in-OpenType is not allowed to paint external resources, so we can
@ -1696,29 +1696,49 @@ gfxMatrix nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) {
return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0); return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
} }
gfxMatrix nsSVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame, gfxMatrix nsSVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
const nsIFrame* aAncestor) {
// We check element instead of aFrame directly because SVG element // We check element instead of aFrame directly because SVG element
// may have non-SVG frame, <tspan> for example. // may have non-SVG frame, <tspan> for example.
MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(), MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
"Only use this wrapper for SVG elements"); "Only use this wrapper for SVG elements");
Matrix mm; if (!aFrame->IsTransformed()) {
auto trans = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor, return {};
nsIFrame::IN_CSS_UNITS); }
trans.ProjectTo2D(); nsDisplayTransform::FrameTransformProperties properties{
trans.CanDraw2D(&mm); aFrame, AppUnitsPerCSSPixel(), nullptr};
gfxMatrix ret = ThebesMatrix(mm); nsStyleTransformMatrix::TransformReferenceBox refBox;
refBox.Init(aFrame);
float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
AppUnitsPerCSSPixel()), // SVG elements can have x/y offset, their default transform origin
initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y, // is the origin of user space, not the top left point of the frame.
AppUnitsPerCSSPixel()); Point3D svgTransformOrigin{
properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
// Remove the initial displacement to mimic the behavior properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
// of SVGElement::PrependLocalTransformsTo(). properties.mToTransformOrigin.z};
ret = ret.PreTranslate(-initPositionX, -initPositionY);
Matrix svgTransform;
return ret; Matrix4x4 trans;
(void)aFrame->IsSVGTransformed(&svgTransform);
if (properties.HasTransform()) {
trans = nsStyleTransformMatrix::ReadTransforms(
properties.mIndividualTransformList
? properties.mIndividualTransformList->mHead
: nullptr,
properties.mMotion,
properties.mTransformList ? properties.mTransformList->mHead : nullptr,
refBox, AppUnitsPerCSSPixel());
} else {
trans = Matrix4x4::From2D(svgTransform);
}
trans.ChangeBasis(svgTransformOrigin);
Matrix mm;
trans.ProjectTo2D();
(void)trans.CanDraw2D(&mm);
return ThebesMatrix(mm);
} }

Просмотреть файл

@ -597,11 +597,13 @@ class nsSVGUtils {
} }
/** /**
* A simple wrapper of nsLayoutUtils::GetTransformToAncestor to avoid * It is a replacement of
* boilerplate code for changing unit and matrix format. * SVGElement::PrependLocalTransformsTo(eUserSpaceToParent).
* If no CSS transform is involved, they should behave exactly the same;
* if there are CSS transforms, this one will take them into account
* while SVGElement::PrependLocalTransformsTo won't.
*/ */
static gfxMatrix GetTransformMatrixInUserSpace(const nsIFrame* aFrame, static gfxMatrix GetTransformMatrixInUserSpace(const nsIFrame* aFrame);
const nsIFrame* aAncestor);
}; };
#endif #endif

Просмотреть файл

@ -9,6 +9,8 @@
<Url type="application/x-suggestions+json" method="GET" template="https://www.prisjakt.nu/plugins/opensearch/suggestions.php"> <Url type="application/x-suggestions+json" method="GET" template="https://www.prisjakt.nu/plugins/opensearch/suggestions.php">
<Param name="search" value="{searchTerms}"/> <Param name="search" value="{searchTerms}"/>
</Url> </Url>
<Url type="text/html" method="GET" template="https://m.prisjakt.nu/search/{searchTerms}"/> <Url type="text/html" method="GET" template="https://www.prisjakt.nu/search">
<Param name="search" value="{searchTerms}"/>
</Url>
<Url type="application/x-moz-tabletsearch" method="GET" template="https://www.prisjakt.nu/#rparams=ss={searchTerms}"/> <Url type="application/x-moz-tabletsearch" method="GET" template="https://www.prisjakt.nu/#rparams=ss={searchTerms}"/>
</SearchPlugin> </SearchPlugin>

Просмотреть файл

@ -63,6 +63,9 @@ add_task(async function test_target_blank_link() {
let link = win.document.getElementById("link"); let link = win.document.getElementById("link");
{ {
// Flush layout so that synthesizeMouseAtCenter on a cross-origin iframe
// works as expected.
document.body.getBoundingClientRect();
synthesizeMouseAtCenter(link, {}, iframe.contentWindow); synthesizeMouseAtCenter(link, {}, iframe.contentWindow);
let {subject: doc} = await promiseObserved("document-element-inserted", doc => doc.documentURI === linkURL); let {subject: doc} = await promiseObserved("document-element-inserted", doc => doc.documentURI === linkURL);
info("Link opened"); info("Link opened");

Просмотреть файл

@ -2144,6 +2144,36 @@ telemetry:
- 'main' - 'main'
- 'content' - 'content'
ecosystem_old_send_time:
bug_numbers:
- 1545365
description: >
The timestamp im milliseconds of the last time we tried to send an Ecosystem ping
expires: "73"
kind: string
notification_emails:
- jrediger@mozilla.com
release_channel_collection: opt-in
record_in_processes:
- 'main'
record_into_store:
- 'pre-account'
ecosystem_new_send_time:
bug_numbers:
- 1545365
description: >
The timestamp im milliseconds of the last time we tried to send an Ecosystem ping
expires: "73"
kind: string
notification_emails:
- jrediger@mozilla.com
release_channel_collection: opt-in
record_in_processes:
- 'main'
record_into_store:
- 'pre-account'
telemetry.discarded: telemetry.discarded:
accumulations: accumulations:
bug_numbers: bug_numbers:

Просмотреть файл

@ -216,10 +216,21 @@ var EcosystemTelemetry = {
this._log.trace(`_submitPing, ping type: ${pingType}, reason: ${reason}`); this._log.trace(`_submitPing, ping type: ${pingType}, reason: ${reason}`);
let now = Policy.monotonicNow(); let now = Policy.monotonicNow();
let new_send_time = now;
let old_send_time = this._lastSendTime;
// Duration in seconds // Duration in seconds
let duration = Math.round((now - this._lastSendTime) / 1000); let duration = Math.round((now - this._lastSendTime) / 1000);
this._lastSendTime = now; this._lastSendTime = now;
// FIXME(bug 1545365): This is a hack to track the values we see,
// in order to determine where negative durations are coming from.
// Note: These scalars must be set _before_ getting the rest of the payload.
// Note: We don't support signed integer scalars, so we convert these to strings
// in order to also capture the negative values.
Services.telemetry.scalarSet("telemetry.ecosystem_old_send_time", old_send_time.toString());
Services.telemetry.scalarSet("telemetry.ecosystem_new_send_time", new_send_time.toString());
let payload = this._payload(reason, duration); let payload = this._payload(reason, duration);
// Never include the client ID. // Never include the client ID.

Просмотреть файл

@ -51,6 +51,9 @@ function checkPingStructure(ping, type, reason) {
Assert.ok("keyedScalars" in payload, "Payload must contain keyed scalars"); Assert.ok("keyedScalars" in payload, "Payload must contain keyed scalars");
Assert.ok("histograms" in payload, "Payload must contain histograms"); Assert.ok("histograms" in payload, "Payload must contain histograms");
Assert.ok("keyedHistograms" in payload, "Payload must contain keyed histograms"); Assert.ok("keyedHistograms" in payload, "Payload must contain keyed histograms");
Assert.ok("telemetry.ecosystem_old_send_time" in payload.scalars.parent, "Old send time should be set");
Assert.ok("telemetry.ecosystem_new_send_time" in payload.scalars.parent, "New send time should be set");
} }
function sendPing() { function sendPing() {

Просмотреть файл

@ -6700,6 +6700,11 @@ function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
{ {
aFocusedEditor.value = ""; aFocusedEditor.value = "";
// The frames and panel are cross-origin, and we no longer
// propagate flushes to parent cross-origin iframes explicitly,
// so flush our own layout here so the positions are correct.
document.documentElement.getBoundingClientRect();
var editorRect = synthesizeQueryEditorRect(); var editorRect = synthesizeQueryEditorRect();
if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) { if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
return; return;