Bug 1653549 - don't sent a location if document is hidden r=edgar
authorMarcos Cáceres <marcos@marcosc.com>
Mon, 19 Jul 2021 01:20:55 +0000
changeset 585911 5388c654aee14b0a7e2b72ab06d89577454b2a39
parent 585910 b181b8a09ecb720839b187341cb0e5b1420f84b5
child 585912 675e63b1d3184436be699861305d6296dd96df6f
push id146466
push usermarcos@marcosc.com
push dateMon, 19 Jul 2021 01:23:24 +0000
treeherderautoland@5388c654aee1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedgar
bugs1653549
milestone92.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1653549 - don't sent a location if document is hidden r=edgar When the document is hidden from view, silently drop position updates on the floor. This aligns, more or less, with Chrome and Safari. The situation with "not fully active" is a bit more quirky, because non-fully active documents are generally hidden (e.g., `remove()`ing and iframe). Regardless, I think this gives us the desired behavior and covers the main privacy case: background tabs should not receive position updates. Differential Revision: https://phabricator.services.mozilla.com/D109279
dom/geolocation/Geolocation.cpp
dom/tests/mochitest/geolocation/mochitest.ini
dom/tests/mochitest/geolocation/popup.html
dom/tests/mochitest/geolocation/test_hidden.html
--- a/dom/geolocation/Geolocation.cpp
+++ b/dom/geolocation/Geolocation.cpp
@@ -857,16 +857,28 @@ void Geolocation::RemoveRequest(nsGeoloc
 
 NS_IMETHODIMP
 Geolocation::Update(nsIDOMGeoPosition* aSomewhere) {
   if (!WindowOwnerStillExists()) {
     Shutdown();
     return NS_OK;
   }
 
+  // Don't update position if window is not fully active or the document is
+  // hidden. We keep the pending callaback and watchers waiting for the next
+  // update.
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner());
+  if (window) {
+    nsCOMPtr<Document> document = window->GetDoc();
+    bool isHidden = document && document->Hidden();
+    if (isHidden || !window->IsFullyActive()) {
+      return NS_OK;
+    }
+  }
+
   if (aSomewhere) {
     nsCOMPtr<nsIDOMGeoPositionCoords> coords;
     aSomewhere->GetCoords(getter_AddRefs(coords));
     if (coords) {
       double accuracy = -1;
       coords->GetAccuracy(&accuracy);
       mozilla::Telemetry::Accumulate(
           mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy);
--- a/dom/tests/mochitest/geolocation/mochitest.ini
+++ b/dom/tests/mochitest/geolocation/mochitest.ini
@@ -1,26 +1,31 @@
 [DEFAULT]
 tags = geolocation
 scheme = https
 support-files =
   geolocation.html
   geolocation_common.js
   network_geolocation.sjs
   windowTest.html
+  popup.html
 prefs =
   dom.security.featurePolicy.header.enabled=true
   dom.security.featurePolicy.webidl.enabled=true
 
 [test_allowCurrent.html]
 skip-if = xorigin  # Hangs
 [test_allowWatch.html]
 skip-if = xorigin  # Hangs
+[test_hidden.html]
+skip-if = xorigin # Hangs
+  toolkit == 'android' # test uses popup windows
+support-files = popup.html
 [test_cachedPosition.html]
-fail-if = xorigin  
+fail-if = xorigin
 [test_cancelCurrent.html]
 [test_cancelWatch.html]
 [test_clearWatch.html]
 skip-if = xorigin  # Hangs
 [test_clearWatchBeforeAllowing.html]
 skip-if = xorigin  # Hangs
 [test_clearWatch_invalid.html]
 [test_crossorigin_iframe.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/geolocation/popup.html
@@ -0,0 +1,19 @@
+<html>
+  <head>
+    <title>Simple access of geolocation</title>
+    <script src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script src="geolocation_common.js"></script>
+    <meta charset="utf-8">
+  <head>
+    <script>
+      async function loadedWindow() {
+        await new Promise(r => force_prompt(true, r));
+        opener.postMessage("initialized", "*");
+      }
+      navigator.geolocation.getCurrentPosition(loadedWindow, loadedWindow, {timeout:30000});
+    </script>
+  </head>
+  <body>
+    <h1>Just a support file</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/geolocation/test_hidden.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1653549
+-->
+<meta charset="utf-8">
+<title>Test that geolocation position can't be gotten when document is hidden</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="geolocation_common.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1653549">Mozilla Bug 1653549</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody">
+SimpleTest.waitForExplicitFinish();
+
+// Little promise wrapper helper.
+function p(f) {
+  return new Promise((r) => f(r));
+}
+
+resume_geolocationProvider(async () => {
+  // Initialize
+  await new Promise((r) => force_prompt(true, r));
+  const popupWindow = window.open("popup.html");
+  popupWindow.opener = window;
+  await new Promise((r) =>
+    window.addEventListener("message", r, { once: true })
+  );
+
+  // Confirm everything is working ok...
+  const geo = popupWindow.navigator.geolocation;
+  await new Promise((resolve, reject) => {
+    geo.getCurrentPosition(resolve, reject);
+  });
+
+  // Hide the document...
+  const hiddenPromise = new Promise(
+    (r) => (popupWindow.document.onvisibilitychange = r)
+  );
+  await SimpleTest.promiseFocus(window);
+  await hiddenPromise;
+
+  // The following promises only resolve successfully when document is visible,
+  // meaning that position updates are ignored when the document is hidden.
+  let success = false;
+  let watchId = null;
+  const watchPositionPromise = new Promise((resolve) => {
+    watchId = geo.watchPosition(
+      () => {
+        ok(success, "watchPosition was called.");
+        if (!success) {
+          throw new Error("watchPosition was called too early");
+        }
+        resolve();
+      },
+      () => {
+        ok(false, "Error callback of watchPosition must not be called.");
+      }
+    );
+  });
+
+  const currentPositionPromise = new Promise((resolve) => {
+    geo.getCurrentPosition(
+      () => {
+        ok(success, "getCurrentPosition was called.");
+        if (!success){
+          throw new Error("getCurrentPosition was called too early");
+        }
+        resolve();
+      },
+      () => {
+        ok(false, "Error callback of getCurrentPosition must not be called.");
+      }
+    );
+  });
+
+  // Send data to be ignored...
+  await p(start_sending_garbage);
+  await p(stop_sending_garbage);
+  await p(resume_geolocationProvider);
+
+  // Refocus popup window...
+  const visiblePopupPromise = new Promise(
+    (r) => (popupWindow.document.onvisibilitychange = r)
+  );
+  await SimpleTest.promiseFocus(popupWindow);
+  await visiblePopupPromise;
+
+  // Resuming the geolocation events must now cause the promises to resolve correctly (with success = true).
+  success = true;
+  await p(resume_geolocationProvider);
+  await Promise.all([currentPositionPromise, watchPositionPromise]);
+
+  // Cleanup and finish!
+  geo.clearWatch(watchId);
+  await SimpleTest.promiseFocus(window);
+  popupWindow.close();
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
+