Bug 1643205 - Navigator's share() must consume user activation r=edgar

navigator.share() must consume the user activation.

Differential Revision: https://phabricator.services.mozilla.com/D79640
This commit is contained in:
Marcos Cáceres 2020-06-17 10:18:41 +00:00
Родитель 180b52ef6f
Коммит 338877b3cb
8 изменённых файлов: 147 добавлений и 34 удалений

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

@ -1374,6 +1374,17 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
return nullptr;
}
// null checked above
auto* doc = mWindow->GetExtantDoc();
if (StaticPrefs::dom_webshare_requireinteraction() &&
!doc->ConsumeTransientUserGestureActivation()) {
aRv.ThrowNotAllowedError(
"User activation was already consumed "
"or share() was not activated by a user gesture.");
return nullptr;
}
// If none of data's members title, text, or url are present, reject p with
// TypeError, and abort these steps.
bool someMemberPassed = aData.mTitle.WasPassed() || aData.mText.WasPassed() ||
@ -1384,9 +1395,6 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
return nullptr;
}
// null checked above
auto doc = mWindow->GetExtantDoc();
// If data's url member is present, try to resolve it...
nsCOMPtr<nsIURI> url;
if (aData.mUrl.WasPassed()) {
@ -1415,16 +1423,6 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
text.SetIsVoid(true);
}
// The spec does the "triggered by user activation" after the data checks.
// Unfortunately, both Chrome and Safari behave this way, so interop wins.
// https://github.com/w3c/web-share/pull/118
if (StaticPrefs::dom_webshare_requireinteraction() &&
!UserActivation::IsHandlingUserInput()) {
NS_WARNING("Attempt to share not triggered by user activation");
aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return nullptr;
}
// Let mSharePromise be a new promise.
mSharePromise = Promise::Create(mWindow->AsGlobal(), aRv);
if (aRv.Failed()) {

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

@ -86,6 +86,7 @@ DIRS += [
'url',
'webauthn',
'webidl',
'webshare',
'xml',
'xslt',
'xul',

11
dom/webshare/moz.build Normal file
Просмотреть файл

@ -0,0 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']

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

@ -0,0 +1,5 @@
[DEFAULT]
prefs =
dom.webshare.enabled=true
scheme = https
[test_navigator_share_consume_user_activation.html]

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

@ -0,0 +1,43 @@
<!DOCTYPE html>
<title>Web Share: consume transient activation</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
<body>
<button id="share">Share</button>
</body>
<script>
// TODO: add a task that tests share() consume the transient user activation.
// Because OS-level prompt can't be cancelled, it's currently not possible to
// test this. We need to add a Web share Mocking service:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1646229
// test that share() would be blocked with an already-consumed-activation.
add_task(async function blockedIfAlreadyConsumed() {
const wrappedDoc = SpecialPowers.wrap(document);
const button = document.getElementById("share");
// Kick off transient activation
synthesizeMouseAtCenter(button, {});
ok(
wrappedDoc.hasValidTransientUserGestureActivation,
"Activated by a gesture"
);
wrappedDoc.consumeTransientUserGestureActivation();
try {
const sharePromise = navigator.share({ title: "test" });
await sharePromise;
ok(false, "must throw because activation was already consumed");
} catch (err) {
is(err.name, "NotAllowedError", "Expected NotAllowedError DOMException");
} finally {
ok(
!wrappedDoc.hasValidTransientUserGestureActivation,
"share() must consume the activation"
);
}
});
</script>

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

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebShare Test: consume user activation</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/manual-helper.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver.js"></script>
</head>
<body>
<script>
promise_test(async t => {
// Not activated by user gesture, so not allowed!
await promise_rejects_dom(t, "NotAllowedError", navigator.share());
await test_driver.bless("web share");
// We have a gesture, but the URL is invalid - so TypeError!
await promise_rejects_js(
t,
TypeError,
navigator.share({ url: "http://example.com:65536" })
);
// The activation has been consumed, so calling share() again would require
// a new gesture.
await promise_rejects_dom(t, "NotAllowedError", navigator.share());
}, "Calling share consumes user activation");
</script>
</body>
</html>

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

@ -1,34 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>WebShare Test: Share no known fields</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/manual-helper.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver.js"></script>
</head>
<body>
<script>
promise_test(t => {
promise_test(async t => {
await test_driver.bless("web share", () => {
return promise_rejects_js(t, TypeError, navigator.share());
}, 'share with no arguments (same as empty dictionary)');
});
}, "share with no arguments (same as empty dictionary)");
promise_test(t => {
promise_test(async t => {
await test_driver.bless("web share", () => {
return promise_rejects_js(t, TypeError, navigator.share({}));
}, 'share with an empty dictionary');
});
}, "share with an empty dictionary");
promise_test(t => {
promise_test(async t => {
await test_driver.bless("web share", () => {
return promise_rejects_js(t, TypeError, navigator.share(undefined));
}, 'share with a undefined argument (same as empty dictionary)');
});
}, "share with a undefined argument (same as empty dictionary)");
promise_test(t => {
promise_test(async t => {
await test_driver.bless("web share", () => {
return promise_rejects_js(t, TypeError, navigator.share(null));
}, 'share with a null argument (same as empty dictionary)');
});
}, "share with a null argument (same as empty dictionary)");
promise_test(t => {
return promise_rejects_js(t,
TypeError, navigator.share({unused: 'unexpected field'}));
}, 'share with a dictionary containing only surplus fields');
promise_test(async t => {
await test_driver.bless("web share", () => {
return promise_rejects_js(
t,
TypeError,
navigator.share({ unused: "unexpected field" })
);
});
}, "share with a dictionary containing only surplus fields");
</script>
</body>
</html>

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

@ -1,20 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>WebShare Test: Share with an invalid URL</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver.js"></script>
</head>
<body>
<script>
promise_test(t => {
// URL is invalid in that the URL Parser returns failure (port is too
// large).
const url = 'http://example.com:65536';
return promise_rejects_js(
t, TypeError, navigator.share({url}));
}, 'share with an invalid URL');
promise_test(async t => {
// URL is invalid in that the URL Parser returns failure (port is too
// large).
const url = "http://example.com:65536";
await test_driver.bless(
"web share",
() => {
return promise_rejects_js(t, TypeError, navigator.share({ url }));
},
"share with an invalid URL"
);
});
</script>
</body>
</html>