зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1527074 - Expose storage manager API to GeckoView r=geckoview-reviewers,snorp
Differential Revision: https://phabricator.services.mozilla.com/D25408 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
15e75bcd4d
Коммит
1cdb609e6a
|
@ -609,10 +609,18 @@ nsresult PersistentStoragePermissionRequest::Start() {
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
PromptResult pr;
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// on Android calling `ShowPrompt` here calls `nsContentPermissionUtils::AskPermission`
|
||||
// once, and a response of `PromptResult::Pending` calls it again. This results in
|
||||
// multiple requests for storage access, so we check the prompt prefs only to ensure we
|
||||
// only request it once.
|
||||
pr = CheckPromptPrefs();
|
||||
#else
|
||||
nsresult rv = ShowPrompt(pr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
#endif
|
||||
if (pr == PromptResult::Granted) {
|
||||
return Allow(JS::UndefinedHandleValue);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ pref("geckoview.logging", "Debug");
|
|||
// Disable Web Push until we get it working
|
||||
pref("dom.push.enabled", false);
|
||||
|
||||
// enable external storage API
|
||||
pref("dom.storageManager.enabled", true);
|
||||
|
||||
// Use containerless scrolling.
|
||||
pref("layout.scroll.root-frame-containers", 0);
|
||||
|
||||
|
|
|
@ -546,6 +546,7 @@ package org.mozilla.geckoview {
|
|||
method @UiThread default public void onMediaPermissionRequest(@NonNull GeckoSession, @NonNull String, @Nullable GeckoSession.PermissionDelegate.MediaSource[], @Nullable GeckoSession.PermissionDelegate.MediaSource[], @NonNull GeckoSession.PermissionDelegate.MediaCallback);
|
||||
field public static final int PERMISSION_DESKTOP_NOTIFICATION = 1;
|
||||
field public static final int PERMISSION_GEOLOCATION = 0;
|
||||
field public static final int PERMISSION_PERSISTENT_STORAGE = 2;
|
||||
}
|
||||
|
||||
public static interface GeckoSession.PermissionDelegate.Callback {
|
||||
|
|
|
@ -41,11 +41,11 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
@WithDevToolsAPI
|
||||
@Test fun media() {
|
||||
assertInAutomationThat("Should have camera permission",
|
||||
hasPermission(Manifest.permission.CAMERA), equalTo(true))
|
||||
hasPermission(Manifest.permission.CAMERA), equalTo(true))
|
||||
|
||||
assertInAutomationThat("Should have microphone permission",
|
||||
hasPermission(Manifest.permission.RECORD_AUDIO),
|
||||
equalTo(true))
|
||||
hasPermission(Manifest.permission.RECORD_AUDIO),
|
||||
equalTo(true))
|
||||
|
||||
// Media test is relatively resource-intensive. Clean up resources from previous tests
|
||||
// first to improve the stability of this test.
|
||||
|
@ -58,9 +58,9 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
"window.navigator.mediaDevices.enumerateDevices()")
|
||||
|
||||
assertThat("Device list should contain camera device",
|
||||
devices.asJSList<Any>(), hasItem(hasEntry("kind", "videoinput")))
|
||||
devices.asJSList<Any>(), hasItem(hasEntry("kind", "videoinput")))
|
||||
assertThat("Device list should contain microphone device",
|
||||
devices.asJSList<Any>(), hasItem(hasEntry("kind", "audioinput")))
|
||||
devices.asJSList<Any>(), hasItem(hasEntry("kind", "audioinput")))
|
||||
|
||||
|
||||
mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
|
||||
|
@ -74,10 +74,10 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
assertThat("Video source should be valid", video, not(emptyArray()))
|
||||
|
||||
if (isEmulator()) {
|
||||
callback.grant(video!![0], null)
|
||||
callback.grant(video!![0], null)
|
||||
} else {
|
||||
assertThat("Audio source should be valid", audio, not(emptyArray()))
|
||||
callback.grant(video!![0], audio!![0])
|
||||
assertThat("Audio source should be valid", audio, not(emptyArray()))
|
||||
callback.grant(video!![0], audio!![0])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -85,11 +85,11 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
// Start a video stream, with audio if on a real device.
|
||||
var code: String?
|
||||
if (isEmulator()) {
|
||||
code = """window.navigator.mediaDevices.getUserMedia({
|
||||
code = """window.navigator.mediaDevices.getUserMedia({
|
||||
video: { width: 320, height: 240, frameRate: 10 },
|
||||
})"""
|
||||
} else {
|
||||
code = """window.navigator.mediaDevices.getUserMedia({
|
||||
code = """window.navigator.mediaDevices.getUserMedia({
|
||||
video: { width: 320, height: 240, frameRate: 10 },
|
||||
audio: true
|
||||
})"""
|
||||
|
@ -97,9 +97,9 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
val stream = mainSession.waitForJS(code)
|
||||
|
||||
assertThat("Stream should be active", stream.asJSMap(),
|
||||
hasEntry("active", true))
|
||||
hasEntry("active", true))
|
||||
assertThat("Stream should have ID", stream.asJSMap(),
|
||||
hasEntry(equalTo("id"), not(isEmptyString())))
|
||||
hasEntry(equalTo("id"), not(isEmptyString())))
|
||||
|
||||
// Stop the stream.
|
||||
mainSession.waitForJS(
|
||||
|
@ -129,15 +129,15 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
fail("Request should have failed")
|
||||
} catch (e: RejectedPromiseException) {
|
||||
assertThat("Error should be correct",
|
||||
e.reason.asJSMap(), hasEntry("name", "NotAllowedError"))
|
||||
e.reason.asJSMap(), hasEntry("name", "NotAllowedError"))
|
||||
}
|
||||
}
|
||||
|
||||
@WithDevToolsAPI
|
||||
@Test fun geolocation() {
|
||||
assertInAutomationThat("Should have location permission",
|
||||
hasPermission(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
equalTo(true))
|
||||
hasPermission(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
equalTo(true))
|
||||
|
||||
mainSession.loadTestPath(HELLO_HTML_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
@ -150,7 +150,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
|
||||
assertThat("Type should match", type,
|
||||
equalTo(GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION))
|
||||
equalTo(GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION))
|
||||
callback.grant()
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
session: GeckoSession, permissions: Array<out String>?,
|
||||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
assertThat("Permissions list should be correct",
|
||||
listOf(*permissions!!), hasItems(Manifest.permission.ACCESS_FINE_LOCATION))
|
||||
listOf(*permissions!!), hasItems(Manifest.permission.ACCESS_FINE_LOCATION))
|
||||
callback.grant()
|
||||
}
|
||||
})
|
||||
|
@ -168,9 +168,9 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
window.navigator.geolocation.getCurrentPosition(resolve, reject))""")
|
||||
|
||||
assertThat("Request should succeed",
|
||||
position.asJSMap(),
|
||||
hasEntry(equalTo("coords"),
|
||||
both(hasKey("longitude")).and(hasKey("latitude"))))
|
||||
position.asJSMap(),
|
||||
hasEntry(equalTo("coords"),
|
||||
both(hasKey("longitude")).and(hasKey("latitude"))))
|
||||
}
|
||||
|
||||
@WithDevToolsAPI
|
||||
|
@ -197,7 +197,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
window.navigator.geolocation.getCurrentPosition(reject, resolve))""")
|
||||
|
||||
assertThat("Request should fail",
|
||||
error.asJSMap(), hasEntry("code", 1.0)) // Error code 1 means permission denied.
|
||||
error.asJSMap(), hasEntry("code", 1.0)) // Error code 1 means permission denied.
|
||||
}
|
||||
|
||||
@WithDevToolsAPI
|
||||
|
@ -212,7 +212,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
|
||||
assertThat("Type should match", type,
|
||||
equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION))
|
||||
equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION))
|
||||
callback.grant()
|
||||
}
|
||||
})
|
||||
|
@ -220,7 +220,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
val result = mainSession.waitForJS("Notification.requestPermission()")
|
||||
|
||||
assertThat("Permission should be granted",
|
||||
result as String, equalTo("granted"))
|
||||
result as String, equalTo("granted"))
|
||||
}
|
||||
|
||||
@WithDevToolsAPI
|
||||
|
@ -240,6 +240,62 @@ class PermissionDelegateTest : BaseSessionTest() {
|
|||
val result = mainSession.waitForJS("Notification.requestPermission()")
|
||||
|
||||
assertThat("Permission should not be granted",
|
||||
result as String, equalTo("default"))
|
||||
result as String, equalTo("default"))
|
||||
}
|
||||
|
||||
@WithDevToolsAPI
|
||||
@Test fun persistentStorage() {
|
||||
mainSession.loadTestPath(HELLO_HTML_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
// Persistent storage can be rejected
|
||||
mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onContentPermissionRequest(
|
||||
session: GeckoSession, uri: String?, type: Int,
|
||||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
callback.reject()
|
||||
}
|
||||
})
|
||||
|
||||
var success = mainSession.waitForJS("""window.navigator.storage.persist()""")
|
||||
|
||||
assertThat("Request should fail",
|
||||
success as Boolean, equalTo(false))
|
||||
|
||||
// Persistent storage can be granted
|
||||
mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
|
||||
// Ensure the content permission is asked first, before the Android permission.
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
override fun onContentPermissionRequest(
|
||||
session: GeckoSession, uri: String?, type: Int,
|
||||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
|
||||
assertThat("Type should match", type,
|
||||
equalTo(GeckoSession.PermissionDelegate.PERMISSION_PERSISTENT_STORAGE))
|
||||
callback.grant()
|
||||
}
|
||||
})
|
||||
|
||||
success = mainSession.waitForJS("""window.navigator.storage.persist()""")
|
||||
|
||||
assertThat("Request should succeed",
|
||||
success as Boolean,
|
||||
equalTo(true))
|
||||
|
||||
// after permission granted further requests will always return true, regardless of response
|
||||
mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onContentPermissionRequest(
|
||||
session: GeckoSession, uri: String?, type: Int,
|
||||
callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
callback.reject()
|
||||
}
|
||||
})
|
||||
|
||||
success = mainSession.waitForJS("""window.navigator.storage.persist()""")
|
||||
|
||||
assertThat("Request should succeed",
|
||||
success as Boolean, equalTo(true))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -757,6 +757,8 @@ public class GeckoSession implements Parcelable {
|
|||
type = PermissionDelegate.PERMISSION_GEOLOCATION;
|
||||
} else if ("desktop-notification".equals(typeString)) {
|
||||
type = PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION;
|
||||
} else if ("persistent-storage".equals(typeString)) {
|
||||
type = PermissionDelegate.PERMISSION_PERSISTENT_STORAGE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown permission request: " + typeString);
|
||||
}
|
||||
|
@ -4268,6 +4270,12 @@ public class GeckoSession implements Parcelable {
|
|||
*/
|
||||
int PERMISSION_DESKTOP_NOTIFICATION = 1;
|
||||
|
||||
/**
|
||||
* Permission for using the storage API.
|
||||
* See: https://developer.mozilla.org/en-US/docs/Web/API/Storage_API
|
||||
*/
|
||||
int PERMISSION_PERSISTENT_STORAGE = 2;
|
||||
|
||||
/**
|
||||
* Callback interface for notifying the result of a permission request.
|
||||
*/
|
||||
|
@ -4308,11 +4316,17 @@ public class GeckoSession implements Parcelable {
|
|||
/**
|
||||
* Request content permission.
|
||||
*
|
||||
* Note, that in the case of PERMISSION_PERSISTENT_STORAGE, once permission has been granted
|
||||
* for a site, it cannot be revoked. If the permission has previously been granted, it is
|
||||
* the responsibility of the consuming app to remember the permission and prevent the prompt
|
||||
* from being redisplayed to the user.
|
||||
*
|
||||
* @param session GeckoSession instance requesting the permission.
|
||||
* @param uri The URI of the content requesting the permission.
|
||||
* @param type The type of the requested permission; possible values are,
|
||||
* PERMISSION_GEOLOCATION
|
||||
* PERMISSION_DESKTOP_NOTIFICATION
|
||||
* PERMISSION_PERSISTENT_STORAGE
|
||||
* @param callback Callback interface.
|
||||
*/
|
||||
@UiThread
|
||||
|
|
|
@ -98,6 +98,10 @@ exclude: true
|
|||
[68.21]: ./GeckoRuntimeSettings.html#setDoubleTapZoomingEnabled-boolean-
|
||||
[68.22]: ./GeckoRuntimeSettings.html#setGlMsaaLevel-int-
|
||||
|
||||
- Added new constant for requesting external storage Android permissions, [`PERMISSION_PERSISTENT_STORAGE`][68.23]
|
||||
|
||||
[68.23]: ../GeckoSession.PermissionDelegate.html#PERMISSION_PERSISTENT_STORAGE
|
||||
|
||||
## v67
|
||||
- Added [`setAutomaticFontSizeAdjustment`][67.2] to
|
||||
[`GeckoRuntimeSettings`][67.3] for automatically adjusting font size settings
|
||||
|
@ -304,4 +308,4 @@ exclude: true
|
|||
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||
[65.25]: ../GeckoResult.html
|
||||
|
||||
[api-version]: 3fbf9d92418d270558cefad65cfe00599aeae263
|
||||
[api-version]: fb98a878c61a487c5e9af358682b54375957d88d
|
||||
|
|
|
@ -47,6 +47,7 @@ import java.io.BufferedReader;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
|
@ -73,6 +74,7 @@ public class GeckoViewActivity extends AppCompatActivity {
|
|||
private boolean mKillProcessOnDestroy;
|
||||
|
||||
private boolean mShowNotificationsRejected;
|
||||
private ArrayList<String> mAcceptedPersistentStorage = new ArrayList<String>();
|
||||
|
||||
private LocationView mLocationView;
|
||||
private String mCurrentUri;
|
||||
|
@ -621,6 +623,26 @@ public class GeckoViewActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
class ExamplePersistentStorageCallback implements GeckoSession.PermissionDelegate.Callback {
|
||||
private final GeckoSession.PermissionDelegate.Callback mCallback;
|
||||
private final String mUri;
|
||||
ExamplePersistentStorageCallback(final GeckoSession.PermissionDelegate.Callback callback, String uri) {
|
||||
mCallback = callback;
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject() {
|
||||
mCallback.reject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grant() {
|
||||
mAcceptedPersistentStorage.add(mUri);
|
||||
mCallback.grant();
|
||||
}
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
if (mCallback == null) {
|
||||
|
@ -666,6 +688,14 @@ public class GeckoViewActivity extends AppCompatActivity {
|
|||
}
|
||||
resId = R.string.request_notification;
|
||||
contentPermissionCallback = new ExampleNotificationCallback(callback);
|
||||
} else if (PERMISSION_PERSISTENT_STORAGE == type) {
|
||||
if (mAcceptedPersistentStorage.contains(uri)) {
|
||||
Log.w(LOGTAG, "Persistent Storage for "+ uri +" already granted by user.");
|
||||
callback.grant();
|
||||
return;
|
||||
}
|
||||
resId = R.string.request_storage;
|
||||
contentPermissionCallback = new ExamplePersistentStorageCallback(callback, uri);
|
||||
} else {
|
||||
Log.w(LOGTAG, "Unknown permission: " + type);
|
||||
callback.reject();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="clear_field">Clear</string>
|
||||
<string name="request_storage">Allow access to device storage for "%1$s"?</string>
|
||||
<string name="request_geolocation">Share location with "%1$s"?</string>
|
||||
<string name="request_notification">Allow notifications for "%1$s"?</string>
|
||||
<string name="request_video">Share video with "%1$s"</string>
|
||||
|
|
Загрузка…
Ссылка в новой задаче