From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../main/java/org/mozilla/gecko/GeckoAppShell.java | 2 +- .../org/mozilla/gecko/GeckoBatteryManager.java | 19 +- .../org/mozilla/geckoview/ContentInputStream.java | 4 +- .../java/org/mozilla/geckoview/GeckoEditable.java | 4 +- .../java/org/mozilla/geckoview/MediaSession.java | 2 +- .../mozilla/geckoview/WebAuthnTokenManager.java | 203 +++++++++++++-------- .../java/org/mozilla/geckoview/WebExtension.java | 17 +- .../mozilla/geckoview/WebExtensionController.java | 50 +++++ .../org/mozilla/geckoview/doc-files/CHANGELOG.md | 9 +- 9 files changed, 223 insertions(+), 87 deletions(-) (limited to 'mobile/android/geckoview/src/main/java') diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java index bcd5762a92..c6aab05068 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java @@ -1231,7 +1231,7 @@ public class GeckoAppShell { @WrapForJNI(calledFrom = "gecko") private static double[] getCurrentBatteryInformation() { - return GeckoBatteryManager.getCurrentInformation(); + return GeckoBatteryManager.getCurrentInformation(getApplicationContext()); } /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */ diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java index 19f489b399..224362d066 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java @@ -54,10 +54,20 @@ public class GeckoBatteryManager extends BroadcastReceiver { mApplicationContext = context.getApplicationContext(); // registerReceiver will return null if registering fails. - if (mApplicationContext.registerReceiver(this, mFilter) == null) { + final Intent intent = mApplicationContext.registerReceiver(this, mFilter); + if (intent == null) { Log.e(LOGTAG, "Registering receiver failed"); + return; + } + + mIsEnabled = true; + final double current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + final double max = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + if (current == -1 || max == -1) { + Log.e(LOGTAG, "Failed to get battery level!"); + sLevel = kDefaultLevel; } else { - mIsEnabled = true; + sLevel = current / max; } } @@ -194,7 +204,10 @@ public class GeckoBatteryManager extends BroadcastReceiver { sNotificationsEnabled = false; } - public static double[] getCurrentInformation() { + public static double[] getCurrentInformation(final Context context) { + if (!getInstance().mIsEnabled) { + getInstance().start(context); + } return new double[] {getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime()}; } } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java index bc9eff98f0..8afa61b4b2 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java @@ -48,7 +48,7 @@ import org.mozilla.gecko.annotation.WrapForJNI; Log.e(LOGTAG, "Cannot open the uri: " + aUri + " (invalid header)"); close(); } - } catch (final IOException | SecurityException e) { + } catch (final Exception e) { Log.e(LOGTAG, "Cannot open the uri: " + aUri, e); close(); } @@ -146,7 +146,7 @@ import org.mozilla.gecko.annotation.WrapForJNI; Log.d(LOGTAG, "The uri is readable: " + uri); return true; } - } catch (final IOException | SecurityException e) { + } catch (final Exception e) { // A SecurityException could happen if the uri is no more valid or if // we're in an isolated process. Log.e(LOGTAG, "Cannot read the uri: " + uri, e); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java index d365f303c2..8750f344a8 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java @@ -1169,7 +1169,9 @@ import org.mozilla.geckoview.SessionTextInput.EditableListener.IMEState; } // Preserve enter and tab keys for the browser - if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_TAB) { + if (keyCode == KeyEvent.KEYCODE_ENTER + || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER + || keyCode == KeyEvent.KEYCODE_TAB) { return true; } // BaseKeyListener returns false even if it handled these keys for us, diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java index a662b3a82d..5b6923ac1e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java @@ -415,7 +415,7 @@ public class MediaSession { public String toString() { final StringBuilder builder = new StringBuilder("Metadata {"); builder - .append(", title=") + .append("title=") .append(title) .append(", artist=") .append(artist) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java index 6fae35f320..0c8457e7a4 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java @@ -6,7 +6,6 @@ package org.mozilla.geckoview; import android.app.PendingIntent; -import android.content.Intent; import android.net.Uri; import android.util.Base64; import android.util.Log; @@ -21,11 +20,13 @@ import com.google.android.gms.fido.fido2.api.common.AuthenticationExtensions; import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse; +import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorSelectionCriteria; import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions; import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions; import com.google.android.gms.fido.fido2.api.common.EC2Algorithm; import com.google.android.gms.fido.fido2.api.common.FidoAppIdExtension; +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters; @@ -128,16 +129,19 @@ import org.mozilla.gecko.util.GeckoBundle; public final byte[] keyHandle; public final byte[] attestationObject; public final String[] transports; + public final String authenticatorAttachment; public MakeCredentialResponse( final byte[] clientDataJson, final byte[] keyHandle, final byte[] attestationObject, - final String[] transports) { + final String[] transports, + final String authenticatorAttachment) { this.clientDataJson = clientDataJson; this.keyHandle = keyHandle; this.attestationObject = attestationObject; this.transports = transports; + this.authenticatorAttachment = authenticatorAttachment; } } @@ -290,42 +294,63 @@ import org.mozilla.gecko.util.GeckoBundle; .startActivityForResult(pendingIntent) .accept( intent -> { - final WebAuthnTokenManager.Exception error = parseErrorIntent(intent); + if (!intent.hasExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)) { + Log.w(LOGTAG, "Failed to get credential data in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + final byte[] rspData = + intent.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA); + final PublicKeyCredential publicKeyCredentialData = + PublicKeyCredential.deserializeFromBytes(rspData); + + final AuthenticatorResponse response = publicKeyCredentialData.getResponse(); + final WebAuthnTokenManager.Exception error = parseErrorResponse(response); if (error != null) { result.completeExceptionally(error); return; } - final byte[] rspData = intent.getByteArrayExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA); - if (rspData != null) { - final AuthenticatorAttestationResponse responseData = - AuthenticatorAttestationResponse.deserializeFromBytes(rspData); - - Log.d( - LOGTAG, - "key handle: " - + Base64.encodeToString(responseData.getKeyHandle(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "clientDataJSON: " - + Base64.encodeToString( - responseData.getClientDataJSON(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "attestation Object: " - + Base64.encodeToString( - responseData.getAttestationObject(), Base64.DEFAULT)); - - Log.d( - LOGTAG, "transports: " + String.join(", ", responseData.getTransports())); - - result.complete( - new WebAuthnTokenManager.MakeCredentialResponse( - responseData.getClientDataJSON(), - responseData.getKeyHandle(), - responseData.getAttestationObject(), - responseData.getTransports())); + if (!(response instanceof AuthenticatorAttestationResponse)) { + Log.w(LOGTAG, "Failed to get attestation response in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; } + + final AuthenticatorAttestationResponse responseData = + (AuthenticatorAttestationResponse) response; + + Log.d( + LOGTAG, + "key handle: " + + Base64.encodeToString( + publicKeyCredentialData.getRawId(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "clientDataJSON: " + + Base64.encodeToString( + responseData.getClientDataJSON(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "attestation Object: " + + Base64.encodeToString( + responseData.getAttestationObject(), Base64.DEFAULT)); + + Log.d(LOGTAG, "transports: " + String.join(", ", responseData.getTransports())); + Log.d( + LOGTAG, + "authenticatorAttachment :" + + publicKeyCredentialData.getAuthenticatorAttachment()); + + result.complete( + new WebAuthnTokenManager.MakeCredentialResponse( + responseData.getClientDataJSON(), + publicKeyCredentialData.getRawId(), + responseData.getAttestationObject(), + responseData.getTransports(), + publicKeyCredentialData.getAuthenticatorAttachment())); }, e -> { Log.w(LOGTAG, "Failed to launch activity: ", e); @@ -390,29 +415,31 @@ import org.mozilla.gecko.util.GeckoBundle; public final byte[] authData; public final byte[] signature; public final byte[] userHandle; + public final String authenticatorAttachment; public GetAssertionResponse( final byte[] clientDataJson, final byte[] keyHandle, final byte[] authData, final byte[] signature, - final byte[] userHandle) { + final byte[] userHandle, + final String authenticatorAttachment) { this.clientDataJson = clientDataJson; this.keyHandle = keyHandle; this.authData = authData; this.signature = signature; this.userHandle = userHandle; + this.authenticatorAttachment = authenticatorAttachment; } } - private static WebAuthnTokenManager.Exception parseErrorIntent(final Intent intent) { - if (!intent.hasExtra(Fido.FIDO2_KEY_ERROR_EXTRA)) { + private static WebAuthnTokenManager.Exception parseErrorResponse( + final AuthenticatorResponse response) { + if (!(response instanceof AuthenticatorErrorResponse)) { return null; } - final byte[] errData = intent.getByteArrayExtra(Fido.FIDO2_KEY_ERROR_EXTRA); - final AuthenticatorErrorResponse responseData = - AuthenticatorErrorResponse.deserializeFromBytes(errData); + final AuthenticatorErrorResponse responseData = (AuthenticatorErrorResponse) response; Log.e(LOGTAG, "errorCode.name: " + responseData.getErrorCode()); Log.e(LOGTAG, "errorMessage: " + responseData.getErrorMessage()); @@ -485,51 +512,73 @@ import org.mozilla.gecko.util.GeckoBundle; .startActivityForResult(pendingIntent) .accept( intent -> { - final WebAuthnTokenManager.Exception error = parseErrorIntent(intent); + if (!intent.hasExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)) { + Log.w(LOGTAG, "Failed to get credential data in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + + final byte[] rspData = + intent.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA); + final PublicKeyCredential publicKeyCredentialData = + PublicKeyCredential.deserializeFromBytes(rspData); + final AuthenticatorResponse response = publicKeyCredentialData.getResponse(); + + final WebAuthnTokenManager.Exception error = parseErrorResponse(response); if (error != null) { result.completeExceptionally(error); return; } - if (intent.hasExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA)) { - final byte[] rspData = - intent.getByteArrayExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA); - final AuthenticatorAssertionResponse responseData = - AuthenticatorAssertionResponse.deserializeFromBytes(rspData); - - Log.d( - LOGTAG, - "key handle: " - + Base64.encodeToString(responseData.getKeyHandle(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "clientDataJSON: " - + Base64.encodeToString( - responseData.getClientDataJSON(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "auth data: " - + Base64.encodeToString( - responseData.getAuthenticatorData(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "signature: " - + Base64.encodeToString(responseData.getSignature(), Base64.DEFAULT)); - - // Nullable field - byte[] userHandle = responseData.getUserHandle(); - if (userHandle == null) { - userHandle = new byte[0]; - } - - result.complete( - new WebAuthnTokenManager.GetAssertionResponse( - responseData.getClientDataJSON(), - responseData.getKeyHandle(), - responseData.getAuthenticatorData(), - responseData.getSignature(), - userHandle)); + if (!(response instanceof AuthenticatorAssertionResponse)) { + Log.w(LOGTAG, "Failed to get assertion response in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + + final AuthenticatorAssertionResponse responseData = + (AuthenticatorAssertionResponse) publicKeyCredentialData.getResponse(); + + Log.d( + LOGTAG, + "key handle: " + + Base64.encodeToString( + publicKeyCredentialData.getRawId(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "clientDataJSON: " + + Base64.encodeToString( + responseData.getClientDataJSON(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "auth data: " + + Base64.encodeToString( + responseData.getAuthenticatorData(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "signature: " + + Base64.encodeToString(responseData.getSignature(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "authenticatorAttachment :" + + publicKeyCredentialData.getAuthenticatorAttachment()); + + // Nullable field + byte[] userHandle = responseData.getUserHandle(); + if (userHandle == null) { + userHandle = new byte[0]; } + + result.complete( + new WebAuthnTokenManager.GetAssertionResponse( + responseData.getClientDataJSON(), + publicKeyCredentialData.getRawId(), + responseData.getAuthenticatorData(), + responseData.getSignature(), + userHandle, + publicKeyCredentialData.getAuthenticatorAttachment())); }, e -> { Log.w(LOGTAG, "Failed to get FIDO intent", e); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java index 1caa5508ed..bf5d431cf1 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java @@ -1811,14 +1811,27 @@ public class WebExtension { public final @NonNull Image icon; /** - * API permissions requested or granted to this extension. + * List of permissions to be prompted to the users. * *

Permission identifiers match entries in the manifest, see * API permissions . + * + * @deprecated Use {@link MetaData#promptPermissions} instead. */ + @Deprecated + @DeprecationSchedule(id = "web-extension-permission", version = 131) public final @NonNull String[] permissions; + /** + * List of permissions to be prompted to the users. + * + *

Permission identifiers match entries in the manifest, see + * API permissions . + */ + public final @NonNull String[] promptPermissions; + /** * API optional @@ -2043,6 +2056,7 @@ public class WebExtension { protected MetaData() { icon = null; permissions = null; + promptPermissions = null; optionalPermissions = null; grantedOptionalPermissions = null; grantedOptionalOrigins = null; @@ -2077,6 +2091,7 @@ public class WebExtension { /* package */ MetaData(final GeckoBundle bundle) { // We only expose permissions that the embedder should prompt for permissions = bundle.getStringArray("promptPermissions"); + promptPermissions = bundle.getStringArray("promptPermissions"); optionalPermissions = bundle.getStringArray("optionalPermissions"); grantedOptionalPermissions = bundle.getStringArray("grantedOptionalPermissions"); optionalOrigins = bundle.getStringArray("optionalOrigins"); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java index 7e936f84f7..07e848b079 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java @@ -708,6 +708,56 @@ public class WebExtensionController { .map(this::registerWebExtension); } + /** + * Add the provided permissions to the {@link WebExtension} with the given id. + * + * @param extensionId the id of {@link WebExtension} instance to modify. + * @param permissions the permissions to add, pass an empty array to not update. + * @param origins the origins to add, pass an empty array to not update. + * @return the updated {@link WebExtension} instance. + */ + @NonNull + @AnyThread + public GeckoResult addOptionalPermissions( + final @NonNull String extensionId, + @NonNull final String[] permissions, + @NonNull final String[] origins) { + final GeckoBundle bundle = new GeckoBundle(3); + bundle.putString("extensionId", extensionId); + bundle.putStringArray("permissions", permissions); + bundle.putStringArray("origins", origins); + + return EventDispatcher.getInstance() + .queryBundle("GeckoView:WebExtension:AddOptionalPermissions", bundle) + .map(ext -> WebExtension.fromBundle(mDelegateControllerProvider, ext)) + .map(this::registerWebExtension); + } + + /** + * Remove the provided permissions from the {@link WebExtension} with the given id. + * + * @param extensionId the id of {@link WebExtension} instance to modify. + * @param permissions the permissions to remove, pass an empty array to not update. + * @param origins the origins to remove, pass an empty array to not update. + * @return the updated {@link WebExtension} instance. + */ + @NonNull + @AnyThread + public GeckoResult removeOptionalPermissions( + final @NonNull String extensionId, + @NonNull final String[] permissions, + @NonNull final String[] origins) { + final GeckoBundle bundle = new GeckoBundle(3); + bundle.putString("extensionId", extensionId); + bundle.putStringArray("permissions", permissions); + bundle.putStringArray("origins", origins); + + return EventDispatcher.getInstance() + .queryBundle("GeckoView:WebExtension:RemoveOptionalPermissions", bundle) + .map(ext -> WebExtension.fromBundle(mDelegateControllerProvider, ext)) + .map(this::registerWebExtension); + } + /** * Install a built-in extension. * diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md index 5776cf5afc..e2df6df21b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -23,6 +23,9 @@ while a user gesture was active (e.g., a tap). - ⚠️ Deprecated [`RuntimeTelemetry`][125.5], [`GeckoRuntimeSettings.getTelemetryDelegate`][125.6] and [`GeckoRuntimeSettings.telemetryDelegate`][125.7], to be removed in v127. ([bug 1877836]({{bugzilla}}1877836)) - Added [`WebExtension.MetaData.grantedOptionalPermissions`][125.8] and [`WebExtension.MetaData.grantedOptionalOrigins`][125.9] which expose the granted optional and origin optional permissions of an extension ([bug 1879543]({{bugzilla}}1879543)). +- Added [`WebExtension.MetaData.promptPermissions`][125.10] which exposes a list of permissions which needs to be prompted to users ([bug 1879547]({{bugzilla}}1879547)). +- ⚠️ Deprecate the [`WebExtension.MetaData.permissions`][125.11] API to be removed in v131. Please use `WebExtension.MetaData.promptPermissions`][125.10] instead. +- Added [`WebExtensionController.addOptionalPermissions`][125.12] and [`WebExtensionController.removeOptionalPermissions`][125.13], which allow to add and remove optional permissions/origins of an extension ([bug 1796176]({{bugzilla}}1796176)). [125.1]: {{javadoc_uri}}/GeckoSession.NavigationDelegate#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List) [125.2]: {{javadoc_uri}}/GeckoSession.NavigationDelegate#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List,boolean) @@ -33,6 +36,10 @@ while a user gesture was active (e.g., a tap). [125.7]: {{javadoc_uri}}/GeckoRuntimeSettings.html#telemetryDelegate [125.8]: {{javadoc_uri}}/WebExtension.MetaData.html#grantedOptionalPermissions [125.9]: {{javadoc_uri}}/WebExtension.MetaData.html#grantedOptionalOrigins +[125.10]: {{javadoc_uri}}/WebExtension.MetaData.html#promptPermissions +[125.11]: {{javadoc_uri}}/WebExtension.MetaData.html#permissions +[125.12]: {{javadoc_uri}}/WebExtensionController.html#addOptionalPermissions +[125.13]: {{javadoc_uri}}/WebExtensionController.html#removeOptionalPermissions ## v124 @@ -1540,4 +1547,4 @@ to allow adding gecko profiler markers. [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String) [65.25]: {{javadoc_uri}}/GeckoResult.html -[api-version]: fc9fd590333bebf38058b7abddbb7a860cd6e4de +[api-version]: 2c319e9f18adb4178ce09d71088a173b56d1a694 -- cgit v1.2.3