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}), formathtml($row->{notes}); # sic
240 printf 'Resources: %s.', join(', ', map {
241 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
242 } @$_) for grep { @$_ } $row->{links} // ();
248 my $row = $caniuse->{data}->{$id};
250 for ($row->{status}) {
251 my $cell = $_ // '-';
252 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
253 printf '<td title="%s" class="l %s">%s',
254 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
259 my ($id, $browser) = @_;
260 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
263 for my $ver (@{ $versions{$browser} }, undef) {
264 unless (!defined $prev
265 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
266 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
267 printf '<td class="%s" colspan="%d" title="%s">%s',
269 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
270 !$usage ? ('p0') : ('p',
271 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
272 sprintf('p%02d', $usage * ($usagepct - .0001)),
274 sprintf('pp%02d', $usage / $usagemax),
277 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
278 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
279 map { $DSTATS{$_} // () }
280 map { split / /, $_ }
281 ref $data eq 'HASH' && $data->{$prev} || 'u'
294 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
299 featurescore($caniuse->{data}->{$b}->{stats})
300 <=> featurescore($caniuse->{data}->{$a}->{stats})
301 } keys %{ $caniuse->{data} }) {
302 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
303 printf '<tr id="%s">', $id;
306 saybrowsercols($id, $_) for @browsers;
314 my $ref = defined wantarray ? [@_] : \@_;
324 # normalised version number comparable as string (cmp)
325 shift =~ /(?:.*-|^)(\d*)(.*)/;
326 # matched (major)(.minor) of last value in range (a-B)
327 return sprintf('%02d', $1 || 0) . $2;
331 my @span = ($_[0], @_>1 ? $_[-1] : ());
337 return join('‒', @span);
344 <table class="glyphs"><tr>
345 <td class="X l5">supported
346 <td class="X l3">partial
347 <td class="X l2">external (js/plugin)
348 <td class="X l1">missing
349 <td class="X l0">unknown
350 <td class="X ex">prefixed
353 <p><: if ($usage) { :>
355 <span class=" p0">0</span> -
356 <span class="p p0 p00">.01</span> -
357 <span class="p p0 p05">1-9</span> -
358 <span class="p p1">10</span> -
359 <span class="p p2">20</span> -
360 <span class="p p5">majority</span>
362 <table class="glyphs"><tr>
363 <td class="p p1">previous version</td>
364 <td class="p p3">current</td>
365 <td class="p p0 p00">upcoming (within months)</td>
366 <td class=" p0">future (within a year)</td>
371 <ul class="legend legend-set">
372 <li>default <strong>style</strong> is
373 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
374 <li><strong>usage</strong> source is
375 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
380 <script type="text/javascript" src="/searchlocal.js"></script>
381 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>