EV-WebKit
view release on metacpan or search on metacpan
t/A2-features.t view on Meta::CPAN
const res={ran:1};
const n=navigator;
// each device API must expose its OWN method set, not a merged union
res.serialHasGetPorts = !!(n.serial && 'getPorts' in n.serial);
res.serialHasGetDevices = !!(n.serial && 'getDevices' in n.serial);
res.usbHasRequestPort = !!(n.usb && 'requestPort' in n.usb);
res.usbHasRequestDevice = !!(n.usb && 'requestDevice' in n.usb);
res.btHasAvailability = !!(n.bluetooth && 'getAvailability' in n.bluetooth);
res.storageHasGetDir = !!(n.storage && 'getDirectory' in n.storage);
// the interface objects must exist alongside their navigator members
res.ifaces={}; ['NetworkInformation','USB','HID','Serial','Bluetooth','StorageManager',
'BatteryManager','RTCPeerConnection','RTCDataChannel'].forEach(function(k){
res.ifaces[k] = (k in window); });
// instances must look like WebIDL objects, not plain literals
if(n.connection){
res.connTag = Object.prototype.toString.call(n.connection);
res.connOwnKeys = Object.keys(n.connection).length; // real: 0 (all on the prototype)
res.connIsET = n.connection instanceof EventTarget;
res.connSame = n.connection === n.connection; // same object every access
}
// RTCPeerConnection must require `new` and not pollute window
try { RTCPeerConnection(); res.rtcNoNewThrew=false; }
catch(e){ res.rtcNoNewThrew = (e instanceof TypeError); }
res.windowPolluted = ('iceGatheringState' in window) || ('signalingState' in window);
// getBattery must resolve the SAME BatteryManager each call
if(typeof n.getBattery==='function'){
const b1=await n.getBattery(), b2=await n.getBattery();
res.batterySame = (b1===b2);
res.batteryTag = Object.prototype.toString.call(b1);
}
return JSON.stringify(res);
JS
});
TWK::run_with_timeout(20);
$b->quit;
require Cpanel::JSON::XS; return Cpanel::JSON::XS::decode_json($out // '{}');
}
{
my $s = shape('windows-chrome');
ok($s->{ran}, 'shape probe ran') or diag('probe returned nothing');
ok($s->{serialHasGetPorts}, 'navigator.serial has getPorts (its real primary method)');
ok(!$s->{serialHasGetDevices}, 'navigator.serial does NOT have getDevices (that is WebUSB/WebHID)');
ok(!$s->{usbHasRequestPort}, 'navigator.usb does NOT have requestPort (WebUSB has no such method)');
ok($s->{usbHasRequestDevice}, 'navigator.usb has requestDevice');
ok($s->{btHasAvailability}, 'navigator.bluetooth has getAvailability');
ok($s->{storageHasGetDir}, 'navigator.storage has getDirectory');
is($s->{ifaces}{$_}, 1, "window.$_ interface object exists")
for qw(NetworkInformation USB HID Serial Bluetooth StorageManager BatteryManager RTCPeerConnection);
is($s->{connTag}, '[object NetworkInformation]', 'connection reports its interface name via toStringTag');
is($s->{connOwnKeys}, 0, 'connection has no own enumerable keys (attributes live on the prototype)');
ok($s->{connIsET}, 'connection is a real EventTarget');
ok($s->{connSame}, 'connection returns the same object on every access');
ok($s->{rtcNoNewThrew}, 'RTCPeerConnection() without new throws TypeError, as the real one does');
ok(!$s->{windowPolluted}, 'calling RTCPeerConnection() does not leak state onto window');
ok($s->{batterySame}, 'getBattery() resolves the same BatteryManager each call');
is($s->{batteryTag}, '[object BatteryManager]', 'battery object reports its interface name');
}
my $c = feat('windows-chrome');
ok($c->{connection}, 'Chrome: navigator.connection present');
ok($c->{usb} && $c->{bluetooth} && $c->{hid} && $c->{serial}, 'Chrome desktop: usb/bluetooth/hid/serial present');
ok($c->{battery}, 'Chrome: navigator.getBattery present');
ok($c->{scheduling} && $c->{rtc} && $c->{storage}, 'Chrome: scheduling/rtc/storage present');
is($c->{effType}, '4g', 'connection.effectiveType is functional');
ok($c->{estimateOk}, 'storage.estimate() resolves a StorageEstimate');
is($c->{batteryLevel}, 1, 'getBattery() resolves a BatteryManager');
ok($c->{cfgGone}, '__evwk_cfg is deleted after injection (no window tell)');
my $s = feat('macos-safari');
# guard: feat() yields {} on any failure, which would make every absence
# assertion below pass vacuously.
ok(exists $s->{connection}, 'macos-safari probe ran (absence assertions are meaningful)');
ok(!$s->{connection}, 'Safari: no navigator.connection');
ok(!$s->{usb} && !$s->{bluetooth} && !$s->{battery}, 'Safari: no usb/bluetooth/battery');
ok(!$s->{scheduling}, 'Safari: no navigator.scheduling');
ok($s->{rtc} && $s->{storage}, 'Safari: rtc + storage present');
my $px = feat('pixel-chrome');
ok(exists $px->{hid}, 'pixel-chrome probe ran (absence assertions are meaningful)');
ok($px->{usb} && $px->{bluetooth}, 'Android Chrome: usb + bluetooth present');
ok(!$px->{hid} && !$px->{serial}, 'Android Chrome: no hid/serial (desktop-only)');
ok($px->{connection} && $px->{battery}, 'Android Chrome: connection + battery present');
# --- regressions from the round-2 adversarial review ---
sub coh {
my ($name) = @_;
my $b = EV::WebKit->new(window => [200,150], fingerprint => $name);
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $out;
$b->go('fp://host/p', sub {
$b->script(<<'JS', sub { $out = $_[0]; EV::break });
const res={ran:1};
const u=navigator.userAgentData;
res.hasUAD=!!u;
if(u){
res.uadTag=Object.prototype.toString.call(u);
res.uadOwnKeys=Object.keys(u).length; // real: 0, all on the prototype
res.uadIfaceGlobal=('NavigatorUAData' in window);
res.uadNoProto=(typeof u.getHighEntropyValues==='function' &&
u.getHighEntropyValues.prototype===undefined);
res.uadBrands=Array.isArray(u.brands);
res.uadFrozen=Object.isFrozen(u.brands);
// a promise-returning operation must REJECT, never throw synchronously
try { const p=u.getHighEntropyValues();
res.hevSyncThrew=false;
res.hevRejected=await p.then(()=>false,()=>true); }
catch(e){ res.hevSyncThrew=true; res.hevRejected=false; }
}
// screen.orientation.type must match the profile's orientation
res.orientType = (screen.orientation && screen.orientation.type) || null;
// RTCPeerConnection members must be enumerable like real WebIDL
if('RTCPeerConnection' in window){
const d=Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype,'createOffer');
res.rtcEnumerable = !!(d && d.enumerable);
}
// matchMedia: method identity, brand check, compound + unit-alias queries
const m=window.matchMedia('(pointer: coarse)');
res.mqlIdentity = (m.addEventListener === m.addEventListener);
try { const g=Object.getOwnPropertyDescriptor(MediaQueryList.prototype,'matches').get;
res.mqlBrand = (typeof g.call(m) === 'boolean'); }
catch(e){ res.mqlBrand=false; }
res.mqlOwnMatches = Object.prototype.hasOwnProperty.call(m,'matches');
res.mqSimple = window.matchMedia('(pointer: coarse)').matches;
res.mqCompound = window.matchMedia('(pointer: coarse) and (min-width: 1px)').matches;
res.mqList = window.matchMedia('(hover: none), (pointer: coarse)').matches;
res.mqAliasX = window.matchMedia('(min-resolution: 2x)').matches;
res.mqRange = window.matchMedia('(resolution >= 2dppx)').matches;
return JSON.stringify(res);
JS
});
TWK::run_with_timeout(20);
$b->quit;
require Cpanel::JSON::XS; return Cpanel::JSON::XS::decode_json($out // '{}');
}
{
my $r = coh('windows-chrome');
ok($r->{ran}, 'coherence probe ran') or diag('probe returned nothing');
SKIP: { skip 'no userAgentData', 8 unless $r->{hasUAD};
is($r->{uadTag}, '[object NavigatorUAData]', 'userAgentData reports its real interface name');
is($r->{uadOwnKeys}, 0, 'userAgentData has no own enumerable keys (attributes on the prototype)');
ok($r->{uadIfaceGlobal}, 'window.NavigatorUAData interface object exists');
ok($r->{uadNoProto}, 'getHighEntropyValues has no .prototype (native-method-like)');
ok($r->{uadBrands}, 'userAgentData.brands is still a real array');
ok($r->{uadFrozen}, 'userAgentData.brands is frozen, as real Chrome returns it');
ok(!$r->{hevSyncThrew},
'getHighEntropyValues() with no argument does not throw synchronously');
ok($r->{hevRejected},
'getHighEntropyValues() with no argument returns a rejected promise');
}
# windows-chrome is a desktop profile: landscape-primary, never the mobile value
is($r->{orientType}, 'landscape-primary',
'desktop screen.orientation.type comes from the profile');
ok($r->{rtcEnumerable}, 'RTCPeerConnection members are enumerable, like real WebIDL and the sibling stubs');
}
{
my $r = coh('pixel-chrome'); # mobile: pointer coarse, hover none, dppx 2.625
ok($r->{mqlIdentity}, 'MediaQueryList method identity is stable (no per-access Proxy binding)');
ok($r->{mqlBrand}, 'MediaQueryList passes the platform-object brand check');
ok(!$r->{mqlOwnMatches}, 'matches stays a prototype accessor, not an own property');
ok($r->{mqSimple}, 'mobile: (pointer: coarse) matches');
ok($r->{mqCompound}, 'compound query with an unspoofed clause still matches');
ok($r->{mqList}, 'comma-separated query list matches');
ok($r->{mqAliasX}, 'the x unit alias for dppx is handled');
ok($r->{mqRange}, 'range syntax (resolution >= 2dppx) is handled');
}
# --- round-3: matchMedia must stay live and handle real-world query forms ---
{
my $b = EV::WebKit->new(window => [400,300], fingerprint => 'pixel-chrome');
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $out;
$b->go('fp://host/p', sub {
$b->script(<<'JS', sub { $out = $_[0]; EV::break });
const res={ran:1}, mm=(q)=>window.matchMedia(q).matches;
res.simple = mm('(pointer: coarse)');
// a media type must not abandon the spoof: X and 'screen and X' must agree
res.screenAnd = mm('screen and (pointer: coarse)');
res.allAnd = mm('all and (pointer: coarse)');
// 'and' also occurs INSIDE real values (landscape, standard, standalone)
res.dynRange = mm('(dynamic-range: standard)');
res.withValueAnd = mm('(pointer: coarse) and (dynamic-range: standard)');
res.landscape = mm('(orientation: landscape), (pointer: coarse)');
// a retained MediaQueryList must re-evaluate, not serve a frozen snapshot
const held = window.matchMedia('(min-resolution: 2dppx) and (min-width: 300px)');
res.heldBefore = held.matches;
const fresh = window.matchMedia('(min-resolution: 2dppx) and (min-width: 300px)');
res.freshAgrees = (held.matches === fresh.matches);
return JSON.stringify(res);
JS
});
TWK::run_with_timeout(20); $b->quit;
require Cpanel::JSON::XS; my $r = Cpanel::JSON::XS::decode_json($out // '{}');
ok($r->{ran}, 'matchMedia probe ran') or diag('probe returned nothing');
ok($r->{simple}, 'mobile: (pointer: coarse) matches');
is($r->{screenAnd}, $r->{simple}, '"screen and (X)" agrees with "(X)" -- a media type keeps the spoof');
is($r->{allAnd}, $r->{simple}, '"all and (X)" agrees with "(X)"');
ok($r->{withValueAnd},
'a conjunction whose value contains the substring "and" is not shredded by the splitter');
ok($r->{landscape}, 'a comma list keeps a spoofed alternative even when another contains "and"');
ok($r->{freshAgrees},
'a retained MediaQueryList agrees with a freshly created one (matches is re-evaluated, not frozen)');
}
# --- round-4: the accessor-name enumeration must find nothing ---
# Every real WebKitGTK accessor is named exactly "get <prop>". Ours were named
# "get" (C layer) or "" (JS), so one loop listed the entire spoof with zero false
# positives -- including the native getters that are meant to be undetectable.
sub enum_probe {
my ($profile) = @_;
my $b = EV::WebKit->new(window => [200,150], fingerprint => $profile, seed => 111);
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $out;
$b->go('fp://host/p', sub {
$b->script(<<'JS', sub { $out = $_[0]; EV::break });
const odd=[], protoProto=[], srcOdd=[], ownTS=[];
// the accessors installed natively from C -- these must STAY native
const nativeNames=['Navigator.platform','Navigator.vendor','Navigator.languages',
'Navigator.language','Navigator.hardwareConcurrency','Navigator.deviceMemory',
'Navigator.maxTouchPoints','Screen.width','Screen.height','Screen.availWidth',
'Screen.availHeight','Screen.colorDepth','Screen.pixelDepth','window.devicePixelRatio'];
const targets=[[window,'window'],[Navigator.prototype,'Navigator'],[Screen.prototype,'Screen']];
['NetworkInformation','USB','HID','Serial','Bluetooth','StorageManager','BatteryManager',
'NavigatorUAData','ScreenOrientation','RTCPeerConnection','MediaQueryList',
'WebGLShaderPrecisionFormat'].forEach(function(n){
if (window[n] && window[n].prototype) targets.push([window[n].prototype, n]); });
for (const [o,label] of targets) {
for (const k of Object.getOwnPropertyNames(o)) {
let d; try { d = Object.getOwnPropertyDescriptor(o,k); } catch(e){ continue; }
if (!d || typeof d.get !== 'function') continue;
const g = d.get;
if (g.name !== 'get '+k) odd.push(label+'.'+k+'='+JSON.stringify(g.name));
// a real native accessor never carries a .prototype
if (Object.prototype.hasOwnProperty.call(g,'prototype')) protoProto.push(label+'.'+k);
// nor an own toString -- that alone enumerates the spoof
if (Object.prototype.hasOwnProperty.call(g,'toString')) ownTS.push(label+'.'+k);
// the NATIVE accessors must still report [native code] under FPT.call
if (nativeNames.indexOf(label+'.'+k) >= 0) {
let src; try { src = Function.prototype.toString.call(g); } catch(e){ src=''; }
if (src.indexOf('[native code]') < 0) srcOdd.push(label+'.'+k);
}
} }
return JSON.stringify({ran:1, odd:odd, protoProto:protoProto, srcOdd:srcOdd,
ownTS:ownTS, checked:targets.length});
JS
});
TWK::run_with_timeout(20); $b->quit;
require Cpanel::JSON::XS; return Cpanel::JSON::XS::decode_json($out // '{}');
}
for my $profile (qw(windows-chrome pixel-chrome)) {
my $r = enum_probe($profile);
ok($r->{ran}, "$profile: accessor-name enumeration probe ran") or diag('probe returned nothing');
cmp_ok($r->{checked}, '>=', 3, "$profile: the probe walked the spoofed prototypes");
is_deeply($r->{odd}, [],
"$profile: no accessor is detectable by a wrong getter name")
or diag('detectable: ' . join(', ', @{$r->{odd} || []}));
is_deeply($r->{protoProto}, [],
"$profile: no spoofed accessor getter carries a .prototype")
or diag('carries .prototype: ' . join(', ', @{$r->{protoProto} || []}));
is_deeply($r->{ownTS}, [],
"$profile: no spoofed accessor getter carries an own toString")
or diag('own toString: ' . join(', ', @{$r->{ownTS} || []}));
# The C-installed accessors must remain genuinely native: renaming them in
# place is fine, replacing them with a JS wrapper is not.
is_deeply($r->{srcOdd}, [],
"$profile: the native navigator/screen getters still report [native code]")
or diag('no longer native: ' . join(', ', @{$r->{srcOdd} || []}));
}
# --- round-7 regressions ---
# window.orientation is mobile-only; screen.orientation is universal. Emitting
# the orientation config for desktop (so it gets screen.orientation) must not
# also hand it window.orientation beside maxTouchPoints:0.
# And a (not X) clause must delegate the RAW inner text, or the calc() welding
# bug returns and a query and its negation are both true.
for my $prof (qw(windows-chrome pixel-chrome)) {
my $b = EV::WebKit->new(window => [200,150], fingerprint => $prof);
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $o;
$b->go('fp://host/p', sub { $b->script(<<'JS', sub { $o = $_[0]; EV::break }) });
const mm = q => window.matchMedia(q).matches;
return JSON.stringify({
ran: 1,
winOrient: ('orientation' in window),
onOrient: ('onorientationchange' in window),
screenOrient: !!(screen.orientation && screen.orientation.type),
calcPos: mm('(min-width: calc(50px + 10px))'),
calcNeg: mm('(not (min-width: calc(50px + 10px)))'),
// The pair above only disagrees if BOTH round-7 media fixes are present,
// so reverting either one alone escapes it. This compound query pins them
// separately: the window is 200px wide so the inner clause is true and the
// whole query must be false, but either bug alone flips it to true.
calcCompound: mm('(pointer: coarse) and (not (min-width: calc(50px + 10px)))'),
});
JS
TWK::run_with_timeout(20); $b->quit;
require Cpanel::JSON::XS; my $r = Cpanel::JSON::XS::decode_json($o // '{}');
my $mobile = $prof eq 'pixel-chrome';
ok($r->{ran}, "$prof: orientation/media probe ran") or diag('probe returned nothing');
is($r->{winOrient}, $mobile ? 1 : 0, "$prof: window.orientation present only on mobile");
is($r->{onOrient}, $mobile ? 1 : 0, "$prof: onorientationchange tracks window.orientation");
ok($r->{screenOrient}, "$prof: screen.orientation is present on desktop and mobile alike");
isnt($r->{calcPos}, $r->{calcNeg}, "$prof: a calc() query and its negation cannot both hold");
is($r->{calcCompound}, 0,
"$prof: (pointer: coarse) and (not <true clause>) is false (pins each media fix separately)");
}
# --- round-8: the screen must not be recoverable through ANY media-query form ---
# Handling only the min-/max- prefixes left the MQ4 range form delegated to the
# engine, so a binary search over '(device-width >= Npx)' still returned the real
# host geometry -- the exact leak the min-/max- branch was added to close.
for my $prof (qw(windows-chrome pixel-chrome)) {
my $b = EV::WebKit->new(window => [200,150], fingerprint => $prof);
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $o;
$b->go('fp://host/p', sub { $b->script(<<'JS', sub { $o = $_[0]; EV::break }) });
const mm = q => window.matchMedia(q).matches;
const search = suffix => { let lo=1, hi=8192;
for (let i=0;i<14;i++){ const mid=(lo+hi)>>1;
if (mm('(min-device-width: '+mid+'px)'+suffix)) lo=mid+1; else hi=mid; }
return lo-1; };
let lo=1, hi=8192;
for (let i=0;i<14;i++){ const mid=(lo+hi)>>1;
if (mm('(device-width >= '+mid+'px)')) lo=mid+1; else hi=mid; }
return JSON.stringify({ ran:1, sw:screen.width, found:lo-1,
// an untokenizable alternative must not disable the spoofed one beside it
foundTail: search(', print'), foundPlain: search(''),
aspect: mm('(device-aspect-ratio: '+screen.width+'/'+screen.height+')'),
orQ: mm('(hover: none) or (pointer: coarse)'),
andQ: mm('(hover: none) and (pointer: coarse)'),
// tokenizing must respect paren depth: a plain split shredded a nested
// condition, so a conjunction containing a TAUTOLOGY went false
taut: mm('((hover: none) or (hover: hover))'),
nested: mm('(pointer: coarse) and ((hover: none) or (hover: hover))'),
bare: mm('(pointer: coarse)') });
JS
TWK::run_with_timeout(20); $b->quit;
require Cpanel::JSON::XS; my $r = Cpanel::JSON::XS::decode_json($o // '{}');
ok($r->{ran}, "$prof: media-geometry probe ran") or diag('probe returned nothing');
is($r->{found}, $r->{sw},
"$prof: a binary search over the device-width RANGE form yields the spoofed width");
is($r->{foundPlain}, $r->{sw}, "$prof: min-device-width search yields the spoofed width");
is($r->{foundTail}, $r->{sw},
"$prof: appending ', print' does not fall back to the real device geometry");
ok($r->{aspect}, "$prof: device-aspect-ratio agrees with the spoofed screen");
# MQ4 `or` is a disjunction: it cannot be false where `and` of the same two is true.
ok(!$r->{andQ} || $r->{orQ}, "$prof: 'A or B' is not false while 'A and B' is true");
ok($r->{taut}, "$prof: a nested (A or B) tautology is true");
is($r->{nested}, $r->{bare},
"$prof: conjoining a tautology changes nothing (tokenizer respects paren depth)");
}
# --- round-11: the media evaluator parses each clause into (feature, operator,
# value) and dispatches by FEATURE NAME. Every defect below was a syntactic form
# of an ALREADY-spoofed feature that the old spelling-matcher did not recognise,
# so it delegated the clause and the host answered -- handing back the real
# geometry or the real DPR. Each is pinned in both a desktop and a mobile profile.
for my $prof (qw(windows-chrome pixel-chrome)) {
my $b = EV::WebKit->new(window => [200,150], fingerprint => $prof);
$b->mock_scheme('fp', sub { ('<html><body></body></html>','text/html') });
my $o;
$b->go('fp://host/p', sub { $b->script(<<'JS', sub { $o = $_[0]; EV::break }) });
const mm = q => window.matchMedia(q).matches;
const R = { ran:1, sw: screen.width, sh: screen.height, dpr: window.devicePixelRatio };
// 1em/1pt in px, resolved by the ENGINE (the viewport `width` feature is
// never spoofed, so this is the host's real font size, not ours).
const t = (e,n) => mm('(max-width:calc(' + e + ' - ' + n.toFixed(6) + 'px))');
const thr = e => { let lo=-1, hi=1, i;
for (i=0;i<44 && !t(e,lo);i++) lo*=2; if (!t(e,lo)) return null;
for (i=0;i<44 && t(e,hi);i++) hi*=2; if (t(e,hi)) return null;
for (i=0;i<44;i++){ const mid=(lo+hi)/2; if (t(e,mid)) lo=mid; else hi=mid; }
return lo; };
const z = thr('0px');
R.emPx = z===null ? null : thr('1em') - z;
R.ptPx = z===null ? null : thr('1pt') - z;
// 1. a binary search over device-width must yield the SPOOFED width in
// EVERY length unit, not only px -- an em/pt/in search walked straight
// past the spoof and recovered the real host geometry.
const search = (unit, per, tail) => { let lo=0, hi=16384;
for (let i=0;i<28;i++){ const mid=(lo+hi)/2;
if (mm('(min-device-width: ' + (mid/per).toFixed(6) + unit + ')' + (tail||''))) lo=mid; else hi=mid; }
return Math.round(lo); };
R.searchPx = search('px', 1);
R.searchEm = R.emPx ? search('em', R.emPx) : null;
R.searchPt = R.ptPx ? search('pt', R.ptPx) : null;
R.searchIn = search('in', 96);
R.searchEmT = R.emPx ? search('em', R.emPx, ', print') : null;
R.searchPtT = R.ptPx ? search('pt', R.ptPx, ', print') : null;
R.searchCalc = (() => { let lo=0, hi=16384;
for (let i=0;i<28;i++){ const mid=(lo+hi)/2;
if (mm('(min-device-width: calc(' + mid.toFixed(6) + 'px + 0px))')) lo=mid; else hi=mid; }
return Math.round(lo); })();
R.searchBadUnit = mm('(min-device-width: 1zz)'); // unresolvable -> delegate, never a wrong answer
// A negative bound carries no information about the width, so it must not
// be a way to force the clause back to the host: the search below still has
// to yield the spoofed width.
R.negTwoSided = (() => { let lo=0, hi=16384;
for (let i=0;i<28;i++){ const mid=(lo+hi)/2;
if (!mm('(-5px < device-width < ' + mid.toFixed(6) + 'px)')) lo=mid; else hi=mid; }
return Math.round(lo); })();
// 2. all four spellings of device-aspect-ratio ask the same question
const ar = screen.width + '/' + screen.height;
R.arColon = mm('(device-aspect-ratio: ' + ar + ')');
R.arRange = mm('(device-aspect-ratio = ' + ar + ')');
R.arSingle = mm('(device-aspect-ratio: ' + (screen.width/screen.height) + ')');
R.arDec = mm('(device-aspect-ratio: ' + (screen.width/10) + '/' + (screen.height/10) + ')');
// 3. reversed-operand ranges
R.revW = (() => { let lo=0, hi=16384;
for (let i=0;i<28;i++){ const mid=(lo+hi)/2;
if (mm('(' + mid.toFixed(6) + 'px <= device-width)')) lo=mid; else hi=mid; }
return Math.round(lo); })();
R.revDpr = (() => { let lo=0, hi=64;
for (let i=0;i<30;i++){ const mid=(lo+hi)/2;
if (mm('(' + mid.toFixed(6) + 'dppx <= resolution)')) lo=mid; else hi=mid; }
return lo; })();
R.twoSided = mm('(1px < device-width < 100000px)');
// 4. equality and order must not contradict: one comparator, one tolerance
( run in 1.178 second using v1.01-cache-2.11-cpan-b16cb0d3907 )