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->{-source} || 'unknown';
92 $ref = sprintf '<a href="%s">%s</a>', $_, $ref for $canihas->{-url} || ();
93 $ref .= " $_" for $canihas->{-date} || ();
94 print "\nwith $ref browser usage statistics";
97 # first() does not work inside given >:(
98 while (my ($browser, $row) = each %$canihas) {
99 my $verlist = $versions{$browser} or next;
100 my %supported = map { $_ => 1 } @$verlist;
101 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
102 while (my ($version, $usage) = each %$row) {
103 next if defined $supported{$version};
104 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
105 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
106 $row->{$next} += $usage;
107 $row->{$version} = 0; # balance browser total
119 my $zero = $#$_ - 2; # baseline index
120 ($_->[$zero - 2] => .5), # past
121 ($_->[$zero - 1] => 10 ), # previous
122 ($_->[$zero + 2] => 0 ), # future
123 ($_->[$zero + 1] => .5), # next
124 ($_->[$zero ] => 30 ), # current
125 } $caniuse->{agents}->{$_}->{versions}
128 }; # fallback hash based on release semantics
129 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
131 my $usagepct = 1; # score multiplier for 0..100 result
132 # normalise usage percentage to only include shown browsers
133 $usagepct = 100 / featurescore({ # yes for every possible version
134 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
137 print '<table class="mapped">';
138 print '<col span="3">'; # should match first thead row
139 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
140 say '</colgroup><col>';
142 my $header = join('',
144 '<th colspan="3">feature',
146 my $name = $caniuse->{agents}->{$_}->{browser};
147 sprintf('<th colspan="%d" class="%s" title="%s">%s',
148 scalar @{ $versions{$_} },
149 join(' ', map {"b-a-$_"} grep {$_}
150 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
153 sprintf('%.1f%%', sum(values %{ $canihas->{$_} }) * $usagepct),
157 length $name < 3 + @{ $versions{$_} }*2 ? $name
158 : $caniuse->{agents}->{$_}->{abbr};
164 print '<thead>', $header;
165 # preceding row without any colspan to work around gecko bug
168 for my $browser (@browsers) {
169 for my $_ (@{ $versions{$browser} }) {
170 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$_};
171 my $future = defined $release;
172 printf('<td title="%s"%s>%s',
174 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
175 $future ? 'development' : (),
178 $future && ' class="ex"',
185 say '<tfoot>', $header, '</tfoot>';
188 # relative amount of support for given feature
189 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
191 if (my $row = shift) {
193 while (my ($browser, $versions) = each %$row) {
194 ref $versions eq 'HASH' or next;
195 while (my ($version, $_) = each %$versions) {
196 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
202 while (my ($browser, $vercols) = each %versions) {
203 my $div = 0; # multiplier exponent (decreased to lower value)
204 my @vers = map { $row->{$browser}->{$_} } @$vercols;
205 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
206 my @future; # find upcoming releases (after current)
207 for (reverse @$vercols) {
208 last if $_ eq $current;
209 push @future, pop @vers;
210 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
212 splice @vers, -1, 0, @future; # move ahead to decrease precedence
214 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
222 my $row = $caniuse->{data}->{$id};
224 for ($row->{categories}) {
225 my $cell = $_ ? lc $_->[0] : '-';
226 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
227 printf '<th title="%s">%s', join(' + ', @$_), $cell;
231 sprintf('<a href="%s" onclick="%s">%s</a>',
233 sprintf("try { %s; return false } catch(err) { return true }",
234 "document.getElementById('$id').classList.toggle('target')",
239 print '<div class=aside>';
240 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
241 Entity($row->{description}),
242 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
243 printf 'Resources: %s.', join(', ', map {
244 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
245 } @$_) for grep { @$_ } $row->{links} // ();
246 printf '<br>Parent feature: %s.', join(', ', map {
247 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
248 } $_) for $row->{parent} || ();
254 my $row = $caniuse->{data}->{$id};
256 for ($row->{status}) {
257 my $cell = $_ // '-';
258 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
259 printf '<td title="%s" class="l %s">%s',
260 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
265 my ($id, $browser) = @_;
266 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
269 for my $ver (@{ $versions{$browser} }, undef) {
270 unless (!defined $prev
271 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
272 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
273 printf '<td class="%s" colspan="%d" title="%s">%s',
275 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
276 !$usage ? ('p0') : ('p',
277 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
278 sprintf('p%02d', $usage * ($usagepct - .0001)),
280 sprintf('pp%02d', $usage / $usagemax),
283 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
284 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
285 map { $DSTATS{$_} // () }
286 map { split / /, $_ }
287 ref $data eq 'HASH' && $data->{$prev} || 'u'
300 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
305 featurescore($caniuse->{data}->{$b}->{stats})
306 <=> featurescore($caniuse->{data}->{$a}->{stats})
307 } keys %{ $caniuse->{data} }) {
308 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
309 printf '<tr id="%s">', $id;
312 saybrowsercols($id, $_) for @browsers;
320 # normalised version number comparable as string (cmp)
321 shift =~ /(?:.*-|^)(\d*)(.*)/;
322 # matched (major)(.minor) of last value in range (a-B)
323 return sprintf('%02d', $1 || 0) . $2;
327 my @span = ($_[0], @_>1 ? $_[-1] : ());
328 s/-.*// for $span[0];
334 return join('‒', @span);
341 <table class="glyphs"><tr>
342 <td class="X l5">supported
343 <td class="X l3">partial
344 <td class="X l2">external (js/plugin)
345 <td class="X l1">missing
346 <td class="X l0">unknown
347 <td class="X ex">prefixed
350 <p><: if ($usage) { :>
352 <span class=" p0">0</span> -
353 <span class="p p0 p00">.01</span> -
354 <span class="p p0 p05">1-9</span> -
355 <span class="p p1">10</span> -
356 <span class="p p2">20</span> -
357 <span class="p p5">majority</span>
359 <table class="glyphs"><tr>
360 <td class="p p1">previous version</td>
361 <td class="p p3">current</td>
362 <td class="p p0 p00">upcoming (within months)</td>
363 <td class=" p0">future (within a year)</td>
368 <ul class="legend legend-set">
369 <li>default <strong>style</strong> is
370 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
371 <li><strong>usage</strong> source is
372 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
377 <script type="text/javascript" src="/searchlocal.js"></script>
378 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>