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'],
21 <h1>Browser compatibility</h1>
23 <p id="intro">Alternate view of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page<:
24 my ($canihas, $usage);
25 given ($get{usage} // 'wm') {
30 printf "<p>Invalid browser usage data request: <em>%s</em>",
31 'identifier must be alphanumeric name or <q>0</q>';
33 $canihas = do "browser-usage-$_.inc.pl" or do {
34 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
38 my $ref = $canihas->{-source} || 'unknown';
39 $ref = sprintf '<a href="%s">%s</a>', $_, $ref for $canihas->{-url} || ();
40 $ref .= " $_" for $canihas->{-date} || ();
41 print "\nwith $ref browser usage statistics";
47 my $caniuse = do 'browser-support.inc.pl' or die $! || $@;
49 # mark last two (future) versions as unreleased, ensure current isn't
50 map { $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => undef } $_->{versions}
51 } for values %{ $caniuse->{agents} };
68 p => 'plugin required',
69 j => 'javascript required',
75 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
80 unoff => 'l1', # unofficial
82 cr => 'l4', # candidate
83 pr => 'l4', # proposed
84 rec => 'l5', # recommendation
85 other => 'l2', # non-w3
86 ietf => 'l5', # standard
89 if (my ($somerow) = values %{ $caniuse->{data} }) {
90 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
91 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
94 my @browsers = grep { $versions{$_} }
95 qw(trident gecko webkit_saf ios_saf webkit_chr android presto op_mob op_mini);
101 my $zero = $#$_ - 2; # baseline index
102 ($_->[$zero - 2] => .5), # past
103 ($_->[$zero - 1] => 10 ), # previous
104 ($_->[$zero + 2] => 0 ), # future
105 ($_->[$zero + 1] => .5), # next
106 ($_->[$zero ] => 30 ), # current
107 } $caniuse->{agents}->{$_}->{versions}
110 }; # fallback hash based on release semantics
111 my $scorediv = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
113 print '<table class="mapped">';
114 print '<col span="3">'; # should match first thead row
115 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
116 say '</colgroup><col>';
118 my $header = join('',
120 '<th colspan="3">feature',
122 my $name = $caniuse->{agents}->{$_}->{browser};
123 sprintf('<th colspan="%d" class="%s" title="%s">%s',
124 scalar @{ $versions{$_} },
125 join(' ', map {"b-a-$_"} grep {$_}
126 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
129 sprintf('%.1f%%', sum(values %{ $canihas->{$_} })),
133 length $name < 3 + @{ $versions{$_} }*2 ? $name
134 : $caniuse->{agents}->{$_}->{abbr};
140 print '<thead>', $header;
141 # preceding row without any colspan to work around gecko bug
144 for my $browser (@browsers) {
145 printf('<td title="%s"%s>%s',
147 sprintf('%.1f%%', $canihas->{$browser}->{$_}),
151 defined $_ && !$_ && ' class="ex"'
152 } $caniuse->{agents}->{$browser}->{verrelease}->{$_}),
154 ) for @{ $versions{$browser} };
158 say '<tfoot>', $header, '</tfoot>';
161 # relative amount of support for given feature
162 state $statspts = { y=>10, 'y x'=>10, a=>5, 'a x'=>5, j=>2, 'p j'=>2, 'p p'=>2, p=>1 };
164 if (my $row = shift) {
166 while (my ($browser, $versions) = each %$row) {
167 ref $versions eq 'HASH' or next;
168 while (my ($version, $_) = each %$versions) {
169 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
175 while (my ($browser, $vercols) = each %versions) {
176 my $div = 0; # multiplier exponent (decreased to lower value)
177 my @vers = map { $row->{$browser}->{$_} } @$vercols;
178 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
179 my @future; # find upcoming releases (after current)
180 for (reverse @$vercols) {
181 last if $_ eq $current;
182 push @future, pop @vers;
183 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
185 splice @vers, -1, 0, @future; # move ahead to decrease precedence
187 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
195 my $row = $caniuse->{data}->{$id};
197 for ($row->{categories}) {
198 my $cell = $_ ? lc $_->[0] : '-';
199 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
200 printf '<th title="%s">%s', join(' + ', @$_), $cell;
204 sprintf('<a href="%s" onclick="%s">%s</a>',
206 sprintf("try { %s; return false } catch(err) { return true }",
207 "document.getElementById('$id').classList.toggle('target')",
212 print '<div class=aside>';
213 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
214 Entity($row->{description}), formathtml($row->{notes}); # sic
215 printf 'Resources: %s.', join(', ', map {
216 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
217 } @$_) for grep { @$_ } $row->{links} // ();
223 my $row = $caniuse->{data}->{$id};
225 for ($row->{status}) {
226 my $cell = $_ // '-';
227 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
228 printf '<td title="%s" class="l %s">%s',
229 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
234 my ($id, $browser) = @_;
235 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
238 for my $ver (@{ $versions{$browser} }, undef) {
239 unless (!defined $prev
240 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
241 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
242 printf '<td class="%s" colspan="%d" title="%s">%s',
244 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
245 !$usage ? ('p0') : ('p',
246 sprintf('p%01d', $usage / 10),
247 sprintf('p%02d', $usage),
249 sprintf('pp%02d', $usage / $scorediv),
252 sprintf('%.1f%% %s', $usage, join(', ',
253 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
254 map { $DSTATS{$_} // () }
255 map { split / /, $_ }
256 ref $data eq 'HASH' && $data->{$prev} || 'u'
269 state $maxscore = featurescore({ # yes for every possible version
270 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
272 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) / $maxscore * 100;
277 featurescore($caniuse->{data}->{$b}->{stats})
278 <=> featurescore($caniuse->{data}->{$a}->{stats})
279 } keys %{ $caniuse->{data} }) {
280 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
281 printf '<tr id="%s">', $id;
284 saybrowsercols($id, $_) for @browsers;
292 my $ref = defined wantarray ? [@_] : \@_;
302 # normalised version number comparable as string (cmp)
303 shift =~ /^(\d*)(.*)/;
304 return sprintf('%02d', $1 || 0) . $2;
308 my @span = ($_[0], @_>1 ? $_[-1] : ());
313 return join('‒', @span);
320 <table class="glyphs"><tr>
321 <td class="X l5">supported
322 <td class="X l3">partial
323 <td class="X l2">external (js/plugin)
324 <td class="X l1">missing
325 <td class="X l0">unknown
326 <td class="X ex">prefixed
329 <p><: if ($usage) { :>
331 <span class=" p0">0</span> -
332 <span class="p p0 p00">.01</span> -
333 <span class="p p0 p05">1-9</span> -
334 <span class="p p1">10</span> -
335 <span class="p p2">20</span> -
336 <span class="p p5">majority</span>
338 <table class="glyphs"><tr>
339 <td class="p p1">previous version</td>
340 <td class="p p3">current</td>
341 <td class="p p0 p00">upcoming (within months)</td>
342 <td class=" p0">future (within a year)</td>
347 <ul class="legend legend-set">
348 <li>default <strong>style</strong> is
349 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
350 <li><strong>usage</strong> source is
351 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
356 <script type="text/javascript" src="/searchlocal.js"></script>
357 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>