4 use List::Util qw(sum max first);
7 title => 'browser compatibility cheat sheet',
12 web browser support compatibility usage available feature
13 html html5 css css3 svg javascript js dom mobile
14 ie internet explorer firefox chrome safari webkit opera
16 stylesheet => [qw'circus dark mono red light'],
17 data => ['browser-support.inc.pl'],
20 say "<h1>Browser compatibility</h1>\n";
22 my $caniuse = do 'browser-support.inc.pl' or die $! || $@;
24 # mark last two (future) versions as unreleased, ensure current isn't
25 map { $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => undef } $_->{versions}
26 } for values %{ $caniuse->{agents} };
43 p => 'plugin required',
44 j => 'javascript required',
50 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
55 unoff => 'l1', # unofficial
57 cr => 'l4', # candidate
58 pr => 'l4', # proposed
59 rec => 'l5', # recommendation
60 other => 'l2', # non-w3
61 ietf => 'l5', # standard
64 if (my ($somerow) = values %{ $caniuse->{data} }) {
65 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
66 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
69 my @browsers = grep { $versions{$_} }
70 qw(trident gecko webkit_saf ios_saf webkit_chr android presto op_mob op_mini);
73 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
75 my ($canihas, $usage);
76 given ($get{usage} // 'wm') {
81 printf "<p>Invalid browser usage data request: <em>%s</em>",
82 'identifier must be alphanumeric name or <q>0</q>';
84 $canihas = do "browser-usage-$_.inc.pl" or do {
85 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
89 my $ref = $canihas->{-source} || 'unknown';
90 $ref = sprintf '<a href="%s">%s</a>', $_, $ref for $canihas->{-url} || ();
91 $ref .= " $_" for $canihas->{-date} || ();
92 print "\nwith $ref browser usage statistics";
95 # first() does not work inside given >:(
96 while (my ($browser, $row) = each %$canihas) {
97 my $verlist = $versions{$browser} or next;
98 my %supported = map { $_ => 1 } @$verlist;
99 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
100 while (my ($version, $usage) = each %$row) {
101 next if defined $supported{$version};
102 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
103 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
104 $row->{$next} += $usage;
105 $row->{$version} = 0; # balance browser total
117 my $zero = $#$_ - 2; # baseline index
118 ($_->[$zero - 2] => .5), # past
119 ($_->[$zero - 1] => 10 ), # previous
120 ($_->[$zero + 2] => 0 ), # future
121 ($_->[$zero + 1] => .5), # next
122 ($_->[$zero ] => 30 ), # current
123 } $caniuse->{agents}->{$_}->{versions}
126 }; # fallback hash based on release semantics
127 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
129 my $usagepct = 1; # score multiplier for 0..100 result
130 # normalise usage percentage to only include shown browsers
131 $usagepct = 100 / featurescore({ # yes for every possible version
132 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
135 print '<table class="mapped">';
136 print '<col span="3">'; # should match first thead row
137 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
138 say '</colgroup><col>';
140 my $header = join('',
142 '<th colspan="3">feature',
144 my $name = $caniuse->{agents}->{$_}->{browser};
145 sprintf('<th colspan="%d" class="%s" title="%s">%s',
146 scalar @{ $versions{$_} },
147 join(' ', map {"b-a-$_"} grep {$_}
148 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
151 sprintf('%.1f%%', sum(values %{ $canihas->{$_} }) * $usagepct),
155 length $name < 3 + @{ $versions{$_} }*2 ? $name
156 : $caniuse->{agents}->{$_}->{abbr};
162 print '<thead>', $header;
163 # preceding row without any colspan to work around gecko bug
166 for my $browser (@browsers) {
167 for my $_ (@{ $versions{$browser} }) {
168 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$_};
169 my $future = defined $release;
170 printf('<td title="%s"%s>%s',
172 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
173 $future ? 'development' : (),
176 $future && ' class="ex"',
183 say '<tfoot>', $header, '</tfoot>';
186 # relative amount of support for given feature
187 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
189 if (my $row = shift) {
191 while (my ($browser, $versions) = each %$row) {
192 ref $versions eq 'HASH' or next;
193 while (my ($version, $_) = each %$versions) {
194 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
200 while (my ($browser, $vercols) = each %versions) {
201 my $div = 0; # multiplier exponent (decreased to lower value)
202 my @vers = map { $row->{$browser}->{$_} } @$vercols;
203 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
204 my @future; # find upcoming releases (after current)
205 for (reverse @$vercols) {
206 last if $_ eq $current;
207 push @future, pop @vers;
208 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
210 splice @vers, -1, 0, @future; # move ahead to decrease precedence
212 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
220 my $row = $caniuse->{data}->{$id};
222 for ($row->{categories}) {
223 my $cell = $_ ? lc $_->[0] : '-';
224 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
225 printf '<th title="%s">%s', join(' + ', @$_), $cell;
229 sprintf('<a href="%s" onclick="%s">%s</a>',
231 sprintf("try { %s; return false } catch(err) { return true }",
232 "document.getElementById('$id').classList.toggle('target')",
237 print '<div class=aside>';
238 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
239 Entity($row->{description}),
240 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
241 printf 'Resources: %s.', join(', ', map {
242 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
243 } @$_) for grep { @$_ } $row->{links} // ();
249 my $row = $caniuse->{data}->{$id};
251 for ($row->{status}) {
252 my $cell = $_ // '-';
253 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
254 printf '<td title="%s" class="l %s">%s',
255 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
260 my ($id, $browser) = @_;
261 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
264 for my $ver (@{ $versions{$browser} }, undef) {
265 unless (!defined $prev
266 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
267 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
268 printf '<td class="%s" colspan="%d" title="%s">%s',
270 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
271 !$usage ? ('p0') : ('p',
272 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
273 sprintf('p%02d', $usage * ($usagepct - .0001)),
275 sprintf('pp%02d', $usage / $usagemax),
278 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
279 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
280 map { $DSTATS{$_} // () }
281 map { split / /, $_ }
282 ref $data eq 'HASH' && $data->{$prev} || 'u'
295 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
300 featurescore($caniuse->{data}->{$b}->{stats})
301 <=> featurescore($caniuse->{data}->{$a}->{stats})
302 } keys %{ $caniuse->{data} }) {
303 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
304 printf '<tr id="%s">', $id;
307 saybrowsercols($id, $_) for @browsers;
315 # normalised version number comparable as string (cmp)
316 shift =~ /(?:.*-|^)(\d*)(.*)/;
317 # matched (major)(.minor) of last value in range (a-B)
318 return sprintf('%02d', $1 || 0) . $2;
322 my @span = ($_[0], @_>1 ? $_[-1] : ());
323 s/-.*// for $span[0];
329 return join('‒', @span);
336 <table class="glyphs"><tr>
337 <td class="X l5">supported
338 <td class="X l3">partial
339 <td class="X l2">external (js/plugin)
340 <td class="X l1">missing
341 <td class="X l0">unknown
342 <td class="X ex">prefixed
345 <p><: if ($usage) { :>
347 <span class=" p0">0</span> -
348 <span class="p p0 p00">.01</span> -
349 <span class="p p0 p05">1-9</span> -
350 <span class="p p1">10</span> -
351 <span class="p p2">20</span> -
352 <span class="p p5">majority</span>
354 <table class="glyphs"><tr>
355 <td class="p p1">previous version</td>
356 <td class="p p3">current</td>
357 <td class="p p0 p00">upcoming (within months)</td>
358 <td class=" p0">future (within a year)</td>
363 <ul class="legend legend-set">
364 <li>default <strong>style</strong> is
365 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
366 <li><strong>usage</strong> source is
367 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
372 <script type="text/javascript" src="/searchlocal.js"></script>
373 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>