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}
134 my $zero = $#$_ - 2; # baseline index
135 ($_->[$zero - 2] => .5), # past
136 ($_->[$zero - 1] => 10 ), # previous
137 ($_->[$zero + 2] => 0 ), # future
138 ($_->[$zero + 1] => .5), # next
139 ($_->[$zero ] => 30 ), # current
140 } $caniuse->{agents}->{$_}->{versions}
143 }; # fallback hash based on release semantics
144 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
146 my $usagepct = 1; # score multiplier for 0..100 result
147 # normalise usage percentage to only include shown browsers
148 $usagepct = 100 / featurescore({ # yes for every possible version
149 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
152 print '<table class="mapped">';
153 print '<col span="3">'; # should match first thead row
154 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
155 say '</colgroup><col>';
157 my $header = join('',
159 '<th colspan="3">feature',
161 my $name = $caniuse->{agents}->{$_}->{browser};
162 sprintf('<th colspan="%d" class="%s" title="%s">%s',
163 scalar @{ $versions{$_} },
164 join(' ', map {"b-a-$_"} grep {$_}
165 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
168 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
172 length $name < 3 + @{ $versions{$_} }*2 ? $name
173 : $caniuse->{agents}->{$_}->{abbr};
179 print '<thead>', $header;
180 # preceding row without any colspan to work around gecko bug
183 for my $browser (@browsers) {
184 for my $_ (@{ $versions{$browser} }) {
185 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$_};
186 my $future = defined $release;
187 printf('<td title="%s"%s>%s',
189 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
190 $future ? 'development' : (),
193 $future && ' class="ex"',
200 say '<tfoot>', $header, '</tfoot>';
203 # relative amount of support for given feature
204 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
206 if (my $row = shift) {
208 while (my ($browser, $versions) = each %$row) {
209 ref $versions eq 'HASH' or next;
210 while (my ($version, $_) = each %$versions) {
211 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
217 while (my ($browser, $vercols) = each %versions) {
218 my $div = 0; # multiplier exponent (decreased to lower value)
219 my @vers = map { $row->{$browser}->{$_} } @$vercols;
220 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
221 my @future; # find upcoming releases (after current)
222 for (reverse @$vercols) {
223 last if $_ eq $current;
224 push @future, pop @vers;
225 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
227 splice @vers, -1, 0, @future; # move ahead to decrease precedence
229 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
237 my $row = $caniuse->{data}->{$id};
239 for ($row->{categories}) {
240 my $cell = $_ ? lc $_->[0] : '-';
241 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
242 printf '<th title="%s">%s', join(' + ', @$_), $cell;
246 sprintf('<a href="%s" onclick="%s">%s</a>',
248 sprintf("try { %s; return false } catch(err) { return true }",
249 "document.getElementById('$id').classList.toggle('target')",
254 print '<div class=aside>';
255 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
256 Entity($row->{description}),
257 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
258 printf 'Resources: %s.', join(', ', map {
259 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
260 } @$_) for grep { @$_ } $row->{links} // ();
261 printf '<br>Parent feature: %s.', join(', ', map {
262 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
263 } $_) for $row->{parent} || ();
269 my $row = $caniuse->{data}->{$id};
271 for ($row->{status}) {
272 my $cell = $_ // '-';
273 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
274 printf '<td title="%s" class="l %s">%s',
275 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
280 my ($id, $browser) = @_;
281 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
282 if (ref $data eq 'ARRAY') {
283 # special case for unsupported
284 my $release = $caniuse->{agents}->{$browser}->{verrelease};
286 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
291 for my $ver (@{ $versions{$browser} }, undef) {
293 !defined $ver ? undef : # last column if nameless
294 ref $data ne 'HASH' ? '' : # unclassified if no support hash
295 $data->{$ver} // $prev # known or inherit from predecessor
296 // (grep { defined } @{$data}{ @{ $versions{$browser} } })[0]
297 ~~ 'n' && 'n' # first known version is unsupported
300 unless (!defined $prev or $prev ~~ $compare) {
301 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
302 printf '<td class="%s" colspan="%d" title="%s">%s',
305 !$usage ? ('p0') : ('p',
306 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
307 sprintf('p%02d', $usage * ($usagepct - .0001)),
309 sprintf('pp%02d', $usage / $usagemax),
312 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
313 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
314 map { $DSTATS{$_} // () }
315 map { split / /, $_ }
329 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
334 featurescore($caniuse->{data}->{$b}->{stats})
335 <=> featurescore($caniuse->{data}->{$a}->{stats})
336 } keys %{ $caniuse->{data} }) {
337 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
338 printf '<tr id="%s">', $id;
341 saybrowsercols($id, $_) for @browsers;
349 # normalised version number comparable as string (cmp)
350 shift =~ /(?:.*-|^)(\d*)(.*)/;
351 # matched (major)(.minor) of last value in range (a-B)
352 return sprintf('%02d', $1 || 0) . $2;
356 my @span = ($_[0], @_>1 ? $_[-1] : ());
357 s/-.*// for $span[0];
363 return join('‒', @span);
370 <table class="glyphs"><tr>
371 <td class="X l5">supported
372 <td class="X l3">partial
373 <td class="X l2">external (js/plugin)
374 <td class="X l1">missing
375 <td class="X l0">unknown
376 <td class="X ex">prefixed
379 <p><: if ($usage) { :>
381 <span class=" p0">0</span> -
382 <span class="p p0 p00">.01</span> -
383 <span class="p p0 p05">1-9</span> -
384 <span class="p p1">10</span> -
385 <span class="p p2">20</span> -
386 <span class="p p5">majority</span>
388 <table class="glyphs"><tr>
389 <td class="p p1">previous version</td>
390 <td class="p p3">current</td>
391 <td class="p p0 p00">upcoming (within months)</td>
392 <td class=" p0">future (within a year)</td>
397 <ul class="legend legend-set">
398 <li>default <strong>style</strong> is
399 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
400 <li><strong>usage</strong> source is
401 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
406 <script type="text/javascript" src="/searchlocal.js"></script>
407 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>