0

[WebLayer] Change Tab#executeScript() to return String directly

Clients wish to be able to process the result in ways other than it
becoming a JSONObject.

For convenience, we leave the test method that wraps this public API
returning a JSONObject; the wrapping of the String into a JSONObject
now occurs in that test API rather than in the public API.

Bug: 1284580
Change-Id: Idf4e8c3e8cfe624ab047cf018316168bf51bb5b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3370223
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#956422}
This commit is contained in:
Colin Blundell
2022-01-07 08:04:22 +00:00
committed by Chromium LUCI CQ
parent b12c10a7f6
commit 272cece5fe
4 changed files with 37 additions and 46 deletions
weblayer
browser
public
java
org
chromium
weblayer

@ -14,7 +14,6 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.UrlUtils; import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.shell.InstrumentationActivity; import org.chromium.weblayer.shell.InstrumentationActivity;
/** /**
@ -35,7 +34,8 @@ public class ExecuteScriptTest {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL); InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
JSONObject result = mActivityTestRule.executeScriptSync( JSONObject result = mActivityTestRule.executeScriptSync(
"document.body.innerHTML", true /* useSeparateIsolate */); "document.body.innerHTML", true /* useSeparateIsolate */);
Assert.assertEquals(result.getString(Tab.SCRIPT_RESULT_KEY), "foo"); Assert.assertEquals(
result.getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), "foo");
} }
@Test @Test
@ -44,7 +44,7 @@ public class ExecuteScriptTest {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL); InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
JSONObject result = JSONObject result =
mActivityTestRule.executeScriptSync("bar", true /* useSeparateIsolate */); mActivityTestRule.executeScriptSync("bar", true /* useSeparateIsolate */);
Assert.assertTrue(result.isNull(Tab.SCRIPT_RESULT_KEY)); Assert.assertTrue(result.isNull(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
} }
@Test @Test
@ -53,7 +53,7 @@ public class ExecuteScriptTest {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL); InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
JSONObject result = JSONObject result =
mActivityTestRule.executeScriptSync("bar", false /* useSeparateIsolate */); mActivityTestRule.executeScriptSync("bar", false /* useSeparateIsolate */);
Assert.assertEquals(result.getInt(Tab.SCRIPT_RESULT_KEY), 10); Assert.assertEquals(result.getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), 10);
} }
@Test @Test
@ -63,7 +63,7 @@ public class ExecuteScriptTest {
mActivityTestRule.executeScriptSync("var foo = 20;", true /* useSeparateIsolate */); mActivityTestRule.executeScriptSync("var foo = 20;", true /* useSeparateIsolate */);
JSONObject result = JSONObject result =
mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */); mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */);
Assert.assertEquals(result.getInt(Tab.SCRIPT_RESULT_KEY), 20); Assert.assertEquals(result.getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), 20);
} }
@Test @Test
@ -76,7 +76,7 @@ public class ExecuteScriptTest {
mActivityTestRule.navigateAndWait(newUrl); mActivityTestRule.navigateAndWait(newUrl);
JSONObject result = JSONObject result =
mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */); mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */);
Assert.assertTrue(result.isNull(Tab.SCRIPT_RESULT_KEY)); Assert.assertTrue(result.isNull(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
} }
@Test @Test

