4 use List::Util qw(sum max);
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";
102 my $zero = $#$_ - 2; # baseline index
103 ($_->[$zero - 2] => .5), # past
104 ($_->[$zero - 1] => 10 ), # previous
105 ($_->[$zero + 2] => 0 ), # future
106 ($_->[$zero + 1] => .5), # next
107 ($_->[$zero ] => 30 ), # current
108 } $caniuse->{agents}->{$_}->{versions}
111 }; # fallback hash based on release semantics
112 my $scorediv = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
114 print '<table class="mapped">';
115 print '<col span="3">'; # should match first thead row
116 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
117 say '</colgroup><col>';
119 my $header = join('',
121 '<th colspan="3">feature',
123 my $name = $caniuse->{agents}->{$_}->{browser};
124 sprintf('<th colspan="%d" class="%s" title="%s">%s',
125 scalar @{ $versions{$_} },
126 join(' ', map {"b-a-$_"} grep {$_}
127 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
130 sprintf('%.1f%%', sum(values %{ $canihas->{$_} })),
134 length $name < 3 + @{ $versions{$_} }*2 ? $name
135 : $caniuse->{agents}->{$_}->{abbr};
141 print '<thead>', $header;
142 # preceding row without any colspan to work around gecko bug
145 for my $browser (@browsers) {
146 printf('<td title="%s"%s>%s',
148 sprintf('%.1f%%', $canihas->{$browser}->{$_}),
152 defined $_ && !$_ && ' class="ex"'
153 } $caniuse->{agents}->{$browser}->{verrelease}->{$_}),
155 ) for @{ $versions{$browser} };
159 say '<tfoot>', $header, '</tfoot>';
162 # relative amount of support for given feature
163 state $statspts = { y=>10, 'y x'=>10, a=>5, 'a x'=>5, j=>2, 'p j'=>2, 'p p'=>2, p=>1 };
165 if (my $row = shift) {
167 while (my ($browser, $versions) = each %$row) {
168 ref $versions eq 'HASH' or next;
169 while (my ($version, $_) = each %$versions) {
170 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
176 while (my ($browser, $vercols) = each %versions) {
177 my $div = 0; # multiplier exponent (decreased to lower value)
178 my @vers = map { $row->{$browser}->{$_} } @$vercols;
179 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
180 my @future; # find upcoming releases (after current)
181 for (reverse @$vercols) {
182 last if $_ eq $current;
183 push @future, pop @vers;
184 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
186 splice @vers, -1, 0, @future; # move ahead to decrease precedence
188 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
196 my $row = $caniuse->{data}->{$id};
198 for ($row->{categories}) {
199 my $cell = $_ ? lc $_->[0] : '-';
200 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
201 printf '<th title="%s">%s', join(' + ', @$_), $cell;
205 sprintf('<a href="%s" onclick="%s">%s</a>',
207 sprintf("try { %s; return false } catch(err) { return true }",
208 "document.getElementById('$id').classList.toggle('target')",
213 print '<div class=aside>';
214 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
215 Entity($row->{description}), formathtml($row->{notes}); # sic
216 printf 'Resources: %s.', join(', ', map {
217 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
218 } @$_) for grep { @$_ } $row->{links} // ();
224 my $row = $caniuse->{data}->{$id};
226 for ($row->{status}) {
227 my $cell = $_ // '-';
228 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
229 printf '<td title="%s" class="l %s">%s',
230 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
235 my ($id, $browser) = @_;
236 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
239 for my $ver (@{ $versions{$browser} }, undef) {
240 unless (!defined $prev
241 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
242 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
243 printf '<td class="%s" colspan="%d" title="%s">%s',
245 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
246 !$usage ? ('p0') : ('p',
247 sprintf('p%01d', $usage / 10),
248 sprintf('p%02d', $usage),
250 sprintf('pp%02d', $usage / $scorediv),
253 sprintf('%.1f%% %s', $usage, join(', ',
254 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
255 map { $DSTATS{$_} // () }
256 map { split / /, $_ }
257 ref $data eq 'HASH' && $data->{$prev} || 'u'
270 state $maxscore = featurescore({ # yes for every possible version
271 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
273 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) / $maxscore * 100;
278 featurescore($caniuse->{data}->{$b}->{stats})
279 <=> featurescore($caniuse->{data}->{$a}->{stats})
280 } keys %{ $caniuse->{data} }) {
281 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
282 printf '<tr id="%s">', $id;
285 saybrowsercols($id, $_) for @browsers;
293 my $ref = defined wantarray ? [@_] : \@_;
303 # normalised version number comparable as string (cmp)
304 shift =~ /^(\d*)(.*)/;
305 return sprintf('%02d', $1 || 0) . $2;
309 my @span = ($_[0], @_>1 ? $_[-1] : ());
314 return join('‒', @span);
321 <table class="glyphs"><tr>
322 <td class="X l5">supported
323 <td class="X l3">partial
324 <td class="X l2">external (js/plugin)
325 <td class="X l1">missing
326 <td class="X l0">unknown
327 <td class="X ex">prefixed
330 <p><: if ($usage) { :>
332 <span class=" p0">0</span> -
333 <span class="p p0 p00">.01</span> -
334 <span class="p p0 p05">1-9</span> -
335 <span class="p p1">10</span> -
336 <span class="p p2">20</span> -
337 <span class="p p5">majority</span>
339 <table class="glyphs"><tr>
340 <td class="p p1">previous version</td>
341 <td class="p p3">current</td>
342 <td class="p p0 p00">upcoming (within months)</td>
343 <td class=" p0">future (within a year)</td>
348 <ul class="legend legend-set">
349 <li>default <strong>style</strong> is
350 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
351 <li><strong>usage</strong> source is
352 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
357 <script type="text/javascript" src="/searchlocal.js"></script>
358 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>