Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info. r=mrbkap, r=heycam

This commit is contained in:
Kathy Brade 2015-06-07 09:02:00 -04:00
Родитель 920f2ea28a
Коммит 8b3f3fad35
24 изменённых файлов: 762 добавлений и 30 удалений

Просмотреть файл

@ -253,6 +253,7 @@ bool nsContentUtils::sIsResourceTimingEnabled = false;
bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
bool nsContentUtils::sEncodeDecodeURLHash = false;
bool nsContentUtils::sPrivacyResistFingerprinting = false;
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
@ -533,6 +534,9 @@ nsContentUtils::Init()
Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
"dom.url.encode_decode_hash", false);
Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
"privacy.resistFingerprinting", false);
Preferences::AddUintVarCache(&sHandlingInputTimeout,
"dom.event.handling-user-input-time-limit",
1000);
@ -1989,6 +1993,16 @@ nsContentUtils::IsCallerChrome()
return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
}
bool
nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
{
if (!aDocShell) {
return false;
}
bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
return !isChrome && sPrivacyResistFingerprinting;
}
namespace mozilla {
namespace dom {
namespace workers {

Просмотреть файл

@ -199,6 +199,9 @@ public:
JS::Handle<jsid> aId,
JS::MutableHandle<JSPropertyDescriptor> aDesc);
// Check whether we should avoid leaking distinguishing information to JS/CSS.
static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
/**
* Returns the parent node of aChild crossing document boundaries.
* Uses the parent node in the composed document.
@ -1913,6 +1916,16 @@ public:
return sEncodeDecodeURLHash;
}
/*
* Returns true if the browser should attempt to prevent content scripts
* from collecting distinctive information about the browser that could
* be used to "fingerprint" and track the user across websites.
*/
static bool ResistFingerprinting()
{
return sPrivacyResistFingerprinting;
}
/**
* Returns true if the doc tree branch which contains aDoc contains any
* plugins which we don't control event dispatch for, i.e. do any plugins
@ -2453,6 +2466,7 @@ private:
static bool sIsUserTimingLoggingEnabled;
static bool sIsExperimentalAutocompleteEnabled;
static bool sEncodeDecodeURLHash;
static bool sPrivacyResistFingerprinting;
static nsHtml5StringParser* sHTMLFragmentParser;
static nsIParser* sXMLFragmentParser;

Просмотреть файл

@ -5003,6 +5003,12 @@ nsGlobalWindow::GetOuterSize(ErrorResult& aError)
{
MOZ_ASSERT(IsOuterWindow());
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
CSSIntSize size;
aError = GetInnerSize(size);
return nsIntSize(size.width, size.height);
}
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
if (!treeOwnerAsWin) {
aError.Throw(NS_ERROR_FAILURE);
@ -5167,6 +5173,11 @@ nsGlobalWindow::GetScreenXY(ErrorResult& aError)
{
MOZ_ASSERT(IsOuterWindow());
// When resisting fingerprinting, always return (0,0)
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
return nsIntPoint(0, 0);
}
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
if (!treeOwnerAsWin) {
aError.Throw(NS_ERROR_FAILURE);
@ -5240,6 +5251,11 @@ nsGlobalWindow::GetMozInnerScreenX(ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenX, (aError), aError, 0);
// When resisting fingerprinting, always return 0.
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
return 0.0;
}
nsRect r = GetInnerScreenRect();
return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
}
@ -5258,6 +5274,11 @@ nsGlobalWindow::GetMozInnerScreenY(ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenY, (aError), aError, 0);
// Return 0 to prevent fingerprinting.
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
return 0.0;
}
nsRect r = GetInnerScreenRect();
return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
}
@ -5286,6 +5307,10 @@ nsGlobalWindow::GetDevicePixelRatio(ErrorResult& aError)
return 1.0;
}
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
return 1.0;
}
return float(nsPresContext::AppUnitsPerCSSPixel())/
presContext->AppUnitsPerDevPixel();
}

Просмотреть файл

@ -1059,6 +1059,8 @@ public:
bool aShowDialog, mozilla::ErrorResult& aError);
uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
bool ShouldResistFingerprinting();
mozilla::dom::MozSelfSupport* GetMozSelfSupport(mozilla::ErrorResult& aError);
already_AddRefed<nsIDOMWindow> OpenDialog(JSContext* aCx,

Просмотреть файл

@ -68,6 +68,11 @@ NS_IMPL_RELEASE_INHERITED(nsScreen, DOMEventTargetHelper)
int32_t
nsScreen::GetPixelDepth(ErrorResult& aRv)
{
// Return 24 to prevent fingerprinting.
if (ShouldResistFingerprinting()) {
return 24;
}
nsDeviceContext* context = GetDeviceContext();
if (!context) {
@ -111,6 +116,11 @@ nsScreen::GetDeviceContext()
nsresult
nsScreen::GetRect(nsRect& aRect)
{
// Return window inner rect to prevent fingerprinting.
if (ShouldResistFingerprinting()) {
return GetWindowInnerRect(aRect);
}
nsDeviceContext *context = GetDeviceContext();
if (!context) {
@ -130,6 +140,11 @@ nsScreen::GetRect(nsRect& aRect)
nsresult
nsScreen::GetAvailRect(nsRect& aRect)
{
// Return window inner rect to prevent fingerprinting.
if (ShouldResistFingerprinting()) {
return GetWindowInnerRect(aRect);
}
nsDeviceContext *context = GetDeviceContext();
if (!context) {
@ -166,22 +181,26 @@ nsScreen::Notify(const hal::ScreenConfiguration& aConfiguration)
void
nsScreen::GetMozOrientation(nsString& aOrientation)
{
switch (mOrientation) {
case eScreenOrientation_PortraitPrimary:
aOrientation.AssignLiteral("portrait-primary");
break;
case eScreenOrientation_PortraitSecondary:
aOrientation.AssignLiteral("portrait-secondary");
break;
case eScreenOrientation_LandscapePrimary:
if (ShouldResistFingerprinting()) {
aOrientation.AssignLiteral("landscape-primary");
break;
case eScreenOrientation_LandscapeSecondary:
aOrientation.AssignLiteral("landscape-secondary");
break;
case eScreenOrientation_None:
default:
MOZ_CRASH("Unacceptable mOrientation value");
} else {
switch (mOrientation) {
case eScreenOrientation_PortraitPrimary:
aOrientation.AssignLiteral("portrait-primary");
break;
case eScreenOrientation_PortraitSecondary:
aOrientation.AssignLiteral("portrait-secondary");
break;
case eScreenOrientation_LandscapePrimary:
aOrientation.AssignLiteral("landscape-primary");
break;
case eScreenOrientation_LandscapeSecondary:
aOrientation.AssignLiteral("landscape-secondary");
break;
case eScreenOrientation_None:
default:
MOZ_CRASH("Unacceptable mOrientation value");
}
}
}
@ -373,3 +392,27 @@ nsScreen::FullScreenEventListener::HandleEvent(nsIDOMEvent* aEvent)
return NS_OK;
}
nsresult
nsScreen::GetWindowInnerRect(nsRect& aRect)
{
aRect.x = 0;
aRect.y = 0;
nsCOMPtr<nsIDOMWindow> win = GetOwner();
if (!win) {
return NS_ERROR_FAILURE;
}
nsresult rv = win->GetInnerWidth(&aRect.width);
NS_ENSURE_SUCCESS(rv, rv);
return win->GetInnerHeight(&aRect.height);
}
bool nsScreen::ShouldResistFingerprinting() const
{
bool resist = false;
nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
if (owner) {
resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
}
return resist;
}

Просмотреть файл

@ -131,6 +131,7 @@ protected:
nsDeviceContext* GetDeviceContext();
nsresult GetRect(nsRect& aRect);
nsresult GetAvailRect(nsRect& aRect);
nsresult GetWindowInnerRect(nsRect& aRect);
mozilla::dom::ScreenOrientation mOrientation;
@ -158,6 +159,8 @@ private:
bool IsDeviceSizePageSize();
bool ShouldResistFingerprinting() const;
nsRefPtr<FullScreenEventListener> mEventListener;
};

Просмотреть файл

@ -0,0 +1,71 @@
// The main test function.
let test = function (isContent) {
SimpleTest.waitForExplicitFinish();
let { ww } = SpecialPowers.Services;
window.chromeWindow = ww.activeWindow;
// The pairs of values expected to be the same when
// fingerprinting resistance is enabled.
let pairs = [
["screenX", 0],
["screenY", 0],
["mozInnerScreenX", 0],
["mozInnerScreenY", 0],
["screen.pixelDepth", 24],
["screen.colorDepth", 24],
["screen.availWidth", "innerWidth"],
["screen.availHeight", "innerHeight"],
["screen.left", 0],
["screen.top", 0],
["screen.availLeft", 0],
["screen.availTop", 0],
["screen.width", "innerWidth"],
["screen.height", "innerHeight"],
["screen.mozOrientation", "'landscape-primary'"],
["devicePixelRatio", 1]
];
// checkPair: tests if members of pair [a, b] are equal when evaluated.
let checkPair = function (a, b) {
is(eval(a), eval(b), a + " should be equal to " + b);
};
// Returns generator object that iterates through pref values.
let prefVals = (for (prefVal of [false, true]) prefVal);
// The main test function, runs until all pref values are exhausted.
let nextTest = function () {
let {value : prefValue, done} = prefVals.next();
if (done) {
SimpleTest.finish();
return;
}
SpecialPowers.pushPrefEnv({set : [["privacy.resistFingerprinting", prefValue]]},
function () {
// We will be resisting fingerprinting if the pref is enabled,
// and we are in a content script (not chrome).
let resisting = prefValue && isContent;
// Check each of the pairs.
pairs.map(function ([item, onVal]) {
if (resisting) {
checkPair("window." + item, onVal);
} else {
if (!item.startsWith("moz")) {
checkPair("window." + item, "chromeWindow." + item);
}
}
});
if (!resisting) {
// Hard to predict these values, but we can enforce constraints:
ok(window.mozInnerScreenX >= chromeWindow.mozInnerScreenX,
"mozInnerScreenX");
ok(window.mozInnerScreenY >= chromeWindow.mozInnerScreenY,
"mozInnerScreenY");
}
nextTest();
});
}
nextTest();
}

Просмотреть файл

@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g'
support-files =
blockNoPlugins.xml
blockPluginHard.xml
bug418986-1.js
cpows_child.js
cpows_parent.xul
file_bug391728.html
@ -31,6 +32,7 @@ support-files =
[test_bug380418.html^headers^]
[test_bug383430.html]
[test_bug391728.html]
[test_bug418986-1.xul]
[test_bug421622.xul]
[test_bug429785.xul]
[test_bug430050.xul]

Просмотреть файл

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1
-->
<window title="Mozilla Bug 418986 (Part 1)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1"
target="_blank">Mozilla Bug 418986 (Part 1)</a>
<script type="application/javascript;version=1.7" src="bug418986-1.js"></script>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
window.onload = function() {
test(false);
};
]]></script>
</body>
</window>

Просмотреть файл

@ -56,6 +56,7 @@ support-files =
bug704320.sjs
bug704320_counter.sjs
bug819051.sjs
chrome/bug418986-1.js
copypaste.js
delayedServerEvents.sjs
echo.sjs
@ -453,6 +454,7 @@ support-files = test_bug402150.html^headers^
[test_bug417255.html]
[test_bug417384.html]
[test_bug418214.html]
[test_bug418986-1.html]
[test_bug419132.html]
[test_bug419527.xhtml]
[test_bug420609.xhtml]

Просмотреть файл

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
-->
<head>
<meta charset="utf-8">
<title>Test 1/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7" src="chrome/bug418986-1.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
window.onload = function() {
test(true);
};
</script>
</body>
</html>

Просмотреть файл

@ -890,6 +890,13 @@ Event::GetScreenCoords(nsPresContext* aPresContext,
WidgetEvent* aEvent,
LayoutDeviceIntPoint aPoint)
{
if (!nsContentUtils::IsCallerChrome() &&
nsContentUtils::ResistFingerprinting()) {
// When resisting fingerprinting, return client coordinates instead.
CSSIntPoint clientCoords = GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0));
return LayoutDeviceIntPoint(clientCoords.x, clientCoords.y);
}
if (EventStateManager::sIsPointerLocked) {
return EventStateManager::sLastScreenPoint;
}

Просмотреть файл

@ -0,0 +1,69 @@
SimpleTest.waitForExplicitFinish();
// The main testing function.
let test = function (isContent) {
// Each definition is [eventType, prefSetting]
// Where we are setting the "privacy.resistFingerprinting" pref.
let eventDefs = [["mousedown", true],
["mouseup", true],
["mousedown", false],
["mouseup", false]];
let testCounter = 0;
// Declare ahead of time.
let setup;
// This function is called when the event handler fires.
let handleEvent = function (event, prefVal) {
let resisting = prefVal && isContent;
if (resisting) {
is(event.screenX, event.clientX, "event.screenX and event.clientX should be the same");
is(event.screenY, event.clientY, "event.screenY and event.clientY should be the same");
} else {
// We can't be sure about X coordinates not being equal, but we can test Y.
isnot(event.screenY, event.clientY, "event.screenY !== event.clientY");
}
++testCounter;
if (testCounter < eventDefs.length) {
nextTest();
} else {
SimpleTest.finish();
}
};
// In this function, we set up the nth div and event handler,
// and then synthesize a mouse event in the div, to test
// whether the resulting events resist fingerprinting by
// suppressing absolute screen coordinates.
nextTest = function () {
let [eventType, prefVal] = eventDefs[testCounter];
SpecialPowers.pushPrefEnv({set:[["privacy.resistFingerprinting", prefVal]]},
function () {
// The following code creates a new div for each event in eventDefs,
// attaches a listener to listen for the event, and then generates
// a fake event at the center of the div.
let div = document.createElement("div");
div.style.width = "10px";
div.style.height = "10px";
div.style.backgroundColor = "red";
// Name the div after the event we're listening for.
div.id = eventType;
document.getElementById("body").appendChild(div);
// Seems we can't add an event listener in chrome unless we run
// it in a later task.
window.setTimeout(function() {
div.addEventListener(eventType, event => handleEvent(event, prefVal), false);
// For some reason, the following synthesizeMouseAtCenter call only seems to run if we
// wrap it in a window.setTimeout(..., 0).
window.setTimeout(function () {
synthesizeMouseAtCenter(div, {type : eventType});
}, 0);
}, 0);
});
};
// Now run by starting with the 0th event.
nextTest();
};

Просмотреть файл

@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g'
support-files =
bug415498-doc1.html
bug415498-doc2.html
bug418986-3.js
bug591249_iframe.xul
bug602962.xul
file_bug679494.html
@ -12,6 +13,7 @@ support-files =
[test_bug336682_2.xul]
[test_bug368835.html]
[test_bug415498.xul]
[test_bug418986-3.xul]
[test_bug524674.xul]
[test_bug586961.xul]
[test_bug591249.xul]

Просмотреть файл

@ -7,6 +7,7 @@ support-files =
bug426082.html
bug648573.html
bug656379-1.html
bug418986-3.js
error_event_worker.js
empty.js
window_bug493251.html
@ -38,6 +39,9 @@ support-files = test_bug336682.js
[test_bug409604.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
[test_bug412567.html]
[test_bug418986-3.html]
# Sometimes fails to finish after tests pass on 'B2G ICS Emulator'.
skip-if = (os == 'b2g')
[test_bug422132.html]
skip-if = buildapp == 'b2g' || e10s # b2g(2 failures out of 8, mousewheel test) b2g-debug(2 failures out of 8, mousewheel test) b2g-desktop(2 failures out of 8, mousewheel test)
[test_bug426082.html]

Просмотреть файл

@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
-->
<head>
<meta charset="utf-8">
<title>Test 3/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body id="body">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
<p id="display"></p>
<pre id="test"></pre>
<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
<script type="application/javascript;version=1.7">
// This test produces fake mouse events and checks that the screenX and screenY
// properties of the received event objects provide client window coordinates.
// Run the test once the window has loaded.
window.onload = () => test(true);
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<!--
Bug 418986
-->
<window title="Mozilla Bug 418986"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<body id="body" xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">
Mozilla Bug 418986</a>
</body>
<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
<script type="application/javascript;version=1.7"><![CDATA[
// This test produces fake mouse events and checks that the screenX and screenY
// properties of the received event objects provide client window coordinates.
// Run the test once the window has loaded.
test(false);
]]></script>
</window>

Просмотреть файл

@ -1450,6 +1450,10 @@ nsComputedDOMStyle::DoGetFontSizeAdjust()
CSSValue*
nsComputedDOMStyle::DoGetOsxFontSmoothing()
{
if (nsContentUtils::ShouldResistFingerprinting(
mPresShell->GetPresContext()->GetDocShell()))
return nullptr;
nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue;
val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.smoothing,
nsCSSProps::kFontSmoothingKTable));

Просмотреть файл

@ -109,13 +109,19 @@ GetDeviceContextFor(nsPresContext* aPresContext)
return aPresContext->DeviceContext();
}
static bool
ShouldResistFingerprinting(nsPresContext* aPresContext)
{
return nsContentUtils::ShouldResistFingerprinting(aPresContext->GetDocShell());
}
// A helper for three features below.
static nsSize
GetDeviceSize(nsPresContext* aPresContext)
{
nsSize size;
if (aPresContext->IsDeviceSizePageSize()) {
if (ShouldResistFingerprinting(aPresContext) || aPresContext->IsDeviceSizePageSize()) {
size = GetSize(aPresContext);
} else if (aPresContext->IsRootPaginatedDocument()) {
// We want the page size, including unprintable areas and margins.
@ -223,13 +229,17 @@ static nsresult
GetColor(nsPresContext* aPresContext, const nsMediaFeature*,
nsCSSValue& aResult)
{
// FIXME: This implementation is bogus. nsDeviceContext
// doesn't provide reliable information (should be fixed in bug
// 424386).
// FIXME: On a monochrome device, return 0!
nsDeviceContext *dx = GetDeviceContextFor(aPresContext);
uint32_t depth;
dx->GetDepth(depth);
uint32_t depth = 24; // Use depth of 24 when resisting fingerprinting.
if (!ShouldResistFingerprinting(aPresContext)) {
// FIXME: This implementation is bogus. nsDeviceContext
// doesn't provide reliable information (should be fixed in bug
// 424386).
// FIXME: On a monochrome device, return 0!
nsDeviceContext *dx = GetDeviceContextFor(aPresContext);
dx->GetDepth(depth);
}
// The spec says to use bits *per color component*, so divide by 3,
// and round down, since the spec says to use the smallest when the
// color components differ.
@ -267,10 +277,15 @@ static nsresult
GetResolution(nsPresContext* aPresContext, const nsMediaFeature*,
nsCSSValue& aResult)
{
// Resolution measures device pixels per CSS (inch/cm/pixel). We
// return it in device pixels per CSS inches.
float dpi = float(nsPresContext::AppUnitsPerCSSInch()) /
float(aPresContext->AppUnitsPerDevPixel());
float dpi = 96; // Use 96 when resisting fingerprinting.
if (!ShouldResistFingerprinting(aPresContext)) {
// Resolution measures device pixels per CSS (inch/cm/pixel). We
// return it in device pixels per CSS inches.
dpi = float(nsPresContext::AppUnitsPerCSSInch()) /
float(aPresContext->AppUnitsPerDevPixel());
}
aResult.SetFloatValue(dpi, eCSSUnit_Inch);
return NS_OK;
}
@ -299,15 +314,26 @@ static nsresult
GetDevicePixelRatio(nsPresContext* aPresContext, const nsMediaFeature*,
nsCSSValue& aResult)
{
float ratio = aPresContext->CSSPixelsToDevPixels(1.0f);
aResult.SetFloatValue(ratio, eCSSUnit_Number);
return NS_OK;
if (!ShouldResistFingerprinting(aPresContext)) {
float ratio = aPresContext->CSSPixelsToDevPixels(1.0f);
aResult.SetFloatValue(ratio, eCSSUnit_Number);
} else {
aResult.SetFloatValue(1.0, eCSSUnit_Number);
}
return NS_OK;
}
static nsresult
GetSystemMetric(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
nsCSSValue& aResult)
{
aResult.Reset();
if (ShouldResistFingerprinting(aPresContext)) {
// If "privacy.resistFingerprinting" is enabled, then we simply don't
// return any system-backed media feature values. (No spoofed values returned.)
return NS_OK;
}
MOZ_ASSERT(aFeature->mValueType == nsMediaFeature::eBoolInteger,
"unexpected type");
nsIAtom *metricAtom = *aFeature->mData.mMetric;
@ -321,6 +347,10 @@ GetWindowsTheme(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
nsCSSValue& aResult)
{
aResult.Reset();
if (ShouldResistFingerprinting(aPresContext)) {
return NS_OK;
}
#ifdef XP_WIN
uint8_t windowsThemeId =
nsCSSRuleProcessor::GetWindowsThemeIdentifier();
@ -346,6 +376,10 @@ GetOperatinSystemVersion(nsPresContext* aPresContext, const nsMediaFeature* aFea
nsCSSValue& aResult)
{
aResult.Reset();
if (ShouldResistFingerprinting(aPresContext)) {
return NS_OK;
}
#ifdef XP_WIN
int32_t metricResult;
if (NS_SUCCEEDED(

Просмотреть файл

@ -0,0 +1,274 @@
// # Bug 418986, part 2.
/* jshint esnext:true */
/* jshint loopfunc:true */
/* global window, screen, ok, SpecialPowers, matchMedia */
SimpleTest.waitForExplicitFinish();
// Expected values. Format: [name, pref_off_value, pref_on_value]
// If pref_*_value is an array with two values, then we will match
// any value in between those two values. If a value is null, then
// we skip the media query.
let expected_values = [
["color", null, 8],
["color-index", null, 0],
["aspect-ratio", null, window.innerWidth + "/" + window.innerHeight],
["device-aspect-ratio", screen.width + "/" + screen.height,
window.innerWidth + "/" + window.innerHeight],
["device-height", screen.height + "px", window.innerHeight + "px"],
["device-width", screen.width + "px", window.innerWidth + "px"],
["grid", null, 0],
["height", window.innerHeight + "px", window.innerHeight + "px"],
["monochrome", null, 0],
// Square is defined as portrait:
["orientation", null,
window.innerWidth > window.innerHeight ?
"landscape" : "portrait"],
["resolution", null, "96dpi"],
["resolution", [0.999 * window.devicePixelRatio + "dppx",
1.001 * window.devicePixelRatio + "dppx"], "1dppx"],
["width", window.innerWidth + "px", window.innerWidth + "px"],
["-moz-device-pixel-ratio", window.devicePixelRatio, 1],
["-moz-device-orientation", screen.width > screen.height ?
"landscape" : "portrait",
window.innerWidth > window.innerHeight ?
"landscape" : "portrait"]
];
// These media queries return value 0 or 1 when the pref is off.
// When the pref is on, they should not match.
let suppressed_toggles = [
"-moz-images-in-menus",
"-moz-mac-graphite-theme",
// Not available on most OSs.
// "-moz-maemo-classic",
"-moz-scrollbar-end-backward",
"-moz-scrollbar-end-forward",
"-moz-scrollbar-start-backward",
"-moz-scrollbar-start-forward",
"-moz-scrollbar-thumb-proportional",
"-moz-touch-enabled",
"-moz-windows-compositor",
"-moz-windows-default-theme",
"-moz-windows-glass",
];
// Possible values for '-moz-os-version'
let windows_versions = [
"windows-xp",
"windows-vista",
"windows-win7",
"windows-win8"];
// Possible values for '-moz-windows-theme'
let windows_themes = [
"aero",
"luna-blue",
"luna-olive",
"luna-silver",
"royale",
"generic",
"zune"
];
// Read the current OS.
let OS = SpecialPowers.Services.appinfo.OS;
// If we are using Windows, add an extra toggle only
// available on that OS.
if (OS === "WINNT") {
suppressed_toggles.push("-moz-windows-classic");
}
// __keyValMatches(key, val)__.
// Runs a media query and returns true if key matches to val.
let keyValMatches = (key, val) => matchMedia("(" + key + ":" + val +")").matches;
// __testMatch(key, val)__.
// Attempts to run a media query match for the given key and value.
// If value is an array of two elements [min max], then matches any
// value in-between.
let testMatch = function (key, val) {
if (val === null) {
return;
} else if (Array.isArray(val)) {
ok(keyValMatches("min-" + key, val[0]) && keyValMatches("max-" + key, val[1]),
"Expected " + key + " between " + val[0] + " and " + val[1]);
} else {
ok(keyValMatches(key, val), "Expected " + key + ":" + val);
}
};
// __testToggles(resisting)__.
// Test whether we are able to match the "toggle" media queries.
let testToggles = function (resisting) {
suppressed_toggles.forEach(
function (key) {
var exists = keyValMatches(key, 0) || keyValMatches(key, 1);
if (resisting) {
ok(!exists, key + " should not exist.");
} else {
ok(exists, key + " should exist.");
}
});
};
// __testWindowsSpecific__.
// Runs a media query on the queryName with the given possible matching values.
let testWindowsSpecific = function (resisting, queryName, possibleValues) {
let found = false;
possibleValues.forEach(function (val) {
found = found || keyValMatches(queryName, val);
});
if (resisting) {
ok(!found, queryName + " should have no match");
} else {
ok(found, queryName + " should match");
}
};
// __generateHtmlLines(resisting)__.
// Create a series of div elements that look like:
// `<div class='spoof' id='resolution'>resolution</div>`,
// where each line corresponds to a different media query.
let generateHtmlLines = function (resisting) {
let lines = "";
expected_values.forEach(
function ([key, offVal, onVal]) {
let val = resisting ? onVal : offVal;
if (val) {
lines += "<div class='spoof' id='" + key + "'>" + key + "</div>\n";
}
});
suppressed_toggles.forEach(
function (key) {
lines += "<div class='suppress' id='" + key + "'>" + key + "</div>\n";
});
if (OS === "WINNT") {
lines += "<div class='windows' id='-moz-os-version'>-moz-os-version</div>";
lines += "<div class='windows' id='-moz-windows-theme'>-moz-windows-theme</div>";
}
return lines;
};
// __cssLine__.
// Creates a line of css that looks something like
// `@media (resolution: 1ppx) { .spoof#resolution { background-color: green; } }`.
let cssLine = function (query, clazz, id, color) {
return "@media " + query + " { ." + clazz + "#" + id +
" { background-color: " + color + "; } }\n";
};
// __mediaQueryCSSLine(key, val, color)__.
// Creates a line containing a CSS media query and a CSS expression.
let mediaQueryCSSLine = function (key, val, color) {
if (val === null) {
return "";
}
let query;
if (Array.isArray(val)) {
query = "(min-" + key + ": " + val[0] + ") and (max-" + key + ": " + val[1] + ")";
} else {
query = "(" + key + ": " + val + ")";
}
return cssLine(query, "spoof", key, color);
};
// __suppressedMediaQueryCSSLine(key, color)__.
// Creates a CSS line that matches the existence of a
// media query that is supposed to be suppressed.
let suppressedMediaQueryCSSLine = function (key, color, suppressed) {
let query = "(" + key + ": 0), (" + key + ": 1)";
return cssLine(query, "suppress", key, color);
};
// __generateCSSLines(resisting)__.
// Creates a series of lines of CSS, each of which corresponds to
// a different media query. If the query produces a match to the
// expected value, then the element will be colored green.
let generateCSSLines = function (resisting) {
let lines = ".spoof { background-color: red;}\n";
expected_values.forEach(
function ([key, offVal, onVal]) {
lines += mediaQueryCSSLine(key, resisting ? onVal : offVal, "green");
});
lines += ".suppress { background-color: " + (resisting ? "green" : "red") + ";}\n";
suppressed_toggles.forEach(
function (key) {
lines += suppressedMediaQueryCSSLine(key, resisting ? "red" : "green");
});
if (OS === "WINNT") {
lines += ".windows { background-color: " + (resisting ? "green" : "red") + ";}\n";
lines += windows_versions.map(val => "(-moz-os-version: " + val + ")").join(", ") +
" { #-moz-os-version { background-color: " + (resisting ? "red" : "green") + ";} }\n";
lines += windows_themes.map(val => "(-moz-windows-theme: " + val + ")").join(",") +
" { #-moz-windows-theme { background-color: " + (resisting ? "red" : "green") + ";} }\n";
}
return lines;
};
// __green__.
// Returns the computed color style corresponding to green.
let green = (function () {
let temp = document.createElement("span");
temp.style.backgroundColor = "green";
return getComputedStyle(temp).backgroundColor;
})();
// __testCSS(resisting)__.
// Creates a series of divs and CSS using media queries to set their
// background color. If all media queries match as expected, then
// all divs should have a green background color.
let testCSS = function (resisting) {
document.getElementById("display").innerHTML = generateHtmlLines(resisting);
document.getElementById("test-css").innerHTML = generateCSSLines(resisting);
let cssTestDivs = document.querySelectorAll(".spoof,.suppress");
for (let div of cssTestDivs) {
let color = window.getComputedStyle(div).backgroundColor;
ok(color === green, "CSS for '" + div.id + "'");
}
};
// __testOSXFontSmoothing(resisting)__.
// When fingerprinting resistance is enabled, the `getComputedStyle`
// should always return `undefined` for `MozOSXFontSmoothing`.
let testOSXFontSmoothing = function (resisting) {
let div = document.createElement("div");
div.style.MozOsxFontSmoothing = "unset";
let readBack = window.getComputedStyle(div).MozOsxFontSmoothing;
let smoothingPref = SpecialPowers.getBoolPref("layout.css.osx-font-smoothing.enabled", false);
is(readBack, resisting ? "" : (smoothingPref ? "auto" : ""),
"-moz-osx-font-smoothing");
};
// An iterator yielding pref values for two consecutive tests.
let prefVals = (for (prefVal of [false, true]) prefVal);
// __test(isContent)__.
// Run all tests.
let test = function(isContent) {
let {value: prefValue, done} = prefVals.next();
if (done) {
SimpleTest.finish();
return;
}
SpecialPowers.pushPrefEnv({set: [["privacy.resistFingerprinting", prefValue]]},
function () {
let resisting = prefValue && isContent;
expected_values.forEach(
function ([key, offVal, onVal]) {
testMatch(key, resisting ? onVal : offVal);
});
testToggles(resisting);
if (OS === "WINNT") {
testWindowsSpecific(resisting, "-moz-os-version", windows_versions);
testWindowsSpecific(resisting, "-moz-windows-theme", windows_themes);
}
testCSS(resisting);
if (OS === "Darwin") {
testOSXFontSmoothing(resisting);
}
test(isContent);
});
};

Просмотреть файл

@ -1,6 +1,7 @@
[DEFAULT]
skip-if = buildapp == 'b2g'
support-files =
bug418986-2.js
bug535806-css.css
bug535806-html.html
bug535806-xul.xul
@ -9,6 +10,7 @@ support-files =
[test_addSheet.html]
[test_additional_sheets.html]
[test_author_specified_style.html]
[test_bug418986-2.xul]
[test_bug1157097.html]
[test_bug1160724.xul]
[test_bug535806.xul]

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
-->
<window title="Mozilla Bug 418986"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<style id="test-css" scoped="true"></style>
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986"
target="_blank">Mozilla Bug 418986</a>
<p id="display"></p>
</body>
<script type="text/javascript;version=1.7" src="bug418986-2.js"></script>
<!-- test code goes here -->
<script type="text/javascript;version=1.7">
// Run all tests now.
window.onload = function () {
test(false);
};
</script>
</window>

Просмотреть файл

@ -5,6 +5,7 @@ support-files =
ccd.sjs
ccd-standards.html
css_properties.js
chrome/bug418986-2.js
descriptor_database.js
empty.html
media_queries_dynamic_xbl_binding.xml
@ -75,6 +76,7 @@ skip-if = true # Bug 701060
[test_bug412901.html]
skip-if = android_version == '18' # bug 1147986
[test_bug413958.html]
[test_bug418986-2.html]
[test_bug437915.html]
[test_bug450191.html]
[test_bug453896_deck.html]

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
-->
<head>
<meta charset="utf-8">
<title>Test 2/3 for Bug #418986: Resist fingerprinting by preventing exposure of screen and system info</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style id="test-css"></style>
<script type="text/javascript;version=1.7" src="chrome/bug418986-2.js"></script>
<script type="text/javascript;version=1.7">
// Run all tests now.
window.onload = function () {
test(true);
};
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
<p id="display">TEST</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>