From 713fd4f612c95797e9fab7962b8d87d48de56f0b Mon Sep 17 00:00:00 2001 From: Olivia Hall Date: Fri, 11 Aug 2023 15:30:10 +0000 Subject: [PATCH] Bug 1845824 - ExperimentDelegate GeckoSessionHandler r=geckoview-reviewers,tthibaud,jonalmeida,owlish This patch adds a an ExperimentDelegate GeckoSessionHandler, a JS Experiment Delegate Parent/Child, and a way to get/set the Experiment Delegate on the session. Differential Revision: https://phabricator.services.mozilla.com/D184841 --- .../GeckoViewExperimentDelegateParent.sys.mjs | 69 +++++++++ mobile/android/actors/moz.build | 1 + .../actors/tests/mochitests/mochitest.ini | 5 +- .../test_geckoview_experiment_delegate.html | 108 ++++++++++++++ mobile/android/chrome/geckoview/geckoview.js | 14 ++ mobile/android/geckoview/api.txt | 7 +- .../geckoview/test/ExperimentDelegateTest.kt | 1 + .../geckoview/test/util/RuntimeCreator.java | 8 +- .../mozilla/geckoview/ExperimentDelegate.java | 33 +++- .../org/mozilla/geckoview/GeckoSession.java | 141 +++++++++++++++++- .../mozilla/geckoview/doc-files/CHANGELOG.md | 6 +- .../test_runner/TestRunnerActivity.java | 64 +++++++- 12 files changed, 441 insertions(+), 16 deletions(-) create mode 100644 mobile/android/actors/GeckoViewExperimentDelegateParent.sys.mjs create mode 100644 mobile/android/actors/tests/mochitests/test_geckoview_experiment_delegate.html diff --git a/mobile/android/actors/GeckoViewExperimentDelegateParent.sys.mjs b/mobile/android/actors/GeckoViewExperimentDelegateParent.sys.mjs new file mode 100644 index 000000000000..5e83ef906379 --- /dev/null +++ b/mobile/android/actors/GeckoViewExperimentDelegateParent.sys.mjs @@ -0,0 +1,69 @@ +/* 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/. */ + +import { GeckoViewActorParent } from "resource://gre/modules/GeckoViewActorParent.sys.mjs"; + +export class GeckoViewExperimentDelegateParent extends GeckoViewActorParent { + constructor() { + super(); + } + + /** + * Gets experiment information on a given feature. + * + * @param feature the experiment item to retrieve information on + * @returns a promise of success with a JSON message or failure + */ + async getExperimentFeature(feature) { + return this.eventDispatcher.sendRequestForResult({ + type: "GeckoView:GetExperimentFeature", + feature, + }); + } + + /** + * Records an exposure event, that the experiment area was encountered, on a given feature. + * + * @param feature the experiment item to record an exposure event of + * @returns a promise of success or failure + */ + async recordExposure(feature) { + return this.eventDispatcher.sendRequestForResult({ + type: "GeckoView:RecordExposure", + feature, + }); + } + + /** + * Records an exposure event on a specific experiment feature and element. + * + * Note: Use recordExposure, if the slug is not known. + * + * @param feature the experiment item to record an exposure event of + * @param slug a specific experiment element + * @returns a promise of success or failure + */ + async recordExperimentExposure(feature, slug) { + return this.eventDispatcher.sendRequestForResult({ + type: "GeckoView:RecordExperimentExposure", + feature, + slug, + }); + } + + /** + * For recording malformed configuration. + * + * @param feature the experiment item to record an exposure event of + * @param part malformed information to send + * @returns a promise of success or failure + */ + async recordExperimentMalformedConfig(feature, part) { + return this.eventDispatcher.sendRequestForResult({ + type: "GeckoView:RecordMalformedConfig", + feature, + part, + }); + } +} diff --git a/mobile/android/actors/moz.build b/mobile/android/actors/moz.build index 48dcc26b096f..89579c1c8ee7 100644 --- a/mobile/android/actors/moz.build +++ b/mobile/android/actors/moz.build @@ -14,6 +14,7 @@ FINAL_TARGET_FILES.actors += [ "GeckoViewClipboardPermissionParent.sys.mjs", "GeckoViewContentChild.sys.mjs", "GeckoViewContentParent.sys.mjs", + "GeckoViewExperimentDelegateParent.sys.mjs", "GeckoViewFormValidationChild.sys.mjs", "GeckoViewPermissionChild.sys.mjs", "GeckoViewPermissionParent.sys.mjs", diff --git a/mobile/android/actors/tests/mochitests/mochitest.ini b/mobile/android/actors/tests/mochitests/mochitest.ini index 31fe69cb03c9..498f50fcff1b 100644 --- a/mobile/android/actors/tests/mochitests/mochitest.ini +++ b/mobile/android/actors/tests/mochitests/mochitest.ini @@ -5,4 +5,7 @@ prefs = dom.enable_window_print=true skip-if = os != 'android' -[test_geckoview_actor_telemetry.html] \ No newline at end of file +[test_geckoview_actor_telemetry.html] +skip-if = + os != 'android' +[test_geckoview_experiment_delegate.html] \ No newline at end of file diff --git a/mobile/android/actors/tests/mochitests/test_geckoview_experiment_delegate.html b/mobile/android/actors/tests/mochitests/test_geckoview_experiment_delegate.html new file mode 100644 index 000000000000..c6425e2983d5 --- /dev/null +++ b/mobile/android/actors/tests/mochitests/test_geckoview_experiment_delegate.html @@ -0,0 +1,108 @@ + + + + + + Test Experiment Delegate + + + + + + + + diff --git a/mobile/android/chrome/geckoview/geckoview.js b/mobile/android/chrome/geckoview/geckoview.js index 85fdb46ab00c..1ea1ab2ea0a0 100644 --- a/mobile/android/chrome/geckoview/geckoview.js +++ b/mobile/android/chrome/geckoview/geckoview.js @@ -842,6 +842,20 @@ function startup() { }, }, }, + { + name: "GeckoViewExperimentDelegate", + onInit: { + actors: { + GeckoViewExperimentDelegate: { + parent: { + esModuleURI: + "resource:///actors/GeckoViewExperimentDelegateParent.sys.mjs", + }, + allFrames: true, + }, + }, + }, + }, ]); if (!Services.appinfo.sessionHistoryInParent) { diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 003091651fd7..e352721fc9f9 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -49,6 +49,7 @@ import java.io.InputStream; import java.lang.Boolean; import java.lang.CharSequence; import java.lang.Class; +import java.lang.Deprecated; import java.lang.Double; import java.lang.Exception; import java.lang.Float; @@ -83,6 +84,7 @@ import org.mozilla.geckoview.CompositorController; import org.mozilla.geckoview.ContentBlocking; import org.mozilla.geckoview.ContentBlockingController; import org.mozilla.geckoview.CrashHandler; +import org.mozilla.geckoview.DeprecationSchedule; import org.mozilla.geckoview.ExperimentDelegate; import org.mozilla.geckoview.GeckoDisplay; import org.mozilla.geckoview.GeckoResult; @@ -660,6 +662,7 @@ package org.mozilla.geckoview { public static class ExperimentDelegate.ExperimentException extends Exception { ctor public ExperimentException(int); + field public static final int ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED = -4; field public static final int ERROR_EXPERIMENT_SLUG_NOT_FOUND = -3; field public static final int ERROR_FEATURE_NOT_FOUND = -2; field public static final int ERROR_UNKNOWN = -1; @@ -942,6 +945,7 @@ package org.mozilla.geckoview { method @AnyThread @Nullable public ContentBlocking.Delegate getContentBlockingDelegate(); method @Nullable @UiThread public GeckoSession.ContentDelegate getContentDelegate(); method @AnyThread @NonNull public static String getDefaultUserAgent(); + method @AnyThread @Nullable public ExperimentDelegate getExperimentDelegate(); method @AnyThread @NonNull public SessionFinder getFinder(); method @AnyThread @Nullable public GeckoSession.HistoryDelegate getHistoryDelegate(); method @AnyThread @Nullable public GeckoSession.MediaDelegate getMediaDelegate(); @@ -987,6 +991,7 @@ package org.mozilla.geckoview { method @UiThread public void setAutofillDelegate(@Nullable Autofill.Delegate); method @AnyThread public void setContentBlockingDelegate(@Nullable ContentBlocking.Delegate); method @UiThread public void setContentDelegate(@Nullable GeckoSession.ContentDelegate); + method @AnyThread public void setExperimentDelegate(@Nullable ExperimentDelegate); method @AnyThread public void setFocused(boolean); method @AnyThread public void setHistoryDelegate(@Nullable GeckoSession.HistoryDelegate); method @AnyThread public void setMediaDelegate(@Nullable GeckoSession.MediaDelegate); @@ -1038,7 +1043,7 @@ package org.mozilla.geckoview { method @UiThread default public void onFirstContentfulPaint(@NonNull GeckoSession); method @UiThread default public void onFocusRequest(@NonNull GeckoSession); method @UiThread default public void onFullScreen(@NonNull GeckoSession, boolean); - method @AnyThread @Nullable default public JSONObject onGetNimbusFeature(@NonNull GeckoSession, @NonNull String); + method @AnyThread @Deprecated @DeprecationSchedule(version=122,id="session-nimbus") @Nullable default public JSONObject onGetNimbusFeature(@NonNull GeckoSession, @NonNull String); method @UiThread default public void onKill(@NonNull GeckoSession); method @UiThread default public void onMetaViewportFitChange(@NonNull GeckoSession, @NonNull String); method @UiThread default public void onPaintStatusReset(@NonNull GeckoSession); diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ExperimentDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ExperimentDelegateTest.kt index 9b630cf0ca7e..ceb90ffe0782 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ExperimentDelegateTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ExperimentDelegateTest.kt @@ -29,6 +29,7 @@ class ExperimentDelegateTest : BaseSessionTest() { mainSession.loadTestPath(TRACEMONKEY_PDF_PATH) sessionRule.waitUntilCalled(object : ContentDelegate { + @Deprecated("Changing to Experiment Delegate in Bug 1840658.") override fun onGetNimbusFeature(session: GeckoSession, featureId: String): JSONObject? { assertThat( "Feature id should match", diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java index 261da57e625c..7eda3604598b 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java @@ -80,7 +80,7 @@ public class RuntimeCreator { if (delegate != null) { return delegate.onGetExperimentFeature(feature); } - return null; + return ExperimentDelegate.super.onGetExperimentFeature(feature); } @Override @@ -88,7 +88,7 @@ public class RuntimeCreator { if (delegate != null) { return delegate.onRecordExposureEvent(feature); } - return null; + return ExperimentDelegate.super.onRecordExposureEvent(feature); } @Override @@ -97,7 +97,7 @@ public class RuntimeCreator { if (delegate != null) { return delegate.onRecordExperimentExposureEvent(feature, slug); } - return null; + return ExperimentDelegate.super.onRecordExperimentExposureEvent(feature, slug); } @Override @@ -106,7 +106,7 @@ public class RuntimeCreator { if (delegate != null) { return delegate.onRecordMalformedConfigurationEvent(feature, part); } - return null; + return ExperimentDelegate.super.onRecordMalformedConfigurationEvent(feature, part); } } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ExperimentDelegate.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ExperimentDelegate.java index 0fce22fae0c5..e014875e053a 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ExperimentDelegate.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ExperimentDelegate.java @@ -6,6 +6,8 @@ package org.mozilla.geckoview; +import static org.mozilla.geckoview.ExperimentDelegate.ExperimentException.ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED; + import androidx.annotation.AnyThread; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -42,7 +44,10 @@ public interface ExperimentDelegate { */ @AnyThread default @NonNull GeckoResult onGetExperimentFeature(@NonNull String feature) { - return null; + final GeckoResult result = new GeckoResult<>(); + result.completeExceptionally( + new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED)); + return result; } /** @@ -64,7 +69,10 @@ public interface ExperimentDelegate { */ @AnyThread default @NonNull GeckoResult onRecordExposureEvent(@NonNull String feature) { - return null; + final GeckoResult result = new GeckoResult<>(); + result.completeExceptionally( + new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED)); + return result; } /** @@ -86,7 +94,10 @@ public interface ExperimentDelegate { @AnyThread default @NonNull GeckoResult onRecordExperimentExposureEvent( @NonNull String feature, @NonNull String slug) { - return null; + final GeckoResult result = new GeckoResult<>(); + result.completeExceptionally( + new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED)); + return result; } /** @@ -102,7 +113,10 @@ public interface ExperimentDelegate { @AnyThread default @NonNull GeckoResult onRecordMalformedConfigurationEvent( @NonNull String feature, @NonNull String part) { - return null; + final GeckoResult result = new GeckoResult<>(); + result.completeExceptionally( + new ExperimentException(ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED)); + return result; } /** @@ -129,9 +143,18 @@ public interface ExperimentDelegate { /** The experiment slug was not available. */ public static final int ERROR_EXPERIMENT_SLUG_NOT_FOUND = -3; + /** The experiment delegate is not implemented. */ + public static final int ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED = -4; + /** Experiment exception error codes. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {ERROR_UNKNOWN, ERROR_FEATURE_NOT_FOUND, ERROR_EXPERIMENT_SLUG_NOT_FOUND}) + @IntDef( + value = { + ERROR_UNKNOWN, + ERROR_FEATURE_NOT_FOUND, + ERROR_EXPERIMENT_SLUG_NOT_FOUND, + ERROR_EXPERIMENT_DELEGATE_NOT_IMPLEMENTED + }) public @interface Codes {} /** One of {@link Codes} that provides more information about this exception. */ diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 2ac70116e7bb..925f583b66e9 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -839,6 +839,95 @@ public class GeckoSession { } }; + private final GeckoSessionHandler mExperimentHandler = + new GeckoSessionHandler( + "GeckoViewExperiment", + this, + new String[] { + "GeckoView:GetExperimentFeature", + "GeckoView:RecordExposure", + "GeckoView:RecordExperimentExposure", + "GeckoView:RecordMalformedConfig" + }) { + @Override + public void handleMessage( + final ExperimentDelegate delegate, + final String event, + final GeckoBundle message, + final EventCallback callback) { + + if (delegate == null) { + if (callback != null) { + callback.sendError("No experiment delegate registered."); + } + Log.w(LOGTAG, "No experiment delegate registered."); + return; + } + final String feature = message.getString("feature", ""); + if ("GeckoView:GetExperimentFeature".equals(event) && callback != null) { + final GeckoResult result = delegate.onGetExperimentFeature(feature); + result + .accept( + json -> { + try { + callback.sendSuccess(GeckoBundle.fromJSONObject(json)); + } catch (final JSONException e) { + callback.sendError("An error occured when serializing the feature data."); + } + }) + .exceptionally( + e -> { + callback.sendError("An error occurred while retrieving feature data."); + return null; + }); + + } else if ("GeckoView:RecordExposure".equals(event) && callback != null) { + final GeckoResult result = delegate.onRecordExposureEvent(feature); + result + .accept( + a -> { + callback.sendSuccess(true); + }) + .exceptionally( + e -> { + callback.sendError("An error occurred while recording feature."); + return null; + }); + + } else if ("GeckoView:RecordExperimentExposure".equals(event) && callback != null) { + final String slug = message.getString("slug", ""); + final GeckoResult result = + delegate.onRecordExperimentExposureEvent(feature, slug); + result + .accept( + a -> { + callback.sendSuccess(true); + }) + .exceptionally( + e -> { + callback.sendError("An error occurred while recording experiment feature."); + return null; + }); + + } else if ("GeckoView:RecordMalformedConfig".equals(event) && callback != null) { + final String part = message.getString("part", ""); + final GeckoResult result = + delegate.onRecordMalformedConfigurationEvent(feature, part); + result + .accept( + a -> { + callback.sendSuccess(true); + }) + .exceptionally( + e -> { + callback.sendError( + "An error occurred while recording malformed feature config."); + return null; + }); + } + } + }; + private final GeckoSessionHandler mProcessHangHandler = new GeckoSessionHandler( "GeckoViewProcessHangMonitor", this, new String[] {"GeckoView:HangReport"}) { @@ -1146,7 +1235,8 @@ public class GeckoSession { mScrollHandler, mSelectionActionDelegate, mContentBlockingHandler, - mMediaSessionHandler + mMediaSessionHandler, + mExperimentHandler }; private static class PermissionCallback @@ -1650,6 +1740,7 @@ public class GeckoSession { mId = id; mWindow = new Window(runtime, this, mNativeQueue); mWebExtensionController.setRuntime(runtime); + mExperimentHandler.setDelegate(getRuntimeExperimentDelegate(), this); onWindowChanged(WINDOW_OPEN, /* inProgress */ true); @@ -3908,13 +3999,18 @@ public class GeckoSession { default void onCookieBannerHandled(@NonNull final GeckoSession session) {} /** - * This method is called when GeckoView is requesting a specific Nimbus feature in using message - * `GeckoView:GetNimbusFeature`. + * This method is scheduled for deprecation, see Bug 1846074 for details. Please switch to the + * [ExperimentDelegate.onGetExperimentFeature] for the same functionality. + * + *

