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} // ();
244 printf '<br>Parent feature: %s.', join(', ', map {
245 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
246 } $_) for $row->{parent} || ();
252 my $row = $caniuse->{data}->{$id};
254 for ($row->{status}) {
255 my $cell = $_ // '-';
256 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
257 printf '<td title="%s" class="l %s">%s',
258 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
263 my ($id, $browser) = @_;
264 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
267 for my $ver (@{ $versions{$browser} }, undef) {
268 unless (!defined $prev
269 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
270 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
271 printf '<td class="%s" colspan="%d" title="%s">%s',
273 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
274 !$usage ? ('p0') : ('p',
275 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
276 sprintf('p%02d', $usage * ($usagepct - .0001)),
278 sprintf('pp%02d', $usage / $usagemax),
281 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
282 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
283 map { $DSTATS{$_} // () }
284 map { split / /, $_ }
285 ref $data eq 'HASH' && $data->{$prev} || 'u'
298 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
303 featurescore($caniuse->{data}->{$b}->{stats})
304 <=> featurescore($caniuse->{data}->{$a}->{stats})
305 } keys %{ $caniuse->{data} }) {
306 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
307 printf '<tr id="%s">', $id;
310 saybrowsercols($id, $_) for @browsers;
318 # normalised version number comparable as string (cmp)
319 shift =~ /(?:.*-|^)(\d*)(.*)/;
320 # matched (major)(.minor) of last value in range (a-B)
321 return sprintf('%02d', $1 || 0) . $2;
325 my @span = ($_[0], @_>1 ? $_[-1] : ());
326 s/-.*// for $span[0];
332 return join('‒', @span);
339 <table class="glyphs"><tr>
340 <td class="X l5">supported
341 <td class="X l3">partial
342 <td class="X l2">external (js/plugin)
343 <td class="X l1">missing
344 <td class="X l0">unknown
345 <td class="X ex">prefixed
348 <p><: if ($usage) { :>
350 <span class=" p0">0</span> -
351 <span class="p p0 p00">.01</span> -
352 <span class="p p0 p05">1-9</span> -
353 <span class="p p1">10</span> -
354 <span class="p p2">20</span> -
355 <span class="p p5">majority</span>
357 <table class="glyphs"><tr>
358 <td class="p p1">previous version</td>
359 <td class="p p3">current</td>
360 <td class="p p0 p00">upcoming (within months)</td>
361 <td class=" p0">future (within a year)</td>
366 <ul class="legend legend-set">
367 <li>default <strong>style</strong> is
368 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
369 <li><strong>usage</strong> source is
370 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
375 <script type="text/javascript" src="/searchlocal.js"></script>
376 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>