Bug 1715482 - Geolocation should gracefully handle Documents that are not fully active r=saschanaz

Spec change https://github.com/w3c/geolocation-api/pull/97

Differential Revision: https://phabricator.services.mozilla.com/D117273
This commit is contained in:
Marcos Cáceres 2021-08-03 00:16:45 +00:00
Родитель 24feffa455
Коммит fc57eb2e71
5 изменённых файлов: 174 добавлений и 22 удалений

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

@ -922,6 +922,16 @@ Geolocation::NotifyError(uint16_t aErrorCode) {
return NS_OK;
}
bool Geolocation::IsFullyActiveOrChrome() {
// For regular content window, only allow this proceed if the window is "fully
// active".
if (nsPIDOMWindowInner* window = this->GetParentObject()) {
return window->IsFullyActive();
}
// Calls coming from chrome code don't have window, so we can proceed.
return true;
}
bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) {
for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
if (mClearedWatchIDs[i] == aRequest->WatchId()) {
@ -993,6 +1003,14 @@ nsresult Geolocation::GetCurrentPosition(GeoPositionCallback callback,
GeoPositionErrorCallback errorCallback,
UniquePtr<PositionOptions>&& options,
CallerType aCallerType) {
if (!IsFullyActiveOrChrome()) {
RefPtr<GeolocationPositionError> positionError =
new GeolocationPositionError(
this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
positionError->NotifyCallback(errorCallback);
return NS_OK;
}
if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -1059,6 +1077,14 @@ int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
UniquePtr<PositionOptions>&& aOptions,
CallerType aCallerType, ErrorResult& aRv) {
if (!IsFullyActiveOrChrome()) {
RefPtr<GeolocationPositionError> positionError =
new GeolocationPositionError(
this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
positionError->NotifyCallback(aErrorCallback);
return 0;
}
if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return 0;

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

@ -138,6 +138,8 @@ class Geolocation final : public nsIGeolocationUpdate, public nsWrapperCache {
PositionErrorCallback* aErrorCallback,
const PositionOptions& aOptions, CallerType aCallerType,
ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT
void GetCurrentPosition(PositionCallback& aCallback,
PositionErrorCallback* aErrorCallback,
const PositionOptions& aOptions,
@ -185,10 +187,12 @@ class Geolocation final : public nsIGeolocationUpdate, public nsWrapperCache {
private:
~Geolocation();
MOZ_CAN_RUN_SCRIPT
nsresult GetCurrentPosition(GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
UniquePtr<PositionOptions>&& aOptions,
CallerType aCallerType);
MOZ_CAN_RUN_SCRIPT
int32_t WatchPosition(GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
@ -204,6 +208,10 @@ class Geolocation final : public nsIGeolocationUpdate, public nsWrapperCache {
// within a context that is not secure.
bool ShouldBlockInsecureRequests() const;
// Checks if the request is in a content window that is fully active, or the
// request is coming from a chrome window.
bool IsFullyActiveOrChrome();
// Two callback arrays. The first |mPendingCallbacks| holds objects for only
// one callback and then they are released/removed from the array. The second
// |mWatchingCallbacks| holds objects until the object is explictly removed or

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

@ -4,29 +4,51 @@
<!--
Test for Geolocation in chrome
-->
<window id="sample-window" width="400" height="400"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<window
id="sample-window"
width="400"
height="400"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
<script
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"
></script>
<script>
SimpleTest.waitForExplicitFinish();
<script>
SimpleTest.waitForExplicitFinish();
async function test() {
/** @type {Geolocation} */
const geolocation = Cc["@mozilla.org/geolocation;1"].getService(
Ci.nsISupports
);
try {
// Watch position
let watchId;
let position = await new Promise((resolve, reject) => {
watchId = geolocation.watchPosition(resolve, reject, { timeout: 0 });
});
ok(position, "watchPosition() callable from chrome");
geolocation.clearWatch(watchId);
var geolocation = Cc["@mozilla.org/geolocation;1"].getService(Ci.nsISupports);
geolocation.getCurrentPosition(done, error);
function error(error)
{
ok(0, "error occured trying to get geolocation from chrome");
SimpleTest.finish();
}
function done(position)
{
ok(position, "geolocation was found from chrome");
SimpleTest.finish();
}
</script>
<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
// Get position
position = await new Promise((resolve, reject) =>
geolocation.getCurrentPosition(resolve, reject)
);
ok(position, "getCurrentPosition() callable from chrome");
} catch (err) {
ok(
false,
"error occurred trying to get geolocation from chrome: " + err.message
);
} finally {
SimpleTest.finish();
}
}
</script>
<body
xmlns="http://www.w3.org/1999/xhtml"
style="height: 300px; overflow: auto;"
onload="test()"
/>
</window>

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

@ -57,6 +57,9 @@ skip-if = xorigin # Hangs
[test_featurePolicy.html]
support-files = file_featurePolicy.html
fail-if = xorigin
[test_not_fully_active.html]
skip-if = xorigin # Hangs
support-files = popup.html
# This test REQUIRES to run on HTTP (_NOT_ HTTPS).
[test_geoWatchPositionBlockedInInsecureContext.html]

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

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test for when geolocation is used on non fully active documents
</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="geolocation_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script>
SimpleTest.waitForExplicitFinish();
async function runTest() {
// Create the iframe, wait for it to load...
const iframe = document.createElement("iframe");
// We rely on this popup.html to acquire prompt privileges.
iframe.src = "popup.html";
document.body.appendChild(iframe);
iframe.contentWindow.opener = window;
await new Promise(r => window.addEventListener("message", r, { once: true }));
// Steal geolocation.
const geo = iframe.contentWindow.navigator.geolocation;
// No longer fully active.
iframe.remove();
// Try to watch a position while not fully active...
const watchError = await new Promise((resolve, reject) => {
const result = geo.watchPosition(
reject, // We don't want a position
resolve // We want an error!
);
is(result, 0, "watchPosition returns 0 on non-fully-active document");
});
is(
watchError.code,
GeolocationPositionError.POSITION_UNAVAILABLE,
"watchPosition returns an error on non-fully-active document"
);
// Now try to get current position while not fully active...
const positionError = await new Promise((resolve, reject) => {
const result = geo.getCurrentPosition(
reject, // We don't want a position
resolve // We want an error!
);
});
is(
positionError.code,
GeolocationPositionError.POSITION_UNAVAILABLE,
"getCurrentPosition returns an error on non-fully-active document"
);
// Re-attach, and go back to fully active.
document.body.appendChild(iframe);
iframe.contentWindow.opener = window;
await new Promise(r => window.addEventListener("message", r, { once: true }));
// And we are back to fully active.
let watchId;
let position = await new Promise((resolve, reject) => {
watchId = iframe.contentWindow.navigator.geolocation.watchPosition(
resolve,
reject
);
});
ok(watchId > 0, "Expected anything greater than 0");
ok(position, "Expected a position");
// Finally, let's get the position from the reattached document.
position = await new Promise((resolve, reject) => {
iframe.contentWindow.navigator.geolocation.getCurrentPosition(
resolve,
reject
);
});
ok(position, "Expected a position");
iframe.contentWindow.navigator.geolocation.clearWatch(watchId);
iframe.remove();
}
resume_geolocationProvider(async () => {
await new Promise(r => force_prompt(true, r));
await runTest();
SimpleTest.finish();
});
</script>
</body>
</html>