This method is called when GeckoView is requesting a specific Nimbus feature in using + * message `GeckoView:GetNimbusFeature`. * * @param session GeckoSession that initiated the callback. * @param featureId Nimbus feature id of the collected data. * @return A {@link JSONObject} with the feature. */ + @Deprecated + @DeprecationSchedule(version = 122, id = "session-nimbus") @AnyThread default @Nullable JSONObject onGetNimbusFeature( @NonNull final GeckoSession session, @NonNull final String featureId) { @@ -7679,6 +7775,45 @@ public class GeckoSession { mPrintHandler.setDelegate(delegate, this); } + /** + * Gets the experiment delegate for this session. + * + * @return The current {@link ExperimentDelegate} for this session, if any. + */ + @AnyThread + public @Nullable ExperimentDelegate getExperimentDelegate() { + return mExperimentHandler.getDelegate(); + } + + /** + * Gets the experiment delegate from the runtime. + * + * @return The current {@link ExperimentDelegate} for the runtime or null. + */ + @AnyThread + private @Nullable ExperimentDelegate getRuntimeExperimentDelegate() { + final GeckoRuntime runtime = this.getRuntime(); + if (runtime != null) { + final GeckoRuntimeSettings runtimeSettings = runtime.getSettings(); + if (runtimeSettings != null) { + return runtimeSettings.getExperimentDelegate(); + } + } + Log.w(LOGTAG, "Could not retrieve experiment delegate from runtime."); + return null; + } + + /** + * Sets the experiment delegate for this session. Default is set to the runtime experiment + * delegate. + * + * @param delegate An instance of {@link ExperimentDelegate}. + */ + @AnyThread + public void setExperimentDelegate(final @Nullable ExperimentDelegate delegate) { + mExperimentHandler.setDelegate(delegate, this); + } + /** Thrown when failure occurs when printing from a website. */ @WrapForJNI public static class GeckoPrintException extends Exception { 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 4e05da5242af..eb1b6e77cb79 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 @@ -22,6 +22,8 @@ exclude: true - Added [`ERROR_INCOMPATIBLE`][118.6] to `WebExtension.InstallException.ErrorCodes`. ([bug 1845749]({{bugzilla}}1845749)) - Added [`GeckoRuntimeSettings.Builder.extensionsWebAPIEnabled`][118.7]. ([bug 1847173]({{bugzilla}}1847173)) - Changed [`GeckoSession.AccountSelectorPrompt`][118.8]: added the Provider to which the Account belongs ([bug 1847059]({{bugzilla}}1847059)) +- Added [`getExperimentDelegate`][118.9] and [`setExperimentDelegate`][118.10] to the GeckoSession allow GeckoView to get and set the experiment delegate for the session. Default is to use the runtime delegate. +- ⚠️ Deprecated [`onGetNimbusFeature`][115.5] by 122, please use `ExperimentDelegate.onGetExperimentFeature` instead. [118.1]: {{javadoc_uri}}/ExperimentDelegate.html [118.2]: {{javadoc_uri}}/WebExtension.InstallException.ErrorCodes.html#ERROR_BLOCKLISTED @@ -31,6 +33,8 @@ exclude: true [118.6]: {{javadoc_uri}}/WebExtension.InstallException.ErrorCodes.html#ERROR_INCOMPATIBLE [118.7]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#extensionsWebAPIEnabled(boolean) [118.8]: {{javadoc_uri}}/GeckoSession.html#AccountSelectorPrompt +[118.9]: {{javadoc_uri}}/GeckoSession.html#getExperimentDelegate() +[118.10]: {{javadoc_uri}}/GeckoSession.html#setExperimentDelegate(org.mozilla.geckoview.ExperimentDelegate) ## v116 - Added [`GeckoSession.didPrintPageContent`][116.1] to included extra print status for a standard print and new `GeckoPrintException.ERROR_NO_PRINT_DELEGATE` @@ -1411,4 +1415,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]: d735eedb188b91b0495d3c2667807f29f6915263 +[api-version]: 4a2f7cc3f89c0baa015d23f9d17fbea7e69e4aff diff --git a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java index f95efeab1b45..3930ab545c7d 100644 --- a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java +++ b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java @@ -3,6 +3,10 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ package org.mozilla.geckoview.test_runner; +import static org.mozilla.geckoview.ExperimentDelegate.ExperimentException.ERROR_EXPERIMENT_SLUG_NOT_FOUND; +import static org.mozilla.geckoview.ExperimentDelegate.ExperimentException.ERROR_FEATURE_NOT_FOUND; +import static org.mozilla.geckoview.ExperimentDelegate.ExperimentException.ERROR_UNKNOWN; + import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -16,8 +20,11 @@ import java.util.ArrayDeque; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.json.JSONException; +import org.json.JSONObject; import org.mozilla.geckoview.AllowOrDeny; import org.mozilla.geckoview.ContentBlocking; +import org.mozilla.geckoview.ExperimentDelegate; import org.mozilla.geckoview.GeckoDisplay; import org.mozilla.geckoview.GeckoResult; import org.mozilla.geckoview.GeckoRuntime; @@ -415,7 +422,8 @@ public class TestRunnerActivity extends Activity { .arguments(new String[] {"-purgecaches"}) .displayDpiOverride(160) .displayDensityOverride(1.0f) - .remoteDebuggingEnabled(true); + .remoteDebuggingEnabled(true) + .experimentDelegate(new TestRunnerExperimentDelegate()); final Bundle extras = intent.getExtras(); if (extras != null) { @@ -650,4 +658,58 @@ public class TestRunnerActivity extends Activity { public GeckoSession getGeckoSession() { return mSession; } + + class TestRunnerExperimentDelegate implements ExperimentDelegate { + @Override + public GeckoResult onGetExperimentFeature(@NonNull String feature) { + GeckoResult result = new GeckoResult<>(); + if (feature.equals("test")) { + try { + result.complete(new JSONObject().put("item-one", true).put("item-two", 5)); + } catch (JSONException e) { + result.completeExceptionally(new ExperimentException(ERROR_UNKNOWN)); + } + } else { + result.completeExceptionally(new ExperimentException(ERROR_FEATURE_NOT_FOUND)); + } + return result; + } + + @Override + public GeckoResult onRecordExposureEvent(@NonNull String feature) { + GeckoResult result = new GeckoResult<>(); + if (feature.equals("test")) { + result.complete(null); + } else { + result.completeExceptionally(new ExperimentException(ERROR_FEATURE_NOT_FOUND)); + } + return result; + } + + @Override + public GeckoResult onRecordExperimentExposureEvent( + @NonNull String feature, @NonNull String slug) { + GeckoResult result = new GeckoResult<>(); + if (feature.equals("test") && slug.equals("test")) { + result.complete(null); + } else if (!slug.equals("test") && feature.equals("test")) { + result.completeExceptionally(new ExperimentException(ERROR_EXPERIMENT_SLUG_NOT_FOUND)); + } else { + result.completeExceptionally(new ExperimentException(ERROR_FEATURE_NOT_FOUND)); + } + return result; + } + + @Override + public GeckoResult onRecordMalformedConfigurationEvent( + @NonNull String feature, @NonNull String part) { + GeckoResult result = new GeckoResult<>(); + if (feature.equals("test")) { + result.complete(null); + } else { + result.completeExceptionally(new ExperimentException(ERROR_FEATURE_NOT_FOUND)); + } + return result; + } + } }