@ -37,7 +37,6 @@ import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.util.TestWebServer; import org.chromium.net.test.util.TestWebServer;
import org.chromium.weblayer.Browser; import org.chromium.weblayer.Browser;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TestWebLayer; import org.chromium.weblayer.TestWebLayer;
import org.chromium.weblayer.shell.InstrumentationActivity; import org.chromium.weblayer.shell.InstrumentationActivity;
@ -242,8 +241,9 @@ public final class GeolocationTest {
false); false);
CriteriaHelper.pollInstrumentationThread(() -> { CriteriaHelper.pollInstrumentationThread(() -> {
try { try {
String result = mActivityTestRule.executeScriptSync("queryResult || ''", false) String result =
.getString(Tab.SCRIPT_RESULT_KEY); mActivityTestRule.executeScriptSync("queryResult || ''", false)
.getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY);
Criteria.checkThat(result, Matchers.not("")); Criteria.checkThat(result, Matchers.not(""));
} catch (JSONException ex) { } catch (JSONException ex) {
throw new CriteriaNotSatisfiedException(ex); throw new CriteriaNotSatisfiedException(ex);
@ -251,7 +251,7 @@ public final class GeolocationTest {
}); });
Assert.assertEquals("prompt", Assert.assertEquals("prompt",
mActivityTestRule.executeScriptSync("queryResult", false) mActivityTestRule.executeScriptSync("queryResult", false)
.getString(Tab.SCRIPT_RESULT_KEY)); .getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
CriteriaHelper.pollInstrumentationThread( CriteriaHelper.pollInstrumentationThread(
() -> { return mTestWebLayer.isPermissionDialogShown(); }); () -> { return mTestWebLayer.isPermissionDialogShown(); });
} }
@ -277,7 +277,7 @@ public final class GeolocationTest {
int result = -1; int result = -1;
try { try {
result = mActivityTestRule.executeScriptSync(variableName, false) result = mActivityTestRule.executeScriptSync(variableName, false)
.getInt(Tab.SCRIPT_RESULT_KEY); .getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY);
} catch (Exception e) { } catch (Exception e) {
Assert.fail("Unable to get " + variableName); Assert.fail("Unable to get " + variableName);
} }

@ -43,17 +43,20 @@ import java.util.concurrent.TimeoutException;
*/ */
public class InstrumentationActivityTestRule public class InstrumentationActivityTestRule
extends WebLayerActivityTestRule<InstrumentationActivity> { extends WebLayerActivityTestRule<InstrumentationActivity> {
/** The top level key of the JSON object returned by executeScriptSync(). */
public static final String SCRIPT_RESULT_KEY = "result";
@Rule @Rule
private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule(); private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
private static final class JSONCallbackHelper extends CallbackHelper { private static final class StringCallbackHelper extends CallbackHelper {
private JSONObject mResult; private String mResult;
public JSONObject getResult() { public String getResult() {
return mResult; return mResult;
} }
public void notifyCalled(JSONObject result) { public void notifyCalled(String result) {
mResult = result; mResult = result;
notifyCalled(); notifyCalled();
} }
@ -149,22 +152,31 @@ public class InstrumentationActivityTestRule
} }
/** /**
* Executes the script passed in and waits for the result. * Executes the script passed in and waits for the result. Wraps that result in a JSONObject for
* convenience of callers that want to process that result as a type other than String.
*/ */
public JSONObject executeScriptSync(String script, boolean useSeparateIsolate, Tab tab) { public JSONObject executeScriptSync(String script, boolean useSeparateIsolate, Tab tab) {
JSONCallbackHelper callbackHelper = new JSONCallbackHelper(); StringCallbackHelper callbackHelper = new StringCallbackHelper();
int count = callbackHelper.getCallCount(); int count = callbackHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab scriptTab = tab == null ? getActivity().getBrowser().getActiveTab() : tab; Tab scriptTab = tab == null ? getActivity().getBrowser().getActiveTab() : tab;
scriptTab.executeScript(script, useSeparateIsolate, scriptTab.executeScript(script, useSeparateIsolate,
(JSONObject result) -> { callbackHelper.notifyCalled(result); }); (String result) -> { callbackHelper.notifyCalled(result); });
}); });
try { try {
callbackHelper.waitForCallback(count); callbackHelper.waitForCallback(count);
} catch (TimeoutException e) { } catch (TimeoutException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return callbackHelper.getResult(); JSONObject resultAsJSONObject;
try {
resultAsJSONObject = new JSONObject(
"{\"" + SCRIPT_RESULT_KEY + "\":" + callbackHelper.getResult() + "}");
} catch (JSONException e) {
// This should never happen since the result should be well formed.
throw new RuntimeException(e);
}
return resultAsJSONObject;
} }
public JSONObject executeScriptSync(String script, boolean useSeparateIsolate) { public JSONObject executeScriptSync(String script, boolean useSeparateIsolate) {
@ -174,7 +186,7 @@ public class InstrumentationActivityTestRule
public int executeScriptAndExtractInt(String script) { public int executeScriptAndExtractInt(String script) {
try { try {
return executeScriptSync(script, true /* useSeparateIsolate */) return executeScriptSync(script, true /* useSeparateIsolate */)
.getInt(Tab.SCRIPT_RESULT_KEY); .getInt(SCRIPT_RESULT_KEY);
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -186,7 +198,7 @@ public class InstrumentationActivityTestRule
public String executeScriptAndExtractString(String script, boolean useSeparateIsolate) { public String executeScriptAndExtractString(String script, boolean useSeparateIsolate) {
try { try {
return executeScriptSync(script, useSeparateIsolate).getString(Tab.SCRIPT_RESULT_KEY); return executeScriptSync(script, useSeparateIsolate).getString(SCRIPT_RESULT_KEY);
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -198,7 +210,7 @@ public class InstrumentationActivityTestRule
public boolean executeScriptAndExtractBoolean(String script, boolean useSeparateIsolate) { public boolean executeScriptAndExtractBoolean(String script, boolean useSeparateIsolate) {
try { try {
return executeScriptSync(script, useSeparateIsolate).getBoolean(Tab.SCRIPT_RESULT_KEY); return executeScriptSync(script, useSeparateIsolate).getBoolean(SCRIPT_RESULT_KEY);
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

@ -13,9 +13,6 @@ import android.webkit.ValueCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import org.chromium.weblayer_private.interfaces.APICallException; import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.IClientNavigation; import org.chromium.weblayer_private.interfaces.IClientNavigation;
import org.chromium.weblayer_private.interfaces.IContextMenuParams; import org.chromium.weblayer_private.interfaces.IContextMenuParams;
@ -42,9 +39,6 @@ import java.util.Set;
* configuring state of the tab, such as delegates and callbacks. * configuring state of the tab, such as delegates and callbacks.
*/ */
public class Tab { public class Tab {
/** The top level key of the JSON object returned by executeScript(). */
public static final String SCRIPT_RESULT_KEY = "result";
// Maps from id (as returned from ITab.getId()) to Tab. // Maps from id (as returned from ITab.getId()) to Tab.
private static final Map<Integer, Tab> sTabMap = new HashMap<Integer, Tab>(); private static final Map<Integer, Tab> sTabMap = new HashMap<Integer, Tab>();
@ -233,9 +227,7 @@ public class Tab {
} }
/** /**
* Executes the script, and returns the result as a JSON object to the callback if provided. The * Executes the script, and returns the result to the callback if provided.
* object passed to the callback will have a single key SCRIPT_RESULT_KEY which will hold the
* result of running the script.
* @param useSeparateIsolate If true, runs the script in a separate v8 Isolate. This uses more * @param useSeparateIsolate If true, runs the script in a separate v8 Isolate. This uses more
* memory, but separates the injected scrips from scripts in the page. This prevents any * memory, but separates the injected scrips from scripts in the page. This prevents any
* potentially malicious interaction between first-party scripts in the page, and injected * potentially malicious interaction between first-party scripts in the page, and injected
@ -243,24 +235,11 @@ public class Tab {
* or you need to interact with first-party scripts. * or you need to interact with first-party scripts.
*/ */
public void executeScript(@NonNull String script, boolean useSeparateIsolate, public void executeScript(@NonNull String script, boolean useSeparateIsolate,
@Nullable ValueCallback<JSONObject> callback) { @Nullable ValueCallback<String> callback) {
ThreadCheck.ensureOnUiThread(); ThreadCheck.ensureOnUiThread();
throwIfDestroyed(); throwIfDestroyed();
try { try {
ValueCallback<String> stringCallback = (String result) -> { mImpl.executeScript(script, useSeparateIsolate, ObjectWrapper.wrap(callback));
if (callback == null) {
return;
}
try {
callback.onReceiveValue(
new JSONObject("{\"" + SCRIPT_RESULT_KEY + "\":" + result + "}"));
} catch (JSONException e) {
// This should never happen since the result should be well formed.
throw new RuntimeException(e);
}
};
mImpl.executeScript(script, useSeparateIsolate, ObjectWrapper.wrap(stringCallback));
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }