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') {
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};
270 for my $ver (@{ $versions{$browser} }, undef) {
272 !defined $ver ? undef : # last column if nameless
273 ref $data ne 'HASH' ? 'u' : # unsupported if no support hash
274 $data->{$ver} // $prev # known or inherit from predecessor
275 // (grep { defined } @{$data}{ @{ $versions{$browser} } })[0]
276 ~~ 'n' && 'n' # first known version is unsupported
279 unless (!defined $prev or $prev ~~ $compare) {
280 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
281 printf '<td class="%s" colspan="%d" title="%s">%s',
284 !$usage ? ('p0') : ('p',
285 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
286 sprintf('p%02d', $usage * ($usagepct - .0001)),
288 sprintf('pp%02d', $usage / $usagemax),
291 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
292 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
293 map { $DSTATS{$_} // () }
294 map { split / /, $_ }
308 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
313 featurescore($caniuse->{data}->{$b}->{stats})
314 <=> featurescore($caniuse->{data}->{$a}->{stats})
315 } keys %{ $caniuse->{data} }) {
316 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
317 printf '<tr id="%s">', $id;
320 saybrowsercols($id, $_) for @browsers;
328 # normalised version number comparable as string (cmp)
329 shift =~ /(?:.*-|^)(\d*)(.*)/;
330 # matched (major)(.minor) of last value in range (a-B)
331 return sprintf('%02d', $1 || 0) . $2;
335 my @span = ($_[0], @_>1 ? $_[-1] : ());
336 s/-.*// for $span[0];
342 return join('‒', @span);
349 <table class="glyphs"><tr>
350 <td class="X l5">supported
351 <td class="X l3">partial
352 <td class="X l2">external (js/plugin)
353 <td class="X l1">missing
354 <td class="X l0">unknown
355 <td class="X ex">prefixed
358 <p><: if ($usage) { :>
360 <span class=" p0">0</span> -
361 <span class="p p0 p00">.01</span> -
362 <span class="p p0 p05">1-9</span> -
363 <span class="p p1">10</span> -
364 <span class="p p2">20</span> -
365 <span class="p p5">majority</span>
367 <table class="glyphs"><tr>
368 <td class="p p1">previous version</td>
369 <td class="p p3">current</td>
370 <td class="p p0 p00">upcoming (within months)</td>
371 <td class=" p0">future (within a year)</td>
376 <ul class="legend legend-set">
377 <li>default <strong>style</strong> is
378 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
379 <li><strong>usage</strong> source is
380 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
385 <script type="text/javascript" src="/searchlocal.js"></script>
386 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>