зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
180b52ef6f
Коммит
338877b3cb
|
@ -1374,6 +1374,17 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
|
||||||
return nullptr;
|
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
|
// If none of data's members title, text, or url are present, reject p with
|
||||||
// TypeError, and abort these steps.
|
// TypeError, and abort these steps.
|
||||||
bool someMemberPassed = aData.mTitle.WasPassed() || aData.mText.WasPassed() ||
|
bool someMemberPassed = aData.mTitle.WasPassed() || aData.mText.WasPassed() ||
|
||||||
|
@ -1384,9 +1395,6 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// null checked above
|
|
||||||
auto doc = mWindow->GetExtantDoc();
|
|
||||||
|
|
||||||
// If data's url member is present, try to resolve it...
|
// If data's url member is present, try to resolve it...
|
||||||
nsCOMPtr<nsIURI> url;
|
nsCOMPtr<nsIURI> url;
|
||||||
if (aData.mUrl.WasPassed()) {
|
if (aData.mUrl.WasPassed()) {
|
||||||
|
@ -1415,16 +1423,6 @@ Promise* Navigator::Share(const ShareData& aData, ErrorResult& aRv) {
|
||||||
text.SetIsVoid(true);
|
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.
|
// Let mSharePromise be a new promise.
|
||||||
mSharePromise = Promise::Create(mWindow->AsGlobal(), aRv);
|
mSharePromise = Promise::Create(mWindow->AsGlobal(), aRv);
|
||||||
if (aRv.Failed()) {
|
if (aRv.Failed()) {
|
||||||
|
|
|
@ -86,6 +86,7 @@ DIRS += [
|
||||||
'url',
|
'url',
|
||||||
'webauthn',
|
'webauthn',
|
||||||
'webidl',
|
'webidl',
|
||||||
|
'webshare',
|
||||||
'xml',
|
'xml',
|
||||||
'xslt',
|
'xslt',
|
||||||
'xul',
|
'xul',
|
||||||
|
|
|
@ -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>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>WebShare Test: Share no known fields</title>
|
<title>WebShare Test: Share no known fields</title>
|
||||||
<script src="/resources/testharness.js"></script>
|
<script src="/resources/testharness.js"></script>
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
<script src="resources/manual-helper.js"></script>
|
<script src="resources/manual-helper.js"></script>
|
||||||
|
<script src="/resources/testdriver-vendor.js"></script>
|
||||||
|
<script src="/resources/testdriver.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
promise_test(t => {
|
promise_test(async t => {
|
||||||
|
await test_driver.bless("web share", () => {
|
||||||
return promise_rejects_js(t, TypeError, navigator.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({}));
|
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));
|
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));
|
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 => {
|
promise_test(async t => {
|
||||||
return promise_rejects_js(t,
|
await test_driver.bless("web share", () => {
|
||||||
TypeError, navigator.share({unused: 'unexpected field'}));
|
return promise_rejects_js(
|
||||||
}, 'share with a dictionary containing only surplus fields');
|
t,
|
||||||
|
TypeError,
|
||||||
|
navigator.share({ unused: "unexpected field" })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, "share with a dictionary containing only surplus fields");
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>WebShare Test: Share with an invalid URL</title>
|
<title>WebShare Test: Share with an invalid URL</title>
|
||||||
<script src="/resources/testharness.js"></script>
|
<script src="/resources/testharness.js"></script>
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/resources/testdriver-vendor.js"></script>
|
||||||
|
<script src="/resources/testdriver.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
promise_test(t => {
|
promise_test(async t => {
|
||||||
// URL is invalid in that the URL Parser returns failure (port is too
|
// URL is invalid in that the URL Parser returns failure (port is too
|
||||||
// large).
|
// large).
|
||||||
const url = 'http://example.com:65536';
|
const url = "http://example.com:65536";
|
||||||
return promise_rejects_js(
|
await test_driver.bless(
|
||||||
t, TypeError, navigator.share({url}));
|
"web share",
|
||||||
}, 'share with an invalid URL');
|
() => {
|
||||||
|
return promise_rejects_js(t, TypeError, navigator.share({ url }));
|
||||||
|
},
|
||||||
|
"share with an invalid URL"
|
||||||
|
);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче