4 use List::Util qw(sum max first);
7 title => 'browser compatibility cheat sheet',
10 "Compatibility table of new web features (HTML5, CSS3, SVG, Javascript)",
11 "comparing support and usage share for all popular browser versions.",
14 web browser support compatibility usage available feature
15 html html5 css css3 svg javascript js dom mobile
16 ie internet explorer firefox chrome safari webkit opera
18 stylesheet => [qw'circus dark mono red light'],
19 data => ['browser-support.inc.pl'],
22 say "<h1>Browser compatibility</h1>\n";
24 my $caniuse = do 'browser-support.inc.pl' or die $! || $@;
26 # mark last three (future) versions as unreleased, ensure current isn't
28 $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => 0,
31 } for values %{ $caniuse->{agents} };
52 p => 'plugin required',
53 j => 'javascript required',
56 d => 'disabled by default',
60 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
65 unoff => 'l1', # unofficial
67 cr => 'l4', # candidate
68 pr => 'l4', # proposed
69 rec => 'l5', # recommendation
70 other => 'l2', # non-w3
71 ietf => 'l5', # standard
74 if (my ($somerow) = values %{ $caniuse->{data} }) {
75 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
76 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
79 my @browsers = keys %versions;
82 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
84 my ($canihas, $usage, $minusage);
85 given ($get{usage} // 'wm') {
89 when (!/^[a-z][\w-]+$/) {
90 printf "<p>Invalid browser usage data request: <em>%s</em>",
91 'identifier must be alphanumeric name or <q>0</q>';
93 $canihas = do "browser-usage-$_.inc.pl" or do {
94 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
98 my $ref = $canihas->{-title} || 'unknown';
99 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
100 for $canihas->{-site} || $canihas->{-source} || ();
101 $ref .= " $_" for $canihas->{-date} || ();
102 print "\nwith $ref browser usage statistics";
104 if ($usage) { # first() does not work inside given >:(
105 # adapt version usage to actual support data
106 my %engineuse; # prefix => usage sum
107 for my $browser (keys %versions) {
108 my $row = $canihas->{$browser} // {};
109 my $verlist = $versions{$browser} or next;
110 my %supported = map { $_ => 1 } @$verlist;
112 # cascade unknown versions
113 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
114 while (my ($version, $usage) = each %$row) {
115 next if defined $supported{$version};
116 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
117 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
118 $row->{$next} += $usage;
119 $row->{$version} = 0; # balance browser total
122 # build row list for each version
123 if ($minusage = $get{threshold} // .5) {
124 my @vershown; # $verlist replacement
125 my ($rowusage, @verrow) = (0); # replacement row tracking
127 push @verrow, $_; # queue each version
128 if (($rowusage += $row->{$_}) >= $minusage) {
129 push @vershown, [@verrow]; # add row
130 ($rowusage, @verrow) = (0); # reset row tracking
133 push @vershown, \@verrow if @verrow; # always add latest
134 @$verlist = @vershown;
137 @$verlist = map { [$_] } @$verlist;
140 # reusable aggregates (grouped by prefix (engine) and browser)
141 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
142 $row->{-total} = sum(values %$row);
145 # order browser columns by usage grouped by engine
147 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
148 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
150 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
154 # order browser columns by name grouped by engine
156 $caniuse->{agents}->{$b}->{prefix} cmp
157 $caniuse->{agents}->{$a}->{prefix}
170 my $zero = $#$_ - 2; # baseline index
171 ($_->[$zero - 2] => .5), # past
172 ($_->[$zero - 1] => 10 ), # previous
173 ($_->[$zero + 2] => 0 ), # future
174 ($_->[$zero + 1] => .5), # next
175 ($_->[$zero ] => 30 ), # current
176 } $caniuse->{agents}->{$_}->{versions}
179 }; # fallback hash based on release semantics
180 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
182 my $usagepct = 1; # score multiplier for 0..100 result
183 # normalise usage percentage to only include shown browsers
184 $usagepct = 100.01 / featurescore({ # yes for every possible version
185 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
188 print '<table class="mapped">';
189 print '<col span="3">'; # should match first thead row
190 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
191 say '</colgroup><col>';
193 my $header = join('',
195 '<th colspan="3">feature',
197 my $name = $caniuse->{agents}->{$_}->{browser};
198 sprintf('<th colspan="%d" class="%s" title="%s">%s',
199 scalar @{ $versions{$_} },
200 join(' ', map {"b-a-$_"} grep {$_}
201 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
204 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
208 length $name < 3 + @{ $versions{$_} }*2 ? $name
209 : $caniuse->{agents}->{$_}->{abbr};
215 print '<thead>', $header;
216 # preceding row without any colspan to work around gecko bug
219 for my $browser (@browsers) {
220 for (@{ $versions{$browser} }) {
221 my $lastver = $_->[-1];
222 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
223 my $future = defined $release;
224 printf('<td title="%s"%s>%s',
226 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
227 $future ? 'development' : (),
228 'version ' . join(', ', @{$_}),
230 $future && ' class="ex"',
231 showversions($lastver),
237 say '<tfoot>', $header, '</tfoot>';
240 # relative amount of support for given feature
241 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
243 if (my $row = shift) {
245 while (my ($browser, $versions) = each %$row) {
246 ref $versions eq 'HASH' or next;
247 while (my ($version, $_) = each %$versions) {
248 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
254 while (my ($browser, $vercols) = each %versions) {
255 my $div = 0; # multiplier exponent (decreased to lower value)
256 my @vers = map { $row->{$browser}->{$_} } @$vercols;
257 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
258 my @future; # find upcoming releases (after current)
259 for (reverse @$vercols) {
260 last if $_ eq $current;
261 push @future, pop @vers;
262 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
264 splice @vers, -1, 0, @future; # move ahead to decrease precedence
266 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
274 my $row = $caniuse->{data}->{$id};
276 for ($row->{categories}) {
277 my $cell = $_ ? lc $_->[0] : '-';
278 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
279 printf '<th title="%s">%s', join(' + ', @$_), $cell;
283 sprintf('<a href="%s" onclick="%s">%s</a>',
285 sprintf("try { %s; return false } catch(err) { return true }",
286 "document.getElementById('$id').classList.toggle('target')",
291 print '<div class=aside>';
292 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
293 Entity($row->{description}),
294 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
295 printf 'Resources: %s.', join(', ', map {
296 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
297 } @$_) for grep { @$_ } $row->{links} // ();
298 printf '<br>Parent feature: %s.', join(', ', map {
299 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
300 } $_) for $row->{parent} || ();
306 my $row = $caniuse->{data}->{$id};
308 for ($row->{status}) {
309 my $cell = $_ // '-';
310 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
311 printf '<td title="%s" class="l %s">%s',
312 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
317 my ($id, $browser) = @_;
318 my $feature = $caniuse->{data}->{$id};
319 my $data = $feature->{stats}->{$browser};
320 if (ref $data eq 'ARRAY') {
321 # special case for unsupported
322 my $release = $caniuse->{agents}->{$browser}->{verrelease};
324 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
329 for my $ver (@{ $versions{$browser} }, undef) {
331 !defined $ver ? undef : # last column if nameless
332 ref $data ne 'HASH' ? '' : # unclassified if no support hash
333 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
334 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
335 ~~ 'n' && 'n' # first known version is unsupported
338 unless (!defined $prev or $prev ~~ $compare) {
339 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
341 # strip #\d note references from support class
343 push @notes, $feature->{notes_by_num}->{$1}
344 while $prev =~ s/\h \# (\d+) \b//x;
346 # prepare version hover details
347 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
348 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
349 map { $DSTATS{$_} // () }
350 map { split / /, $_ }
353 $title .= "\n".EscapeHTML($_) for @notes;
355 printf('<td class="%s" colspan="%d" title="%s">%s',
358 !$usage ? ('p0') : ('p',
359 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
360 sprintf('p%02d', $usage * ($usagepct - .0001)),
362 sprintf('pp%02d', $usage / $usagemax),
366 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
371 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
378 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
383 featurescore($caniuse->{data}->{$b}->{stats})
384 <=> featurescore($caniuse->{data}->{$a}->{stats})
385 } keys %{ $caniuse->{data} }) {
386 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
387 printf '<tr id="%s">', $id;
390 saybrowsercols($id, $_) for @browsers;
398 # normalised version number comparable as string (cmp)
399 shift =~ /(?:.*-|^)(\d*)(.*)/;
400 # matched (major)(.minor) of last value in range (a-B)
401 return sprintf('%02d', $1 || 99) . $2;
405 my @span = ($_[0], @_>1 ? $_[-1] : ());
406 s/-.*// for $span[0];
412 return join('‒', @span);
419 <table class="glyphs"><tr>
420 <td class="X l5">supported
421 <td class="X l3">partial
422 <td class="X l2">optional
423 <td class="X l1">missing
424 <td class="X l0">unknown
425 <td class="X ex">prefixed
428 <p><: if ($usage) { :>
430 <span class=" p0">0</span> -
431 <span class="p p0 p00">.01</span> -
432 <span class="p p0 p05">1-9</span> -
433 <span class="p p1">10</span> -
434 <span class="p p2">20</span> -
435 <span class="p p5">majority</span>
437 <table class="glyphs"><tr>
438 <td class="p p1">previous version</td>
439 <td class="p p3">current</td>
440 <td class="p p0 p00">upcoming (within months)</td>
441 <td class=" p0">future (within a year)</td>
446 <ul class="legend legend-set">
447 <li>default <strong>style</strong> is
448 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
449 <li><strong>usage</strong> source is
450 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
451 <li>usage <strong>threshold</strong> is
452 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
457 <script type="text/javascript" src="/searchlocal.js"></script>
458 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>