1(function() {
2// Test is initiated from body.onload, so explicit done() call is required.
3setup({ explicit_done: true });
4
5function checkSubtreeExpectedValues(t, parent, prefix)
6{
7    var checkedLayout = checkExpectedValues(t, parent, prefix);
8    Array.prototype.forEach.call(parent.childNodes, function(node) {
9        checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
10    });
11    return checkedLayout;
12}
13
14function checkAttribute(output, node, attribute)
15{
16    var result = node.getAttribute && node.getAttribute(attribute);
17    output.checked |= !!result;
18    return result;
19}
20
21function assert_tolerance(actual, expected, message)
22{
23    if (isNaN(expected) || isNaN(actual) || Math.abs(actual - expected) >= 1) {
24        assert_equals(actual, Number(expected), message);
25    }
26}
27
28function checkDataKeys(node) {
29    var validData = new Set([
30        "data-expected-width",
31        "data-expected-height",
32        "data-offset-x",
33        "data-offset-y",
34        "data-expected-client-width",
35        "data-expected-client-height",
36        "data-expected-scroll-width",
37        "data-expected-scroll-height",
38        "data-expected-bounding-client-rect-width",
39        "data-expected-bounding-client-rect-height",
40        "data-total-x",
41        "data-total-y",
42        "data-expected-display",
43        "data-expected-padding-top",
44        "data-expected-padding-bottom",
45        "data-expected-padding-left",
46        "data-expected-padding-right",
47        "data-expected-margin-top",
48        "data-expected-margin-bottom",
49        "data-expected-margin-left",
50        "data-expected-margin-right"
51    ]);
52    if (!node || !node.getAttributeNames)
53        return;
54    // Use "data-test" prefix if you need custom-named data elements.
55    for (let name of node.getAttributeNames()) {
56        if (name.startsWith("data-") && !name.startsWith("data-test"))
57            assert_true(validData.has(name), name + " is a valid data attribute");
58    }
59}
60
61function checkExpectedValues(t, node, prefix)
62{
63    checkDataKeys(node);
64    var output = { checked: false };
65
66    var expectedWidth = checkAttribute(output, node, "data-expected-width");
67    if (expectedWidth) {
68        assert_tolerance(node.offsetWidth, expectedWidth, prefix + "width");
69    }
70
71    var expectedHeight = checkAttribute(output, node, "data-expected-height");
72    if (expectedHeight) {
73        assert_tolerance(node.offsetHeight, expectedHeight, prefix + "height");
74    }
75
76    var expectedOffset = checkAttribute(output, node, "data-offset-x");
77    if (expectedOffset) {
78        assert_tolerance(node.offsetLeft, expectedOffset, prefix + "offsetLeft");
79    }
80
81    var expectedOffset = checkAttribute(output, node, "data-offset-y");
82    if (expectedOffset) {
83        assert_tolerance(node.offsetTop, expectedOffset, prefix + "offsetTop");
84    }
85
86    var expectedWidth = checkAttribute(output, node, "data-expected-client-width");
87    if (expectedWidth) {
88        assert_tolerance(node.clientWidth, expectedWidth, prefix + "clientWidth");
89    }
90
91    var expectedHeight = checkAttribute(output, node, "data-expected-client-height");
92    if (expectedHeight) {
93        assert_tolerance(node.clientHeight, expectedHeight, prefix + "clientHeight");
94    }
95
96    var expectedWidth = checkAttribute(output, node, "data-expected-scroll-width");
97    if (expectedWidth) {
98        assert_tolerance(node.scrollWidth, expectedWidth, prefix + "scrollWidth");
99    }
100
101    var expectedHeight = checkAttribute(output, node, "data-expected-scroll-height");
102    if (expectedHeight) {
103        assert_tolerance(node.scrollHeight, expectedHeight, prefix + "scrollHeight");
104    }
105
106    var expectedWidth = checkAttribute(output, node, "data-expected-bounding-client-rect-width");
107    if (expectedWidth) {
108        assert_tolerance(node.getBoundingClientRect().width, expectedWidth, prefix + "getBoundingClientRect().width");
109    }
110
111    var expectedHeight = checkAttribute(output, node, "data-expected-bounding-client-rect-height");
112    if (expectedHeight) {
113        assert_tolerance(node.getBoundingClientRect().height, expectedHeight, prefix + "getBoundingClientRect().height");
114    }
115
116    var expectedOffset = checkAttribute(output, node, "data-total-x");
117    if (expectedOffset) {
118        var totalLeft = node.clientLeft + node.offsetLeft;
119        assert_tolerance(totalLeft, expectedOffset, prefix +
120                         "clientLeft+offsetLeft (" + node.clientLeft + " + " + node.offsetLeft + ")");
121    }
122
123    var expectedOffset = checkAttribute(output, node, "data-total-y");
124    if (expectedOffset) {
125        var totalTop = node.clientTop + node.offsetTop;
126        assert_tolerance(totalTop, expectedOffset, prefix +
127                         "clientTop+offsetTop (" + node.clientTop + " + " + node.offsetTop + ")");
128    }
129
130    var expectedDisplay = checkAttribute(output, node, "data-expected-display");
131    if (expectedDisplay) {
132        var actualDisplay = getComputedStyle(node).display;
133        assert_equals(actualDisplay, expectedDisplay, prefix + "display");
134    }
135
136    var expectedPaddingTop = checkAttribute(output, node, "data-expected-padding-top");
137    if (expectedPaddingTop) {
138        var actualPaddingTop = getComputedStyle(node).paddingTop;
139        // Trim the unit "px" from the output.
140        actualPaddingTop = actualPaddingTop.slice(0, -2);
141        assert_equals(actualPaddingTop, expectedPaddingTop, prefix + "padding-top");
142    }
143
144    var expectedPaddingBottom = checkAttribute(output, node, "data-expected-padding-bottom");
145    if (expectedPaddingBottom) {
146        var actualPaddingBottom = getComputedStyle(node).paddingBottom;
147        // Trim the unit "px" from the output.
148        actualPaddingBottom = actualPaddingBottom.slice(0, -2);
149        assert_equals(actualPaddingBottom, expectedPaddingBottom, prefix + "padding-bottom");
150    }
151
152    var expectedPaddingLeft = checkAttribute(output, node, "data-expected-padding-left");
153    if (expectedPaddingLeft) {
154        var actualPaddingLeft = getComputedStyle(node).paddingLeft;
155        // Trim the unit "px" from the output.
156        actualPaddingLeft = actualPaddingLeft.slice(0, -2);
157        assert_equals(actualPaddingLeft, expectedPaddingLeft, prefix + "padding-left");
158    }
159
160    var expectedPaddingRight = checkAttribute(output, node, "data-expected-padding-right");
161    if (expectedPaddingRight) {
162        var actualPaddingRight = getComputedStyle(node).paddingRight;
163        // Trim the unit "px" from the output.
164        actualPaddingRight = actualPaddingRight.slice(0, -2);
165        assert_equals(actualPaddingRight, expectedPaddingRight, prefix + "padding-right");
166    }
167
168    var expectedMarginTop = checkAttribute(output, node, "data-expected-margin-top");
169    if (expectedMarginTop) {
170        var actualMarginTop = getComputedStyle(node).marginTop;
171        // Trim the unit "px" from the output.
172        actualMarginTop = actualMarginTop.slice(0, -2);
173        assert_equals(actualMarginTop, expectedMarginTop, prefix + "margin-top");
174    }
175
176    var expectedMarginBottom = checkAttribute(output, node, "data-expected-margin-bottom");
177    if (expectedMarginBottom) {
178        var actualMarginBottom = getComputedStyle(node).marginBottom;
179        // Trim the unit "px" from the output.
180        actualMarginBottom = actualMarginBottom.slice(0, -2);
181        assert_equals(actualMarginBottom, expectedMarginBottom, prefix + "margin-bottom");
182    }
183
184    var expectedMarginLeft = checkAttribute(output, node, "data-expected-margin-left");
185    if (expectedMarginLeft) {
186        var actualMarginLeft = getComputedStyle(node).marginLeft;
187        // Trim the unit "px" from the output.
188        actualMarginLeft = actualMarginLeft.slice(0, -2);
189        assert_equals(actualMarginLeft, expectedMarginLeft, prefix + "margin-left");
190    }
191
192    var expectedMarginRight = checkAttribute(output, node, "data-expected-margin-right");
193    if (expectedMarginRight) {
194        var actualMarginRight = getComputedStyle(node).marginRight;
195        // Trim the unit "px" from the output.
196        actualMarginRight = actualMarginRight.slice(0, -2);
197        assert_equals(actualMarginRight, expectedMarginRight, prefix + "margin-right");
198    }
199
200    return output.checked;
201}
202
203var testNumber = 0;
204var highlightError = false; // displays outline around failed test element.
205var printDomOnError = true; // prints dom when test fails.
206
207window.checkLayout = function(selectorList, callDone = true)
208{
209    if (!selectorList) {
210        console.error("You must provide a CSS selector of nodes to check.");
211        return;
212    }
213    var nodes = document.querySelectorAll(selectorList);
214    nodes = Array.prototype.slice.call(nodes);
215    var checkedLayout = false;
216    Array.prototype.forEach.call(nodes, function(node) {
217        test(function(t) {
218            var container = node.parentNode.className == 'container' ? node.parentNode : node;
219            var prefix =
220                printDomOnError ? '\n' + container.outerHTML + '\n' : '';
221            var passed = false;
222            try {
223                checkedLayout |= checkExpectedValues(t, node.parentNode, prefix);
224                checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
225                passed = true;
226            } finally {
227              if (!passed && highlightError) {
228                if (!document.getElementById('testharness_error_css')) {
229                  var style = document.createElement('style');
230                  style.id = 'testharness_error_css';
231                  style.textContent = '.testharness_error { outline: red dotted 2px !important; }';
232                  document.body.appendChild(style);
233                }
234                if (node)
235                  node.classList.add('testharness_error');
236              }
237                checkedLayout |= !passed;
238            }
239        }, selectorList + ' ' + String(++testNumber));
240    });
241    if (!checkedLayout) {
242        console.error("No valid data-* attributes found in selector list : " + selectorList);
243    }
244    if (callDone)
245        done();
246};
247
248})();
249