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 = grep { $versions{$_} }
72 qw(trident gecko webkit_saf ios_saf webkit_chr android presto op_mob op_mini);
75 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
77 my ($canihas, $usage);
78 given ($get{usage} // 'wm') {
82 when (!/^[a-z][\w-]+$/) {
83 printf "<p>Invalid browser usage data request: <em>%s</em>",
84 'identifier must be alphanumeric name or <q>0</q>';
86 $canihas = do "browser-usage-$_.inc.pl" or do {
87 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
91 my $ref = $canihas->{-title} || 'unknown';
92 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
93 for $canihas->{-site} || $canihas->{-source} || ();
94 $ref .= " $_" for $canihas->{-date} || ();
95 print "\nwith $ref browser usage statistics";
98 # first() does not work inside given >:(
99 while (my ($browser, $row) = each %$canihas) {
100 my $verlist = $versions{$browser} or next;
101 my %supported = map { $_ => 1 } @$verlist;
102 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
103 while (my ($version, $usage) = each %$row) {
104 next if defined $supported{$version};
105 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
106 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
107 $row->{$next} += $usage;
108 $row->{$version} = 0; # balance browser total
120 my $zero = $#$_ - 2; # baseline index
121 ($_->[$zero - 2] => .5), # past
122 ($_->[$zero - 1] => 10 ), # previous
123 ($_->[$zero + 2] => 0 ), # future
124 ($_->[$zero + 1] => .5), # next
125 ($_->[$zero ] => 30 ), # current
126 } $caniuse->{agents}->{$_}->{versions}
129 }; # fallback hash based on release semantics
130 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
132 my $usagepct = 1; # score multiplier for 0..100 result
133 # normalise usage percentage to only include shown browsers
134 $usagepct = 100 / featurescore({ # yes for every possible version
135 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
138 print '<table class="mapped">';
139 print '<col span="3">'; # should match first thead row
140 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
141 say '</colgroup><col>';
143 my $header = join('',
145 '<th colspan="3">feature',
147 my $name = $caniuse->{agents}->{$_}->{browser};
148 sprintf('<th colspan="%d" class="%s" title="%s">%s',
149 scalar @{ $versions{$_} },
150 join(' ', map {"b-a-$_"} grep {$_}
151 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
154 sprintf('%.1f%%', sum(values %{ $canihas->{$_} }) * $usagepct),
158 length $name < 3 + @{ $versions{$_} }*2 ? $name
159 : $caniuse->{agents}->{$_}->{abbr};
165 print '<thead>', $header;
166 # preceding row without any colspan to work around gecko bug
169 for my $browser (@browsers) {
170 for my $_ (@{ $versions{$browser} }) {
171 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$_};
172 my $future = defined $release;
173 printf('<td title="%s"%s>%s',
175 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
176 $future ? 'development' : (),
179 $future && ' class="ex"',
186 say '<tfoot>', $header, '</tfoot>';
189 # relative amount of support for given feature
190 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
192 if (my $row = shift) {
194 while (my ($browser, $versions) = each %$row) {
195 ref $versions eq 'HASH' or next;
196 while (my ($version, $_) = each %$versions) {
197 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
203 while (my ($browser, $vercols) = each %versions) {
204 my $div = 0; # multiplier exponent (decreased to lower value)
205 my @vers = map { $row->{$browser}->{$_} } @$vercols;
206 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
207 my @future; # find upcoming releases (after current)
208 for (reverse @$vercols) {
209 last if $_ eq $current;
210 push @future, pop @vers;
211 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
213 splice @vers, -1, 0, @future; # move ahead to decrease precedence
215 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
223 my $row = $caniuse->{data}->{$id};
225 for ($row->{categories}) {
226 my $cell = $_ ? lc $_->[0] : '-';
227 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
228 printf '<th title="%s">%s', join(' + ', @$_), $cell;
232 sprintf('<a href="%s" onclick="%s">%s</a>',
234 sprintf("try { %s; return false } catch(err) { return true }",
235 "document.getElementById('$id').classList.toggle('target')",
240 print '<div class=aside>';
241 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
242 Entity($row->{description}),
243 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
244 printf 'Resources: %s.', join(', ', map {
245 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
246 } @$_) for grep { @$_ } $row->{links} // ();
247 printf '<br>Parent feature: %s.', join(', ', map {
248 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
249 } $_) for $row->{parent} || ();
255 my $row = $caniuse->{data}->{$id};
257 for ($row->{status}) {
258 my $cell = $_ // '-';
259 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
260 printf '<td title="%s" class="l %s">%s',
261 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
266 my ($id, $browser) = @_;
267 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
268 if (ref $data eq 'ARRAY') {
269 # special case for unsupported
270 my $release = $caniuse->{agents}->{$browser}->{verrelease};
272 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
277 for my $ver (@{ $versions{$browser} }, undef) {
279 !defined $ver ? undef : # last column if nameless
280 ref $data ne 'HASH' ? '' : # unclassified if no support hash
281 $data->{$ver} // $prev # known or inherit from predecessor
282 // (grep { defined } @{$data}{ @{ $versions{$browser} } })[0]
283 ~~ 'n' && 'n' # first known version is unsupported
286 unless (!defined $prev or $prev ~~ $compare) {
287 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
288 printf '<td class="%s" colspan="%d" title="%s">%s',
291 !$usage ? ('p0') : ('p',
292 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
293 sprintf('p%02d', $usage * ($usagepct - .0001)),
295 sprintf('pp%02d', $usage / $usagemax),
298 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
299 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
300 map { $DSTATS{$_} // () }
301 map { split / /, $_ }
315 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
320 featurescore($caniuse->{data}->{$b}->{stats})
321 <=> featurescore($caniuse->{data}->{$a}->{stats})
322 } keys %{ $caniuse->{data} }) {
323 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
324 printf '<tr id="%s">', $id;
327 saybrowsercols($id, $_) for @browsers;
335 # normalised version number comparable as string (cmp)
336 shift =~ /(?:.*-|^)(\d*)(.*)/;
337 # matched (major)(.minor) of last value in range (a-B)
338 return sprintf('%02d', $1 || 0) . $2;
342 my @span = ($_[0], @_>1 ? $_[-1] : ());
343 s/-.*// for $span[0];
349 return join('‒', @span);
356 <table class="glyphs"><tr>
357 <td class="X l5">supported
358 <td class="X l3">partial
359 <td class="X l2">external (js/plugin)
360 <td class="X l1">missing
361 <td class="X l0">unknown
362 <td class="X ex">prefixed
365 <p><: if ($usage) { :>
367 <span class=" p0">0</span> -
368 <span class="p p0 p00">.01</span> -
369 <span class="p p0 p05">1-9</span> -
370 <span class="p p1">10</span> -
371 <span class="p p2">20</span> -
372 <span class="p p5">majority</span>
374 <table class="glyphs"><tr>
375 <td class="p p1">previous version</td>
376 <td class="p p3">current</td>
377 <td class="p p0 p00">upcoming (within months)</td>
378 <td class=" p0">future (within a year)</td>
383 <ul class="legend legend-set">
384 <li>default <strong>style</strong> is
385 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
386 <li><strong>usage</strong> source is
387 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
392 <script type="text/javascript" src="/searchlocal.js"></script>
393 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>