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 $_ = paddedver($_) for $version;
103 my $next = first { paddedver($_) ge $version } @$verlist; # or next
104 $row->{$next} += $usage;
116 my $zero = $#$_ - 2; # baseline index
117 ($_->[$zero - 2] => .5), # past
118 ($_->[$zero - 1] => 10 ), # previous
119 ($_->[$zero + 2] => 0 ), # future
120 ($_->[$zero + 1] => .5), # next
121 ($_->[$zero ] => 30 ), # current
122 } $caniuse->{agents}->{$_}->{versions}
125 }; # fallback hash based on release semantics
126 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
128 my $usagepct = 1; # score multiplier for 0..100 result
129 # normalise usage percentage to only include shown browsers
130 $usagepct = 100 / featurescore({ # yes for every possible version
131 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
134 print '<table class="mapped">';
135 print '<col span="3">'; # should match first thead row
136 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
137 say '</colgroup><col>';
139 my $header = join('',
141 '<th colspan="3">feature',
143 my $name = $caniuse->{agents}->{$_}->{browser};
144 sprintf('<th colspan="%d" class="%s" title="%s">%s',
145 scalar @{ $versions{$_} },
146 join(' ', map {"b-a-$_"} grep {$_}
147 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
150 sprintf('%.1f%%', sum(values %{ $canihas->{$_} }) * $usagepct),
154 length $name < 3 + @{ $versions{$_} }*2 ? $name
155 : $caniuse->{agents}->{$_}->{abbr};
161 print '<thead>', $header;
162 # preceding row without any colspan to work around gecko bug
165 for my $browser (@browsers) {
166 printf('<td title="%s"%s>%s',
168 sprintf('%.1f%%', $canihas->{$browser}->{$_} * $usagepct),
172 defined $_ && !$_ && ' class="ex"'
173 } $caniuse->{agents}->{$browser}->{verrelease}->{$_}),
175 ) for @{ $versions{$browser} };
179 say '<tfoot>', $header, '</tfoot>';
182 # relative amount of support for given feature
183 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
185 if (my $row = shift) {
187 while (my ($browser, $versions) = each %$row) {
188 ref $versions eq 'HASH' or next;
189 while (my ($version, $_) = each %$versions) {
190 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
196 while (my ($browser, $vercols) = each %versions) {
197 my $div = 0; # multiplier exponent (decreased to lower value)
198 my @vers = map { $row->{$browser}->{$_} } @$vercols;
199 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
200 my @future; # find upcoming releases (after current)
201 for (reverse @$vercols) {
202 last if $_ eq $current;
203 push @future, pop @vers;
204 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
206 splice @vers, -1, 0, @future; # move ahead to decrease precedence
208 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
216 my $row = $caniuse->{data}->{$id};
218 for ($row->{categories}) {
219 my $cell = $_ ? lc $_->[0] : '-';
220 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
221 printf '<th title="%s">%s', join(' + ', @$_), $cell;
225 sprintf('<a href="%s" onclick="%s">%s</a>',
227 sprintf("try { %s; return false } catch(err) { return true }",
228 "document.getElementById('$id').classList.toggle('target')",
233 print '<div class=aside>';
234 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
235 Entity($row->{description}), formathtml($row->{notes}); # sic
236 printf 'Resources: %s.', join(', ', map {
237 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
238 } @$_) for grep { @$_ } $row->{links} // ();
244 my $row = $caniuse->{data}->{$id};
246 for ($row->{status}) {
247 my $cell = $_ // '-';
248 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
249 printf '<td title="%s" class="l %s">%s',
250 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
255 my ($id, $browser) = @_;
256 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
259 for my $ver (@{ $versions{$browser} }, undef) {
260 unless (!defined $prev
261 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
262 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
263 printf '<td class="%s" colspan="%d" title="%s">%s',
265 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
266 !$usage ? ('p0') : ('p',
267 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
268 sprintf('p%02d', $usage * ($usagepct - .0001)),
270 sprintf('pp%02d', $usage / $usagemax),
273 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
274 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
275 map { $DSTATS{$_} // () }
276 map { split / /, $_ }
277 ref $data eq 'HASH' && $data->{$prev} || 'u'
290 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
295 featurescore($caniuse->{data}->{$b}->{stats})
296 <=> featurescore($caniuse->{data}->{$a}->{stats})
297 } keys %{ $caniuse->{data} }) {
298 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
299 printf '<tr id="%s">', $id;
302 saybrowsercols($id, $_) for @browsers;
310 my $ref = defined wantarray ? [@_] : \@_;
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] : ());
333 return join('‒', @span);
340 <table class="glyphs"><tr>
341 <td class="X l5">supported
342 <td class="X l3">partial
343 <td class="X l2">external (js/plugin)
344 <td class="X l1">missing
345 <td class="X l0">unknown
346 <td class="X ex">prefixed
349 <p><: if ($usage) { :>
351 <span class=" p0">0</span> -
352 <span class="p p0 p00">.01</span> -
353 <span class="p p0 p05">1-9</span> -
354 <span class="p p1">10</span> -
355 <span class="p p2">20</span> -
356 <span class="p p5">majority</span>
358 <table class="glyphs"><tr>
359 <td class="p p1">previous version</td>
360 <td class="p p3">current</td>
361 <td class="p p0 p00">upcoming (within months)</td>
362 <td class=" p0">future (within a year)</td>
367 <ul class="legend legend-set">
368 <li>default <strong>style</strong> is
369 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
370 <li><strong>usage</strong> source is
371 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
376 <script type="text/javascript" src="/searchlocal.js"></script>
377 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>