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 two (future) versions as unreleased, ensure current isn't
27 map { $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => undef } $_->{versions}
28 } for values %{ $caniuse->{agents} };
45 p => 'plugin required',
46 j => 'javascript required',
52 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
57 unoff => 'l1', # unofficial
59 cr => 'l4', # candidate
60 pr => 'l4', # proposed
61 rec => 'l5', # recommendation
62 other => 'l2', # non-w3
63 ietf => 'l5', # standard
66 if (my ($somerow) = values %{ $caniuse->{data} }) {
67 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
68 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
71 my @browsers = keys %versions;
74 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
76 my ($canihas, $usage, $minusage);
77 given ($get{usage} // 'wm') {
81 when (!/^[a-z][\w-]+$/) {
82 printf "<p>Invalid browser usage data request: <em>%s</em>",
83 'identifier must be alphanumeric name or <q>0</q>';
85 $canihas = do "browser-usage-$_.inc.pl" or do {
86 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
90 my $ref = $canihas->{-title} || 'unknown';
91 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
92 for $canihas->{-site} || $canihas->{-source} || ();
93 $ref .= " $_" for $canihas->{-date} || ();
94 print "\nwith $ref browser usage statistics";
96 if ($usage) { # first() does not work inside given >:(
97 # adapt version usage to actual support data
98 my %engineuse; # prefix => usage sum
99 while (my ($browser, $row) = each %$canihas) {
100 my $verlist = $versions{$browser} or next;
101 my %supported = map { $_ => 1 } @$verlist;
103 # cascade unknown versions
104 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
105 while (my ($version, $usage) = each %$row) {
106 next if defined $supported{$version};
107 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
108 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
109 $row->{$next} += $usage;
110 $row->{$version} = 0; # balance browser total
113 # build row list for each version
114 if ($minusage = $get{threshold} // .2) {
115 my @vershown; # $verlist replacement
116 my ($rowusage, @verrow) = (0); # replacement row tracking
118 push @verrow, $_; # queue each version
119 if (($rowusage += $row->{$_}) >= $minusage) {
120 push @vershown, [@verrow]; # add row
121 ($rowusage, @verrow) = (0); # reset row tracking
124 push @vershown, \@verrow if @verrow; # always add latest
125 @$verlist = @vershown;
128 @$verlist = map { [$_] } @$verlist;
131 # reusable aggregates (grouped by prefix (engine) and browser)
132 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
133 $row->{-total} = sum(values %$row);
136 # order browser columns by usage grouped by engine
138 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
139 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
141 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
145 # order browser columns by name grouped by engine
147 $caniuse->{agents}->{$b}->{prefix} cmp
148 $caniuse->{agents}->{$a}->{prefix}
161 my $zero = $#$_ - 2; # baseline index
162 ($_->[$zero - 2] => .5), # past
163 ($_->[$zero - 1] => 10 ), # previous
164 ($_->[$zero + 2] => 0 ), # future
165 ($_->[$zero + 1] => .5), # next
166 ($_->[$zero ] => 30 ), # current
167 } $caniuse->{agents}->{$_}->{versions}
170 }; # fallback hash based on release semantics
171 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
173 my $usagepct = 1; # score multiplier for 0..100 result
174 # normalise usage percentage to only include shown browsers
175 $usagepct = 100.01 / featurescore({ # yes for every possible version
176 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
179 print '<table class="mapped">';
180 print '<col span="3">'; # should match first thead row
181 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
182 say '</colgroup><col>';
184 my $header = join('',
186 '<th colspan="3">feature',
188 my $name = $caniuse->{agents}->{$_}->{browser};
189 sprintf('<th colspan="%d" class="%s" title="%s">%s',
190 scalar @{ $versions{$_} },
191 join(' ', map {"b-a-$_"} grep {$_}
192 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
195 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
199 length $name < 3 + @{ $versions{$_} }*2 ? $name
200 : $caniuse->{agents}->{$_}->{abbr};
206 print '<thead>', $header;
207 # preceding row without any colspan to work around gecko bug
210 for my $browser (@browsers) {
211 for (@{ $versions{$browser} }) {
212 my $lastver = $_->[-1];
213 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
214 my $future = defined $release;
215 printf('<td title="%s"%s>%s',
217 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
218 $future ? 'development' : (),
219 'version ' . join(', ', @{$_}),
221 $future && ' class="ex"',
222 showversions($lastver),
228 say '<tfoot>', $header, '</tfoot>';
231 # relative amount of support for given feature
232 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
234 if (my $row = shift) {
236 while (my ($browser, $versions) = each %$row) {
237 ref $versions eq 'HASH' or next;
238 while (my ($version, $_) = each %$versions) {
239 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
245 while (my ($browser, $vercols) = each %versions) {
246 my $div = 0; # multiplier exponent (decreased to lower value)
247 my @vers = map { $row->{$browser}->{$_} } @$vercols;
248 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
249 my @future; # find upcoming releases (after current)
250 for (reverse @$vercols) {
251 last if $_ eq $current;
252 push @future, pop @vers;
253 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
255 splice @vers, -1, 0, @future; # move ahead to decrease precedence
257 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
265 my $row = $caniuse->{data}->{$id};
267 for ($row->{categories}) {
268 my $cell = $_ ? lc $_->[0] : '-';
269 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
270 printf '<th title="%s">%s', join(' + ', @$_), $cell;
274 sprintf('<a href="%s" onclick="%s">%s</a>',
276 sprintf("try { %s; return false } catch(err) { return true }",
277 "document.getElementById('$id').classList.toggle('target')",
282 print '<div class=aside>';
283 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
284 Entity($row->{description}),
285 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
286 printf 'Resources: %s.', join(', ', map {
287 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
288 } @$_) for grep { @$_ } $row->{links} // ();
289 printf '<br>Parent feature: %s.', join(', ', map {
290 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
291 } $_) for $row->{parent} || ();
297 my $row = $caniuse->{data}->{$id};
299 for ($row->{status}) {
300 my $cell = $_ // '-';
301 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
302 printf '<td title="%s" class="l %s">%s',
303 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
308 my ($id, $browser) = @_;
309 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
310 if (ref $data eq 'ARRAY') {
311 # special case for unsupported
312 my $release = $caniuse->{agents}->{$browser}->{verrelease};
314 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
319 for my $ver (@{ $versions{$browser} }, undef) {
321 !defined $ver ? undef : # last column if nameless
322 ref $data ne 'HASH' ? '' : # unclassified if no support hash
323 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
324 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
325 ~~ 'n' && 'n' # first known version is unsupported
328 unless (!defined $prev or $prev ~~ $compare) {
329 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
330 printf '<td class="%s" colspan="%d" title="%s">%s',
333 !$usage ? ('p0') : ('p',
334 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
335 sprintf('p%02d', $usage * ($usagepct - .0001)),
337 sprintf('pp%02d', $usage / $usagemax),
340 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
341 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
342 map { $DSTATS{$_} // () }
343 map { split / /, $_ }
346 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
350 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
357 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
362 featurescore($caniuse->{data}->{$b}->{stats})
363 <=> featurescore($caniuse->{data}->{$a}->{stats})
364 } keys %{ $caniuse->{data} }) {
365 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
366 printf '<tr id="%s">', $id;
369 saybrowsercols($id, $_) for @browsers;
377 # normalised version number comparable as string (cmp)
378 shift =~ /(?:.*-|^)(\d*)(.*)/;
379 # matched (major)(.minor) of last value in range (a-B)
380 return sprintf('%02d', $1 || 0) . $2;
384 my @span = ($_[0], @_>1 ? $_[-1] : ());
385 s/-.*// for $span[0];
391 return join('‒', @span);
398 <table class="glyphs"><tr>
399 <td class="X l5">supported
400 <td class="X l3">partial
401 <td class="X l2">external (js/plugin)
402 <td class="X l1">missing
403 <td class="X l0">unknown
404 <td class="X ex">prefixed
407 <p><: if ($usage) { :>
409 <span class=" p0">0</span> -
410 <span class="p p0 p00">.01</span> -
411 <span class="p p0 p05">1-9</span> -
412 <span class="p p1">10</span> -
413 <span class="p p2">20</span> -
414 <span class="p p5">majority</span>
416 <table class="glyphs"><tr>
417 <td class="p p1">previous version</td>
418 <td class="p p3">current</td>
419 <td class="p p0 p00">upcoming (within months)</td>
420 <td class=" p0">future (within a year)</td>
425 <ul class="legend legend-set">
426 <li>default <strong>style</strong> is
427 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
428 <li><strong>usage</strong> source is
429 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
430 <li>usage <strong>threshold</strong> is
431 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
436 <script type="text/javascript" src="/searchlocal.js"></script>
437 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>