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);
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 # reusable aggregates (grouped by prefix (engine) and browser)
114 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
115 $row->{-total} = sum(values %$row);
118 # order browser columns by usage grouped by engine
120 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
121 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
123 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
127 # order browser columns by name grouped by engine
129 $caniuse->{agents}->{$b}->{prefix} cmp
130 $caniuse->{agents}->{$a}->{prefix}
143 my $zero = $#$_ - 2; # baseline index
144 ($_->[$zero - 2] => .5), # past
145 ($_->[$zero - 1] => 10 ), # previous
146 ($_->[$zero + 2] => 0 ), # future
147 ($_->[$zero + 1] => .5), # next
148 ($_->[$zero ] => 30 ), # current
149 } $caniuse->{agents}->{$_}->{versions}
152 }; # fallback hash based on release semantics
153 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
155 my $usagepct = 1; # score multiplier for 0..100 result
156 # normalise usage percentage to only include shown browsers
157 $usagepct = 100.01 / featurescore({ # yes for every possible version
158 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
161 print '<table class="mapped">';
162 print '<col span="3">'; # should match first thead row
163 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
164 say '</colgroup><col>';
166 my $header = join('',
168 '<th colspan="3">feature',
170 my $name = $caniuse->{agents}->{$_}->{browser};
171 sprintf('<th colspan="%d" class="%s" title="%s">%s',
172 scalar @{ $versions{$_} },
173 join(' ', map {"b-a-$_"} grep {$_}
174 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
177 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
181 length $name < 3 + @{ $versions{$_} }*2 ? $name
182 : $caniuse->{agents}->{$_}->{abbr};
188 print '<thead>', $header;
189 # preceding row without any colspan to work around gecko bug
192 for my $browser (@browsers) {
193 for my $_ (@{ $versions{$browser} }) {
194 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$_};
195 my $future = defined $release;
196 printf('<td title="%s"%s>%s',
198 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
199 $future ? 'development' : (),
202 $future && ' class="ex"',
209 say '<tfoot>', $header, '</tfoot>';
212 # relative amount of support for given feature
213 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
215 if (my $row = shift) {
217 while (my ($browser, $versions) = each %$row) {
218 ref $versions eq 'HASH' or next;
219 while (my ($version, $_) = each %$versions) {
220 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
226 while (my ($browser, $vercols) = each %versions) {
227 my $div = 0; # multiplier exponent (decreased to lower value)
228 my @vers = map { $row->{$browser}->{$_} } @$vercols;
229 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
230 my @future; # find upcoming releases (after current)
231 for (reverse @$vercols) {
232 last if $_ eq $current;
233 push @future, pop @vers;
234 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
236 splice @vers, -1, 0, @future; # move ahead to decrease precedence
238 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
246 my $row = $caniuse->{data}->{$id};
248 for ($row->{categories}) {
249 my $cell = $_ ? lc $_->[0] : '-';
250 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
251 printf '<th title="%s">%s', join(' + ', @$_), $cell;
255 sprintf('<a href="%s" onclick="%s">%s</a>',
257 sprintf("try { %s; return false } catch(err) { return true }",
258 "document.getElementById('$id').classList.toggle('target')",
263 print '<div class=aside>';
264 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
265 Entity($row->{description}),
266 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
267 printf 'Resources: %s.', join(', ', map {
268 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
269 } @$_) for grep { @$_ } $row->{links} // ();
270 printf '<br>Parent feature: %s.', join(', ', map {
271 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
272 } $_) for $row->{parent} || ();
278 my $row = $caniuse->{data}->{$id};
280 for ($row->{status}) {
281 my $cell = $_ // '-';
282 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
283 printf '<td title="%s" class="l %s">%s',
284 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
289 my ($id, $browser) = @_;
290 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
291 if (ref $data eq 'ARRAY') {
292 # special case for unsupported
293 my $release = $caniuse->{agents}->{$browser}->{verrelease};
295 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
300 for my $ver (@{ $versions{$browser} }, undef) {
302 !defined $ver ? undef : # last column if nameless
303 ref $data ne 'HASH' ? '' : # unclassified if no support hash
304 $data->{$ver} // $prev # known or inherit from predecessor
305 // (grep { defined } @{$data}{ @{ $versions{$browser} } })[0]
306 ~~ 'n' && 'n' # first known version is unsupported
309 unless (!defined $prev or $prev ~~ $compare) {
310 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
311 printf '<td class="%s" colspan="%d" title="%s">%s',
314 !$usage ? ('p0') : ('p',
315 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
316 sprintf('p%02d', $usage * ($usagepct - .0001)),
318 sprintf('pp%02d', $usage / $usagemax),
321 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
322 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
323 map { $DSTATS{$_} // () }
324 map { split / /, $_ }
338 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
343 featurescore($caniuse->{data}->{$b}->{stats})
344 <=> featurescore($caniuse->{data}->{$a}->{stats})
345 } keys %{ $caniuse->{data} }) {
346 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
347 printf '<tr id="%s">', $id;
350 saybrowsercols($id, $_) for @browsers;
358 # normalised version number comparable as string (cmp)
359 shift =~ /(?:.*-|^)(\d*)(.*)/;
360 # matched (major)(.minor) of last value in range (a-B)
361 return sprintf('%02d', $1 || 0) . $2;
365 my @span = ($_[0], @_>1 ? $_[-1] : ());
366 s/-.*// for $span[0];
372 return join('‒', @span);
379 <table class="glyphs"><tr>
380 <td class="X l5">supported
381 <td class="X l3">partial
382 <td class="X l2">external (js/plugin)
383 <td class="X l1">missing
384 <td class="X l0">unknown
385 <td class="X ex">prefixed
388 <p><: if ($usage) { :>
390 <span class=" p0">0</span> -
391 <span class="p p0 p00">.01</span> -
392 <span class="p p0 p05">1-9</span> -
393 <span class="p p1">10</span> -
394 <span class="p p2">20</span> -
395 <span class="p p5">majority</span>
397 <table class="glyphs"><tr>
398 <td class="p p1">previous version</td>
399 <td class="p p3">current</td>
400 <td class="p p0 p00">upcoming (within months)</td>
401 <td class=" p0">future (within a year)</td>
406 <ul class="legend legend-set">
407 <li>default <strong>style</strong> is
408 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
409 <li><strong>usage</strong> source is
410 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
415 <script type="text/javascript" src="/searchlocal.js"></script>
416 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>