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 printf('<td title="%s"%s>%s',
169 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
173 defined $_ && !$_ && ' class="ex"'
174 } $caniuse->{agents}->{$browser}->{verrelease}->{$_}),
176 ) for @{ $versions{$browser} };
180 say '<tfoot>', $header, '</tfoot>';
183 # relative amount of support for given feature
184 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
186 if (my $row = shift) {
188 while (my ($browser, $versions) = each %$row) {
189 ref $versions eq 'HASH' or next;
190 while (my ($version, $_) = each %$versions) {
191 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
197 while (my ($browser, $vercols) = each %versions) {
198 my $div = 0; # multiplier exponent (decreased to lower value)
199 my @vers = map { $row->{$browser}->{$_} } @$vercols;
200 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
201 my @future; # find upcoming releases (after current)
202 for (reverse @$vercols) {
203 last if $_ eq $current;
204 push @future, pop @vers;
205 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
207 splice @vers, -1, 0, @future; # move ahead to decrease precedence
209 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
217 my $row = $caniuse->{data}->{$id};
219 for ($row->{categories}) {
220 my $cell = $_ ? lc $_->[0] : '-';
221 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
222 printf '<th title="%s">%s', join(' + ', @$_), $cell;
226 sprintf('<a href="%s" onclick="%s">%s</a>',
228 sprintf("try { %s; return false } catch(err) { return true }",
229 "document.getElementById('$id').classList.toggle('target')",
234 print '<div class=aside>';
235 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
236 Entity($row->{description}), formathtml($row->{notes}); # sic
237 printf 'Resources: %s.', join(', ', map {
238 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
239 } @$_) for grep { @$_ } $row->{links} // ();
245 my $row = $caniuse->{data}->{$id};
247 for ($row->{status}) {
248 my $cell = $_ // '-';
249 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
250 printf '<td title="%s" class="l %s">%s',
251 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
256 my ($id, $browser) = @_;
257 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
260 for my $ver (@{ $versions{$browser} }, undef) {
261 unless (!defined $prev
262 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
263 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
264 printf '<td class="%s" colspan="%d" title="%s">%s',
266 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
267 !$usage ? ('p0') : ('p',
268 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
269 sprintf('p%02d', $usage * ($usagepct - .0001)),
271 sprintf('pp%02d', $usage / $usagemax),
274 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
275 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
276 map { $DSTATS{$_} // () }
277 map { split / /, $_ }
278 ref $data eq 'HASH' && $data->{$prev} || 'u'
291 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
296 featurescore($caniuse->{data}->{$b}->{stats})
297 <=> featurescore($caniuse->{data}->{$a}->{stats})
298 } keys %{ $caniuse->{data} }) {
299 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
300 printf '<tr id="%s">', $id;
303 saybrowsercols($id, $_) for @browsers;
311 my $ref = defined wantarray ? [@_] : \@_;
321 # normalised version number comparable as string (cmp)
322 shift =~ /(?:.*-|^)(\d*)(.*)/;
323 # matched (major)(.minor) of last value in range (a-B)
324 return sprintf('%02d', $1 || 0) . $2;
328 my @span = ($_[0], @_>1 ? $_[-1] : ());
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>