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 $scorediv = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
128 print '<table class="mapped">';
129 print '<col span="3">'; # should match first thead row
130 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
131 say '</colgroup><col>';
133 my $header = join('',
135 '<th colspan="3">feature',
137 my $name = $caniuse->{agents}->{$_}->{browser};
138 sprintf('<th colspan="%d" class="%s" title="%s">%s',
139 scalar @{ $versions{$_} },
140 join(' ', map {"b-a-$_"} grep {$_}
141 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
144 sprintf('%.1f%%', sum(values %{ $canihas->{$_} })),
148 length $name < 3 + @{ $versions{$_} }*2 ? $name
149 : $caniuse->{agents}->{$_}->{abbr};
155 print '<thead>', $header;
156 # preceding row without any colspan to work around gecko bug
159 for my $browser (@browsers) {
160 printf('<td title="%s"%s>%s',
162 sprintf('%.1f%%', $canihas->{$browser}->{$_}),
166 defined $_ && !$_ && ' class="ex"'
167 } $caniuse->{agents}->{$browser}->{verrelease}->{$_}),
169 ) for @{ $versions{$browser} };
173 say '<tfoot>', $header, '</tfoot>';
176 # relative amount of support for given feature
177 state $statspts = { y=>10, 'y x'=>10, a=>5, 'a x'=>5, j=>2, 'p j'=>2, 'p p'=>2, p=>1 };
179 if (my $row = shift) {
181 while (my ($browser, $versions) = each %$row) {
182 ref $versions eq 'HASH' or next;
183 while (my ($version, $_) = each %$versions) {
184 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
190 while (my ($browser, $vercols) = each %versions) {
191 my $div = 0; # multiplier exponent (decreased to lower value)
192 my @vers = map { $row->{$browser}->{$_} } @$vercols;
193 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
194 my @future; # find upcoming releases (after current)
195 for (reverse @$vercols) {
196 last if $_ eq $current;
197 push @future, pop @vers;
198 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
200 splice @vers, -1, 0, @future; # move ahead to decrease precedence
202 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
210 my $row = $caniuse->{data}->{$id};
212 for ($row->{categories}) {
213 my $cell = $_ ? lc $_->[0] : '-';
214 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
215 printf '<th title="%s">%s', join(' + ', @$_), $cell;
219 sprintf('<a href="%s" onclick="%s">%s</a>',
221 sprintf("try { %s; return false } catch(err) { return true }",
222 "document.getElementById('$id').classList.toggle('target')",
227 print '<div class=aside>';
228 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
229 Entity($row->{description}), formathtml($row->{notes}); # sic
230 printf 'Resources: %s.', join(', ', map {
231 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
232 } @$_) for grep { @$_ } $row->{links} // ();
238 my $row = $caniuse->{data}->{$id};
240 for ($row->{status}) {
241 my $cell = $_ // '-';
242 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
243 printf '<td title="%s" class="l %s">%s',
244 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
249 my ($id, $browser) = @_;
250 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
253 for my $ver (@{ $versions{$browser} }, undef) {
254 unless (!defined $prev
255 or ref $data eq 'HASH' && $data->{$prev} ~~ $data->{$ver}) {
256 my $usage = sum(map { $canihas->{$browser}->{$_} } @span);
257 printf '<td class="%s" colspan="%d" title="%s">%s',
259 X => $CSTATS{ ref $data eq 'HASH' && $data->{$prev} || 'u' },
260 !$usage ? ('p0') : ('p',
261 sprintf('p%01d', $usage / 10),
262 sprintf('p%02d', $usage),
264 sprintf('pp%02d', $usage / $scorediv),
267 sprintf('%.1f%% %s', $usage, join(', ',
268 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
269 map { $DSTATS{$_} // () }
270 map { split / /, $_ }
271 ref $data eq 'HASH' && $data->{$prev} || 'u'
284 state $maxscore = featurescore({ # yes for every possible version
285 map { $_ => { map {$_ => 'y'} @{$versions{$_}} } } keys %versions
287 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) / $maxscore * 100;
292 featurescore($caniuse->{data}->{$b}->{stats})
293 <=> featurescore($caniuse->{data}->{$a}->{stats})
294 } keys %{ $caniuse->{data} }) {
295 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
296 printf '<tr id="%s">', $id;
299 saybrowsercols($id, $_) for @browsers;
307 my $ref = defined wantarray ? [@_] : \@_;
317 # normalised version number comparable as string (cmp)
318 shift =~ /(?:.*-|^)(\d*)(.*)/;
319 # matched (major)(.minor) of last value in range (a-B)
320 return sprintf('%02d', $1 || 0) . $2;
324 my @span = ($_[0], @_>1 ? $_[-1] : ());
330 return join('‒', @span);
337 <table class="glyphs"><tr>
338 <td class="X l5">supported
339 <td class="X l3">partial
340 <td class="X l2">external (js/plugin)
341 <td class="X l1">missing
342 <td class="X l0">unknown
343 <td class="X ex">prefixed
346 <p><: if ($usage) { :>
348 <span class=" p0">0</span> -
349 <span class="p p0 p00">.01</span> -
350 <span class="p p0 p05">1-9</span> -
351 <span class="p p1">10</span> -
352 <span class="p p2">20</span> -
353 <span class="p p5">majority</span>
355 <table class="glyphs"><tr>
356 <td class="p p1">previous version</td>
357 <td class="p p3">current</td>
358 <td class="p p0 p00">upcoming (within months)</td>
359 <td class=" p0">future (within a year)</td>
364 <ul class="legend legend-set">
365 <li>default <strong>style</strong> is
366 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
367 <li><strong>usage</strong> source is
368 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
373 <script type="text/javascript" src="/searchlocal.js"></script>
374 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>