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:
Emily Toop 2019-04-25 16:20:48 +00:00
Родитель 15e75bcd4d
Коммит 1cdb609e6a
8 изменённых файлов: 142 добавлений и 25 удалений

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

@ -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>