зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
This commit is contained in:
Коммит
8ed4c0a1dd
|
@ -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,10 +18,11 @@
|
||||||
"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");
|
||||||
|
|
||||||
|
if (!aFrame->IsTransformed()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
nsDisplayTransform::FrameTransformProperties properties{
|
||||||
|
aFrame, AppUnitsPerCSSPixel(), nullptr};
|
||||||
|
nsStyleTransformMatrix::TransformReferenceBox refBox;
|
||||||
|
refBox.Init(aFrame);
|
||||||
|
|
||||||
|
// SVG elements can have x/y offset, their default transform origin
|
||||||
|
// is the origin of user space, not the top left point of the frame.
|
||||||
|
Point3D svgTransformOrigin{
|
||||||
|
properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
|
||||||
|
properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
|
||||||
|
properties.mToTransformOrigin.z};
|
||||||
|
|
||||||
|
Matrix svgTransform;
|
||||||
|
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;
|
Matrix mm;
|
||||||
auto trans = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor,
|
|
||||||
nsIFrame::IN_CSS_UNITS);
|
|
||||||
|
|
||||||
trans.ProjectTo2D();
|
trans.ProjectTo2D();
|
||||||
trans.CanDraw2D(&mm);
|
(void)trans.CanDraw2D(&mm);
|
||||||
gfxMatrix ret = ThebesMatrix(mm);
|
|
||||||
|
|
||||||
float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
|
return ThebesMatrix(mm);
|
||||||
AppUnitsPerCSSPixel()),
|
|
||||||
initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
|
|
||||||
AppUnitsPerCSSPixel());
|
|
||||||
|
|
||||||
// Remove the initial displacement to mimic the behavior
|
|
||||||
// of SVGElement::PrependLocalTransformsTo().
|
|
||||||
ret = ret.PreTranslate(-initPositionX, -initPositionY);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче