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 matrix 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 => ['data/browser/support.inc.pl'],
22 say "<h1>Browser compatibility</h1>\n";
24 my $caniuse = do 'data/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 => 'l3', # candidate
68 pr => 'l3', # proposed
69 rec => 'l5', # recommendation
71 ietf => 'l0', # standard
72 other => 'l0', # non-w3
75 if (my ($somerow) = values %{ $caniuse->{data} }) {
76 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
77 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
82 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
84 my ($canihas, $usage);
85 my $minusage = $get{threshold} // .7;
86 given ($get{usage} // 'wm') {
90 when (!m{ \A [a-z]\w+ (?:/\d[\d-]*\d)? \z }x) {
91 printf "<p>Invalid browser usage data request: <em>%s</em>",
92 'identifier must be alphanumeric name or <q>0</q>';
94 $canihas = do "data/browser/usage-$_.inc.pl" or do {
95 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
99 my $ref = $canihas->{-title} || 'unknown';
100 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
101 for $canihas->{-site} || $canihas->{-source} || ();
102 $ref .= " $_" for $canihas->{-date} || ();
103 print "\nwith $ref browser usage statistics";
107 if ($usage) { # first() does not work inside given >:(
108 # adapt version usage to actual support data
109 my %engineuse; # prefix => usage sum
110 for my $browser (keys %versions) {
111 my $row = $canihas->{$browser} // {};
112 my $verlist = $versions{$browser} or next;
113 if ($minusage and sum(values %$row) < $minusage) {
114 delete $versions{$browser};
117 my %supported = map { $_ => 1 } @$verlist;
119 # cascade unknown versions
120 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
121 while (my ($version, $usage) = each %$row) {
122 next if defined $supported{$version};
123 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
124 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
125 $row->{$next} += $usage;
126 $row->{$version} = 0; # balance browser total
129 # build row list for each version
131 my @vershown; # $verlist replacement
132 my ($rowusage, @verrow) = (0); # replacement row tracking
134 push @verrow, $_; # queue each version
135 if (($rowusage += $row->{$_}) >= $minusage) {
136 push @vershown, [@verrow]; # add row
137 ($rowusage, @verrow) = (0); # reset row tracking
140 push @vershown, \@verrow if @verrow; # always add latest
141 @$verlist = @vershown;
144 @$verlist = map { [$_] } @$verlist;
147 # reusable aggregates (grouped by prefix (engine) and browser)
148 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
149 $row->{-total} = sum(values %$row);
152 # order browser columns by usage grouped by engine
154 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
155 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
157 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
161 # order browser columns by name grouped by engine
162 @{$_} = map { [$_] } @{$_} for values %versions;
164 $caniuse->{agents}->{$b}->{prefix} cmp
165 $caniuse->{agents}->{$a}->{prefix}
178 my $zero = $#$_ - 2; # baseline index
179 ($_->[$zero - 2] => .5), # past
180 ($_->[$zero - 1] => 10 ), # previous
181 ($_->[$zero + 2] => 0 ), # future
182 ($_->[$zero + 1] => .5), # next
183 ($_->[$zero ] => 30 ), # current
184 } $caniuse->{agents}->{$_}->{versions}
187 }; # fallback hash based on release semantics
188 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
190 my $usagepct = 1; # score multiplier for 0..100 result
191 # normalise usage percentage to only include shown browsers
192 $usagepct = 100.01 / featurescore({ # yes for every possible version
193 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
196 print '<table class="mapped">';
197 print '<col span="3">'; # should match first thead row
198 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
199 say '</colgroup><col>';
201 my $header = join('',
203 '<th colspan="3" rowspan="2">feature',
205 my $name = $caniuse->{agents}->{$_}->{browser};
206 sprintf('<th colspan="%d" class="%s" title="%s">%s',
207 scalar @{ $versions{$_} },
208 join(' ', map {"b-a-$_"} grep {$_}
209 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
212 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
216 length $name <= (3 * @{ $versions{$_} }) ? $name
217 : $caniuse->{agents}->{$_}->{abbr};
223 print '<thead>', $header;
224 # preceding row without any colspan to work around gecko bug
226 for my $browser (@browsers) {
227 for (@{ $versions{$browser} }) {
228 my $lastver = $_->[-1];
229 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
230 my $future = defined $release;
231 printf('<td title="%s"%s>%s',
233 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
234 $future ? 'development' : (),
235 'version ' . join(', ', @{$_}),
237 $future && ' class="ex"',
238 showversions($lastver),
243 say '<tfoot>', $header;
245 # prefix indicates browser family; count adjacent families
246 my (@families, %familycount);
247 for my $browser (@browsers) {
248 my $family = $caniuse->{agents}->{$browser}->{prefix};
249 push @families, $family unless $familycount{$family};
250 $familycount{$family} += @{ $versions{$browser} };
253 print "\n", '<tr class="cat">';
254 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
259 # relative amount of support for given feature
260 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
262 if (my $row = shift) {
264 while (my ($browser, $versions) = each %$row) {
265 ref $versions eq 'HASH' or next;
266 while (my ($version, $_) = each %$versions) {
267 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
273 while (my ($browser, $vercols) = each %versions) {
274 my $div = 0; # multiplier exponent (decreased to lower value)
275 my @vers = map { $row->{$browser}->{$_} } @$vercols;
276 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
277 my @future; # find upcoming releases (after current)
278 for (reverse @$vercols) {
279 last if $_ eq $current;
280 push @future, pop @vers;
281 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
283 splice @vers, -1, 0, @future; # move ahead to decrease precedence
285 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
293 my $row = $caniuse->{data}->{$id};
295 for ($row->{categories}) {
296 my $cell = $_ ? lc $_->[0] : '-';
297 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
298 printf '<th title="%s">%s', join(' + ', @$_), $cell;
302 sprintf('<a href="%s" onclick="%s">%s</a>',
304 sprintf("try { %s; return false } catch(err) { return true }",
305 "document.getElementById('$id').classList.toggle('target')",
310 print '<div class=aside>';
311 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
312 Entity($row->{description}),
313 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
314 printf 'Resources: %s.', join(', ', map {
315 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
316 } @$_) for grep { @$_ } $row->{links} // ();
317 printf '<br>Parent feature: %s.', join(', ', map {
318 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
319 } $_) for $row->{parent} || ();
325 my $row = $caniuse->{data}->{$id};
327 for ($row->{status}) {
328 my $cell = $_ // '-';
329 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
330 printf '<td title="%s" class="l %s">%s',
331 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
336 my ($id, $browser) = @_;
337 my $feature = $caniuse->{data}->{$id};
338 my $data = $feature->{stats}->{$browser};
339 if (ref $data eq 'ARRAY') {
340 # special case for unsupported
341 my $release = $caniuse->{agents}->{$browser}->{verrelease};
343 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
348 for my $ver (@{ $versions{$browser} }, undef) {
350 !defined $ver ? undef : # last column if nameless
351 ref $data ne 'HASH' ? '' : # unclassified if no support hash
352 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
353 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
354 ~~ 'n' && 'n' # first known version is unsupported
357 unless (!defined $prev or $prev ~~ $compare) {
358 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
360 # strip #\d note references from support class
362 push @notes, $feature->{notes_by_num}->{$1}
363 while $prev =~ s/\h \# (\d+) \b//x;
365 # prepare version hover details
366 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
367 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
368 map { $DSTATS{$_} // () }
369 map { split / /, $_ }
372 $title .= "\n".EscapeHTML($_) for @notes;
374 printf('<td class="%s" colspan="%d" title="%s">%s',
377 !$usage ? ('p0') : ('p',
378 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
379 sprintf('p%02d', $usage * ($usagepct - .0001)),
381 sprintf('pp%02d', $usage / $usagemax),
385 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
390 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
397 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
402 featurescore($caniuse->{data}->{$b}->{stats})
403 <=> featurescore($caniuse->{data}->{$a}->{stats})
404 } keys %{ $caniuse->{data} }) {
405 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
406 printf '<tr id="%s">', $id;
409 saybrowsercols($id, $_) for @browsers;
417 # normalised version number comparable as string (cmp)
418 shift =~ /(?:.*-|^)(\d*)(.*)/;
419 # matched (major)(.minor) of last value in range (a-B)
420 return sprintf('%02d', $1 || 99) . $2;
424 my @span = ($_[0], @_>1 ? $_[-1] : ());
425 s/-.*// for $span[0];
431 return join('‒', @span);
438 <table class="glyphs"><tr>
439 <td class="X l5">supported
440 <td class="X l3">partial
441 <td class="X l2">optional
442 <td class="X l1">missing
443 <td class="X l0">unknown
444 <td class="X ex">prefixed
447 <p><: if ($usage) { :>
449 <span class=" p0">0</span> -
450 <span class="p p0 p00">.01</span> -
451 <span class="p p0 p05">1-9</span> -
452 <span class="p p1">10</span> -
453 <span class="p p2">20</span> -
454 <span class="p p5">majority</span>
456 <table class="glyphs"><tr>
457 <td class="p p1">previous version</td>
458 <td class="p p3">current</td>
459 <td class="p p0 p00">upcoming (within months)</td>
460 <td class=" p0">future (within a year)</td>
465 <ul class="legend legend-set">
466 <li>default <strong>style</strong> is
467 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
468 <li><strong>usage</strong> source is
469 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
470 <li>usage <strong>threshold</strong> is
471 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
476 <script type="text/javascript" src="/searchlocal.js"></script>
477 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>