Skip to content

Commit 3f70fbc

Browse files
authored
CSS: Fix dimensions of table <col> elements
Changes: 1. Fix measurements of `<col span="2">` elements in Firefox. 2. Fix measurements of all implicitly sized `<col>` elements in Safari. Firefox always reports computed width as if `span` was 1. In Safari, computed width for columns is always 0. Work around both issues by using `offsetWidth`. In IE/Edge, `<col>` computed width is `"auto"` unless `width` is set explicitly via CSS so measurements there remain incorrect. Because of the lack of a proper workaround, we accept this limitation. Fixes jquerygh-5628 Closes jquerygh-5634 Ref jquerygh-5630
1 parent 6b7d74a commit 3f70fbc

File tree

4 files changed

+214
-103
lines changed

4 files changed

+214
-103
lines changed

src/css.js

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ define( [
2424

2525
"use strict";
2626

27-
var
28-
29-
// Swappable if display is none or starts with table
30-
// except "table", "table-cell", or "table-caption"
31-
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
32-
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
33-
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
27+
var cssShow = { position: "absolute", visibility: "hidden", display: "block" },
3428
cssNormalTransform = {
3529
letterSpacing: "0",
3630
fontWeight: "400"
@@ -144,27 +138,26 @@ function getWidthOrHeight( elem, dimension, extra ) {
144138
}
145139

146140

147-
// Support: IE 9 - 11 only
148-
// Use offsetWidth/offsetHeight for when box sizing is unreliable.
149-
// In those cases, the computed value can be trusted to be border-box.
150-
if ( ( !support.boxSizingReliable() && isBorderBox ||
141+
if (
142+
(
143+
144+
// Fall back to offsetWidth/offsetHeight when value is "auto"
145+
// This happens for inline elements with no explicit setting (gh-3571)
146+
val === "auto" ||
147+
148+
// Support: IE 9 - 11 only
149+
// Use offsetWidth/offsetHeight for when box sizing is unreliable.
150+
// In those cases, the computed value can be trusted to be border-box.
151+
( !support.boxSizingReliable() && isBorderBox ) ||
151152

152-
// Support: IE 10 - 11+, Edge 15 - 18+
153-
// IE/Edge misreport `getComputedStyle` of table rows with width/height
154-
// set in CSS while `offset*` properties report correct values.
155-
// Interestingly, in some cases IE 9 doesn't suffer from this issue.
156-
// Support: Firefox 70+
157-
// Firefox includes border widths
158-
// in computed dimensions for table rows. (gh-4529)
159-
!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
153+
( !support.reliableColDimensions() && nodeName( elem, "col" ) ) ||
160154

161-
// Fall back to offsetWidth/offsetHeight when value is "auto"
162-
// This happens for inline elements with no explicit setting (gh-3571)
163-
val === "auto" ||
155+
( !support.reliableTrDimensions() && nodeName( elem, "tr" ) ) ||
164156

165-
// Support: Android <=4.1 - 4.3 only
166-
// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
167-
!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
157+
// Support: Android <=4.1 - 4.3 only
158+
// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
159+
( !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" )
160+
) &&
168161

169162
// Make sure the element is visible & connected
170163
elem.getClientRects().length ) {
@@ -375,17 +368,9 @@ jQuery.each( [ "height", "width" ], function( _i, dimension ) {
375368
get: function( elem, computed, extra ) {
376369
if ( computed ) {
377370

378-
// Certain elements can have dimension info if we invisibly show them
379-
// but it must have a current display style that would benefit
380-
return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
381-
382-
// Support: Safari 8+
383-
// Table columns in Safari have non-zero offsetWidth & zero
384-
// getBoundingClientRect().width unless display is changed.
385-
// Support: IE <=11 only
386-
// Running getBoundingClientRect on a disconnected node
387-
// in IE throws an error.
388-
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
371+
// Elements with `display: none` can have dimension info if
372+
// we invisibly show them.
373+
return jQuery.css( elem, "display" ) === "none" ?
389374
swap( elem, cssShow, function() {
390375
return getWidthOrHeight( elem, dimension, extra );
391376
} ) :

src/css/support.js

Lines changed: 92 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ define( [
99

1010
( function() {
1111

12+
var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
13+
reliableMarginLeftVal, reliableTrDimensionsVal, reliableColDimensionsVal,
14+
container = document.createElement( "div" ),
15+
div = document.createElement( "div" ),
16+
table = document.createElement( "table" );
17+
18+
// Finish early in limited (non-browser) environments
19+
if ( !div.style ) {
20+
return;
21+
}
22+
23+
function roundPixelMeasures( measure ) {
24+
return Math.round( parseFloat( measure ) );
25+
}
26+
1227
// Executing both pixelPosition & boxSizingReliable tests require only one layout
1328
// so they're executed at the same time to save the second computation.
1429
function computeStyleTests() {
@@ -59,23 +74,83 @@ define( [
5974

6075
documentElement.removeChild( container );
6176

62-
// Nullify the div so it wouldn't be stored in the memory and
63-
// it will also be a sign that checks already performed
77+
// Nullify the table so it wouldn't be stored in the memory;
78+
// it will also be a sign that checks were already performed.
6479
div = null;
6580
}
6681

67-
function roundPixelMeasures( measure ) {
68-
return Math.round( parseFloat( measure ) );
69-
}
82+
// Executing table tests requires only one layout, so they're executed
83+
// at the same time to save the second computation.
84+
function computeTableStyleTests() {
7085

71-
var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
72-
reliableTrDimensionsVal, reliableMarginLeftVal,
73-
container = document.createElement( "div" ),
74-
div = document.createElement( "div" );
86+
// This is a singleton, we need to execute it only once
87+
if ( !table ) {
88+
return;
89+
}
7590

76-
// Finish early in limited (non-browser) environments
77-
if ( !div.style ) {
78-
return;
91+
var trStyle,
92+
col = document.createElement( "col" ),
93+
tr = document.createElement( "tr" ),
94+
td = document.createElement( "td" );
95+
96+
table.style.cssText = "position:absolute;left:-11111px;" +
97+
"border-collapse:separate;border-spacing:0";
98+
tr.style.cssText = "box-sizing:content-box;border:1px solid;height:1px";
99+
td.style.cssText = "height:9px;width:9px;padding:0";
100+
101+
col.span = 2;
102+
103+
documentElement
104+
.appendChild( table )
105+
.appendChild( col )
106+
.parentNode
107+
.appendChild( tr )
108+
.appendChild( td )
109+
.parentNode
110+
.appendChild( td.cloneNode( true ) );
111+
112+
// Don't run until window is visible
113+
if ( table.offsetWidth === 0 ) {
114+
documentElement.removeChild( table );
115+
return;
116+
}
117+
118+
trStyle = window.getComputedStyle( tr );
119+
120+
// Support: Firefox 135+
121+
// Firefox always reports computed width as if `span` was 1.
122+
// Support: Safari 18.3+
123+
// In Safari, computed width for columns is always 0.
124+
// In both these browsers, using `offsetWidth` solves the issue.
125+
// Support: IE 11+, Edge 15 - 18+
126+
// In IE/Edge, `<col>` computed width is `"auto"` unless `width` is set
127+
// explicitly via CSS so measurements there remain incorrect. Because of
128+
// the lack of a proper workaround, we accept this limitation, treating
129+
// IE/Edge as passing the test. Detect them by checking for
130+
// `msMatchesSelector`; despite Edge 15+ implementing `matches`, all
131+
// IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.
132+
reliableColDimensionsVal = !!documentElement.msMatchesSelector || Math.round( parseFloat(
133+
window.getComputedStyle( col ).width )
134+
) === 18;
135+
136+
// Support: IE 9 - 11+, Edge 15 - 18+
137+
// IE/Edge misreport `getComputedStyle` of table rows with width/height
138+
// set in CSS while `offset*` properties report correct values.
139+
// Behavior in IE 9 is more subtle than in newer versions & it passes
140+
// some versions of this test; make sure not to make it pass there!
141+
//
142+
// Support: Firefox 70+
143+
// Only Firefox includes border widths
144+
// in computed dimensions for table rows. (gh-4529)
145+
reliableTrDimensionsVal = Math.round( parseFloat( trStyle.height ) +
146+
parseFloat( trStyle.borderTopWidth ) +
147+
parseFloat( trStyle.borderBottomWidth ) ) === tr.offsetHeight;
148+
149+
documentElement.removeChild( table );
150+
151+
// Nullify the table so it wouldn't be stored in the memory;
152+
// it will also be a sign that checks were already performed.
153+
table = null;
79154
}
80155

81156
// Support: IE <=9 - 11 only
@@ -106,58 +181,13 @@ define( [
106181
return scrollboxSizeVal;
107182
},
108183

109-
// Support: IE 9 - 11+, Edge 15 - 18+
110-
// IE/Edge misreport `getComputedStyle` of table rows with width/height
111-
// set in CSS while `offset*` properties report correct values.
112-
// Behavior in IE 9 is more subtle than in newer versions & it passes
113-
// some versions of this test; make sure not to make it pass there!
114-
//
115-
// Support: Firefox 70+
116-
// Only Firefox includes border widths
117-
// in computed dimensions. (gh-4529)
118184
reliableTrDimensions: function() {
119-
var table, tr, trChild, trStyle;
120-
if ( reliableTrDimensionsVal == null ) {
121-
table = document.createElement( "table" );
122-
tr = document.createElement( "tr" );
123-
trChild = document.createElement( "div" );
124-
125-
table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
126-
tr.style.cssText = "box-sizing:content-box;border:1px solid";
127-
128-
// Support: Chrome 86+
129-
// Height set through cssText does not get applied.
130-
// Computed height then comes back as 0.
131-
tr.style.height = "1px";
132-
trChild.style.height = "9px";
133-
134-
// Support: Android 8 Chrome 86+
135-
// In our bodyBackground.html iframe,
136-
// display for all div elements is set to "inline",
137-
// which causes a problem only in Android 8 Chrome 86.
138-
// Ensuring the div is `display: block`
139-
// gets around this issue.
140-
trChild.style.display = "block";
141-
142-
documentElement
143-
.appendChild( table )
144-
.appendChild( tr )
145-
.appendChild( trChild );
146-
147-
// Don't run until window is visible
148-
if ( table.offsetWidth === 0 ) {
149-
documentElement.removeChild( table );
150-
return;
151-
}
152-
153-
trStyle = window.getComputedStyle( tr );
154-
reliableTrDimensionsVal = ( Math.round( parseFloat( trStyle.height ) ) +
155-
Math.round( parseFloat( trStyle.borderTopWidth ) ) +
156-
Math.round( parseFloat( trStyle.borderBottomWidth ) ) ) === tr.offsetHeight;
157-
158-
documentElement.removeChild( table );
159-
}
185+
computeTableStyleTests();
160186
return reliableTrDimensionsVal;
187+
},
188+
reliableColDimensions: function() {
189+
computeTableStyleTests();
190+
return reliableColDimensionsVal;
161191
}
162192
} );
163193
} )();

test/unit/dimensions.js

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,17 +368,97 @@ QUnit.test( "getting dimensions shouldn't modify runtimeStyle see trac-9233", fu
368368
$div.remove();
369369
} );
370370

371-
QUnit.test( "table dimensions", function( assert ) {
371+
QUnit.test( "hidden element with dimensions from a stylesheet", function( assert ) {
372372
assert.expect( 2 );
373373

374-
var table = jQuery( "<table><colgroup><col></col><col></col></colgroup><tbody><tr><td></td><td>a</td></tr><tr><td></td><td>a</td></tr></tbody></table>" ).appendTo( "#qunit-fixture" ),
374+
var div = jQuery( "" +
375+
"<div class='display-none-style'>" +
376+
" <style>" +
377+
" .display-none-style {" +
378+
" display: none;" +
379+
" width: 111px;" +
380+
" height: 123px;" +
381+
" }" +
382+
" </style>" +
383+
"</div>" +
384+
"" )
385+
.appendTo( "#qunit-fixture" );
386+
387+
assert.strictEqual( div.width(), 111, "width of a hidden element" );
388+
assert.strictEqual( div.height(), 123, "height of a hidden element" );
389+
} );
390+
391+
QUnit.test( "hidden element with implicit content-based dimensions", function( assert ) {
392+
assert.expect( 2 );
393+
394+
var container = jQuery( "" +
395+
396+
// font-size affects the child dimensions implicitly
397+
"<div style='font-size: 20px'>" +
398+
" <div style='padding: 10px; display: none'>" +
399+
" <div style='width: 3em; height: 2em'></div>" +
400+
" </div>" +
401+
"</div>" +
402+
"" ),
403+
div = container.children().first();
404+
405+
container.appendTo( "#qunit-fixture" );
406+
407+
assert.strictEqual( div.width(), 60, "width of a hidden element" );
408+
assert.strictEqual( div.height(), 40, "height of a hidden element" );
409+
} );
410+
411+
QUnit.test( "table dimensions", function( assert ) {
412+
assert.expect( 3 );
413+
414+
var table = jQuery( "" +
415+
"<table style='border-spacing: 0'>" +
416+
" <colgroup>" +
417+
" <col />" +
418+
" <col span='2' class='col-double' />" +
419+
" </colgroup>" +
420+
" <tbody>" +
421+
" <tr>" +
422+
" <td></td>" +
423+
" <td class='td-a-1'>a</td>" +
424+
" <td class='td-b-1'>b</td>" +
425+
" </tr>" +
426+
" <tr>" +
427+
" <td></td>" +
428+
" <td>a</td>" +
429+
" <td>b</td>" +
430+
" </tr>" +
431+
" </tbody>" +
432+
"</table>"
433+
).appendTo( "#qunit-fixture" ),
375434
tdElem = table.find( "td" ).first(),
376-
colElem = table.find( "col" ).first().width( 300 );
435+
colElem = table.find( "col" ).first(),
436+
doubleColElem = table.find( ".col-double" );
377437

378-
table.find( "td" ).css( { "margin": 0, "padding": 0 } );
438+
table.find( "td" ).css( { margin: 0, padding: 0, border: 0 } );
439+
440+
colElem.width( 300 );
441+
442+
table.find( ".td-a-1" ).width( 200 );
443+
table.find( ".td-b-1" ).width( 400 );
379444

380445
assert.equal( tdElem.width(), tdElem.width(), "width() doesn't alter dimension values of empty cells, see trac-11293" );
381-
assert.equal( colElem.width(), 300, "col elements have width(), see trac-12243" );
446+
assert.equal( colElem.width(), 300, "col elements have width(), (trac-12243)" );
447+
448+
// Support: IE 9 - 11+, Edge 15 - 18+
449+
// In IE/Edge, `<col>` computed width is `"auto"` unless `width` is set
450+
// explicitly via CSS so measurements there remain incorrect. Because of
451+
// the lack of a proper workaround, we accept this limitation.
452+
// To make IE/Edge pass the test, set the width explicitly. Detect them by
453+
// checking for `msMatchesSelector`; despite Edge 15+ implementing
454+
// `matches`, all IE 9+ and Edge Legacy versions implement
455+
// `msMatchesSelector` as well.
456+
if ( document.documentElement.msMatchesSelector ) {
457+
doubleColElem.width( 600 );
458+
}
459+
460+
assert.equal( doubleColElem.width(), 600,
461+
"col with span measured correctly (gh-5628)" );
382462
} );
383463

384464
QUnit.test( "SVG dimensions (basic content-box)", function( assert ) {

0 commit comments

Comments
 (0)