2 use List::Util qw(sum max first);
3 no if $] >= 5.018, warnings => 'experimental::smartmatch';
6 title => 'browser compatibility cheat sheet',
9 "Compatibility table of new web features (HTML5, CSS3, SVG, Javascript)",
10 "comparing support and usage share for all popular browser versions.",
13 web browser support compatibility usage matrix available feature
14 html html5 css css3 svg javascript js dom mobile
15 ie internet explorer firefox chrome safari webkit opera
17 stylesheet => [qw'circus dark mono red light'],
18 data => ['data/browser/support.inc.pl'],
21 say "<h1>Browser compatibility</h1>\n";
23 my $caniuse = do 'data/browser/support.inc.pl' or die $@ || $!;
25 # mark last three (future) versions as unreleased, ensure current isn't
27 $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => 0,
30 } for values %{ $caniuse->{agents} };
48 p => 'plugin required',
51 d => '(disabled by default)',
56 ($caniuse->{agents}->{$_[0]}->{prefix_exceptions} // {})->{$_[1]}
57 // $caniuse->{agents}->{$_[0]}->{prefix} // (),
61 my %PSTATS = ( # score percentage
63 a => .5, 'a x' => .5, 'a d' => .2,
65 n => 0, 'n d' => .2, 'n x d' => .2,
69 unoff => 'l1', # unofficial
71 cr => 'l3', # candidate
72 pr => 'l3', # proposed
73 rec => 'l5', # recommendation
75 ietf => 'l0', # standard
76 other => 'l0', # non-w3
79 while (my ($browser, $row) = each %{ $caniuse->{agents} }) {
80 $versions{$browser} = [
81 sort { paddedver($a) cmp paddedver($b) } grep { defined }
87 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
89 my ($canihas, $usage);
90 my $minusage = $get{threshold} // 1;
91 given ($get{usage} // 'wm') {
95 when (!m{ \A [a-z]\w+ (?:/\d[\d-]*\d)? \z }x) {
97 'Invalid browser usage data request',
98 'Identifier must be alphanumeric name or <q>0</q>.',
101 $canihas = do "data/browser/usage-$_.inc.pl" or do {
102 Alert('Browser usage data not found', $@ || $!);
106 my $ref = $canihas->{-title} || 'unknown';
107 $ref = showlink($ref, $_)
108 for $canihas->{-site} || $canihas->{-source} || ();
109 $ref .= " $_" for $canihas->{-date} || ();
110 print "\nwith $ref browser usage statistics";
114 if ($usage) { # first() does not work inside given >:(
115 # adapt version usage to actual support data
116 my %engineuse; # prefix => usage sum
117 for my $browser (keys %versions) {
118 my $row = $canihas->{$browser} // {};
119 my $verlist = $versions{$browser} or next;
120 if ($minusage and sum(values %$row) < $minusage) {
121 delete $versions{$browser};
124 my %supported = map { $_ => 1 } @$verlist;
126 # cascade unknown versions
127 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
128 while (my ($version, $usage) = each %$row) {
129 next if defined $supported{$version};
130 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
131 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
132 $row->{$next} += $usage;
133 $row->{$version} = 0; # balance browser total
136 # build row list for each version
138 my @vershown; # $verlist replacement
139 my ($rowusage, @verrow) = (0); # replacement row tracking
141 push @verrow, $_; # queue each version
142 if (($rowusage += $row->{$_}) >= $minusage) {
143 push @vershown, [@verrow]; # add row
144 ($rowusage, @verrow) = (0); # reset row tracking
147 push @vershown, \@verrow if @verrow; # always add latest
148 @$verlist = @vershown;
151 @$verlist = map { [$_] } @$verlist;
154 # reusable aggregates (grouped by prefix (engine) and browser)
155 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
156 $row->{-total} = sum(values %$row);
159 # order browser columns by usage grouped by engine
161 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
162 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
164 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
168 # order browser columns by name grouped by engine
169 @{$_} = map { [$_] } @{$_} for values %versions;
171 $caniuse->{agents}->{$b}->{prefix} cmp
172 $caniuse->{agents}->{$a}->{prefix}
185 my $zero = $#$_ - 2; # baseline index
186 ($_->[$zero - 2] => .5), # past
187 ($_->[$zero - 1] => 10 ), # previous
188 ($_->[$zero + 2] => 0 ), # future
189 ($_->[$zero + 1] => .5), # next
190 ($_->[$zero ] => 30 ), # current
191 } $caniuse->{agents}->{$_}->{versions}
194 }; # fallback hash based on release semantics
196 # score multiplier for percentage of all browser versions
197 my $usagepct = 99.99 / sum(
198 map { $_->{-total} // values %{$_} } values %{$canihas}
201 $_->{usage} = featurescore($_->{stats}) * $usagepct
202 for values %{ $caniuse->{data} };
204 print '<table class="mapped">';
205 print '<col span="3">'; # should match first thead row
206 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
207 say '</colgroup><col>';
209 my $header = join('',
211 '<th colspan="3" rowspan="2">feature',
213 my $name = $caniuse->{agents}->{$_}->{browser};
214 sprintf('<th colspan="%d" class="%s" title="%s">%s',
215 scalar @{ $versions{$_} },
216 join(' ', map {"b-a-$_"} grep {$_}
217 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
220 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
224 length $name <= (3 * @{ $versions{$_} }) ? $name
225 : $caniuse->{agents}->{$_}->{abbr};
231 print '<thead>', $header;
232 # preceding row without any colspan to work around gecko bug
234 for my $browser (@browsers) {
235 for my $span (@{ $versions{$browser} }) {
236 my $lastver = first {
237 !defined $caniuse->{agents}->{$browser}->{verrelease}->{$_} # stable
239 printf('<td title="%s"%s>%s',
241 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{ @{$span} }) * $usagepct),
242 'version ' . showversions(@{$span}, undef),
243 $span->[-1] eq $lastver ? () : '(development)',
245 !defined $lastver && ' class="ex"',
246 showversions($lastver // $span->[0]),
251 say '<tfoot>', $header;
253 # prefix indicates browser family; count adjacent families
254 my (@families, %familycount);
255 for my $browser (@browsers) {
256 my $family = $caniuse->{agents}->{$browser}->{prefix};
257 push @families, $family unless $familycount{$family};
258 $familycount{$family} += @{ $versions{$browser} };
261 print "\n", '<tr class="cat">';
262 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
267 # relative amount of support for given feature
269 if (my $row = shift) {
271 while (my ($browser, $versions) = each %$row) {
272 ref $versions eq 'HASH' or next;
273 while (my ($version, $status) = each %$versions) {
274 $status =~ s/\h\#\d+//g;
275 $rank += ($canihas->{$browser}->{$version} || .001) * $PSTATS{$status};
281 while (my ($browser, $vercols) = each %versions) {
282 my $div = 0; # multiplier exponent (decreased to lower value)
283 my @vers = map { $row->{$browser}->{$_} } @$vercols;
284 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
285 my @future; # find upcoming releases (after current)
286 for (reverse @$vercols) {
287 last if $_ eq $current;
288 push @future, pop @vers;
289 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
291 splice @vers, -1, 0, @future; # move ahead to decrease precedence
293 $rank += $PSTATS{$_} * 2**($div--) for reverse @vers;
302 s/\r\n?/\n/g; # windows returns
303 s/\h* $//gmx; # trailing whitespace
304 s/(?<= [^.\n]) $/./gmx; # consistently end each line by a period
306 s{ ` ([^`]*) ` }{<code>$1</code>}gx;
307 s{ \[ ([^]]*) \] \( ([^)]*) \) }{<a href="$2">$1</a>}gx;
316 s{ \[ ([^]]*) \] \( [^)]* \) }{$1}gx; # strip link urls
323 my $row = $caniuse->{data}->{$id};
325 for ($row->{categories}) {
326 my $cell = $_ ? lc $_->[0] : '-';
327 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
328 printf '<th title="%s">%s', join(' + ', @$_), $cell;
332 sprintf('<a href="%s" onclick="%s">%s</a>',
334 sprintf("try { %s; return false } catch(err) { return true }",
335 "document.getElementById('$id').classList.toggle('target')",
340 print '<div class=aside>';
342 for formatnotes($row->{description}, $row->{notes} || ());
343 if (my %notes = %{ $row->{notes_by_num} }) {
344 say '<p>Browser-specific notes:';
345 say "<br>#$_: ", formatnotes($notes{$_}) for sort keys %notes;
348 printf 'Resources: %s.', join(', ', map {
349 showlink($_->{title}, $_->{url})
350 } @$_) for grep { @$_ } $row->{links} // ();
351 printf '<br>Parent feature: %s.', join(', ', map {
352 showlink($caniuse->{data}->{$_}->{title}, "#$_")
353 } $_) for $row->{parent} || ();
359 my $row = $caniuse->{data}->{$id};
361 for ($row->{status}) {
362 my $cell = $_ // '-';
363 $cell = showlink($cell, $_) for $row->{spec} // ();
364 printf '<td title="%s" class="l %s">%s',
365 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
370 my ($id, $browser) = @_;
371 my $feature = $caniuse->{data}->{$id};
372 my $data = $feature->{stats}->{$browser};
373 if (ref $data eq 'ARRAY') {
374 # special case for unsupported
375 my $release = $caniuse->{agents}->{$browser}->{verrelease};
377 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
382 for my $ver (@{ $versions{$browser} }, undef) {
384 !defined $ver ? undef : # last column if nameless
385 ref $data ne 'HASH' ? '' : # unclassified if no support hash
386 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
387 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
388 ~~ 'n' && 'n' # first known version is unsupported
391 unless (!defined $prev or $prev ~~ $compare) {
392 my @vercover = (map { @{$_} } @span);
393 for ($ver ? @{$ver} : ()) {
394 $data->{$_} eq $data->{$vercover[-1]} or last;
395 push @vercover, $_; # matches from next span start
397 my $usage = sum(@{ $canihas->{$browser} }{@vercover});
399 # strip #\d note references from support class
401 push @notes, $feature->{notes_by_num}->{$1}
402 while $prev =~ s/\h \# (\d+) \b//x;
404 # prepare version hover details
405 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(' ',
406 (map { ref $_ eq 'CODE' ? $_->($browser, $vercover[0]) : $_ }
407 map { $DSTATS{$_} // () }
408 map { split / /, $_ }
411 'in', $caniuse->{agents}->{$browser}->{abbr},
412 showversions(@vercover, undef),
414 $title .= "\n$_" for notestotitle(@notes);
416 printf('<td class="%s" colspan="%d" title="%s">%s',
419 !$usage ? ('p0') : ('p',
420 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
421 sprintf('p%02d', $usage * ($usagepct - .0001)),
426 showversions($span[0]->[0], @span > 1 ? $span[-1]->[-1] : ()),
431 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
438 print '<td>', int $caniuse->{data}->{$id}->{usage};
443 $caniuse->{data}->{$b}->{usage} <=> $caniuse->{data}->{$a}->{usage}
444 } keys %{ $caniuse->{data} }) {
445 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
446 printf '<tr id="%s">', $id;
449 saybrowsercols($id, $_) for @browsers;
457 # normalised version number comparable as string (cmp)
458 $_[0] =~ m/(?:.*-|^)(\d*)(.*)/;
459 # matched (major)(.minor) of last value in range (a-B)
460 return sprintf('%02d', length $1 ? $1 : 99) . $2;
464 # title to describe minumum version and optional maximum for multiple cells
465 my @span = (map { split /-/ } grep { defined } @_);
466 return $span[0] =~ s/\.0\z//r if @_ <= 1;
468 return join('‒', @span);
475 <table class="glyphs"><tr>
476 <td class="X l5">supported
477 <td class="X l3">partial
478 <td class="X l2">optional
479 <td class="X l1">missing
480 <td class="X l0">unknown
481 <td class="X ex">prefixed
484 <p><: if ($usage) { :>
486 <span class=" p0">0</span> -
487 <span class="p p0 p00">.01</span> -
488 <span class="p p0 p05">1-9</span> -
489 <span class="p p1">10</span> -
490 <span class="p p2">20</span> -
491 <span class="p p5">majority</span>
493 <table class="glyphs"><tr>
494 <td class="p p1">previous version</td>
495 <td class="p p3">current</td>
496 <td class="p p0 p00">upcoming (within months)</td>
497 <td class=" p0">future (within a year)</td>
502 <ul class="legend legend-set">
503 <li>default <strong>style</strong> is
504 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
505 <li><strong>usage</strong> source is
506 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
507 <li>usage <strong>threshold</strong> is
508 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
513 <script type="text/javascript" src="/searchlocal.js"></script>
514 <script type="text/javascript"><!--
515 prependsearch(document.getElementById('intro'));