Skip to content

Commit b5822ce

Browse files
authored
Chrome-based browsers can do CDP-based script pinning (#13125)
1 parent 10adfe8 commit b5822ce

File tree

4 files changed

+88
-84
lines changed

4 files changed

+88
-84
lines changed

java/src/org/openqa/selenium/ScriptKey.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public ScriptKey(String identifier) {
2828
this.identifier = Require.nonNull("Script ID", identifier);
2929
}
3030

31+
public String getIdentifier() {
32+
return identifier;
33+
}
34+
3135
@Override
3236
public boolean equals(Object o) {
3337
if (!(o instanceof ScriptKey)) {

java/src/org/openqa/selenium/chromium/ChromiumDriver.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323

2424
import java.net.URI;
2525
import java.net.URISyntaxException;
26+
import java.util.HashMap;
2627
import java.util.List;
2728
import java.util.Map;
2829
import java.util.Optional;
30+
import java.util.Set;
2931
import java.util.function.Predicate;
3032
import java.util.function.Supplier;
3133
import java.util.logging.Level;
@@ -35,7 +37,9 @@
3537
import org.openqa.selenium.Credentials;
3638
import org.openqa.selenium.HasAuthentication;
3739
import org.openqa.selenium.ImmutableCapabilities;
40+
import org.openqa.selenium.JavascriptException;
3841
import org.openqa.selenium.PersistentCapabilities;
42+
import org.openqa.selenium.ScriptKey;
3943
import org.openqa.selenium.WebDriver;
4044
import org.openqa.selenium.WebDriverException;
4145
import org.openqa.selenium.bidi.BiDi;
@@ -54,6 +58,7 @@
5458
import org.openqa.selenium.html5.SessionStorage;
5559
import org.openqa.selenium.html5.WebStorage;
5660
import org.openqa.selenium.internal.Require;
61+
import org.openqa.selenium.json.TypeToken;
5762
import org.openqa.selenium.logging.EventType;
5863
import org.openqa.selenium.logging.HasLogEvents;
5964
import org.openqa.selenium.mobile.NetworkConnection;
@@ -102,6 +107,7 @@ public class ChromiumDriver extends RemoteWebDriver
102107
private final Optional<BiDi> biDi;
103108
protected HasCasting casting;
104109
protected HasCdp cdp;
110+
private final Map<Integer, ScriptKey> scriptKeys = new HashMap<>();
105111

106112
protected ChromiumDriver(
107113
CommandExecutor commandExecutor, Capabilities capabilities, String capabilityKey) {
@@ -194,6 +200,75 @@ public Capabilities getCapabilities() {
194200
return capabilities;
195201
}
196202

203+
@Override
204+
public ScriptKey pin(String script) {
205+
Require.nonNull("Script to pin", script);
206+
207+
ScriptKey existingKey = scriptKeys.get(script.hashCode());
208+
if (existingKey != null) {
209+
return existingKey;
210+
}
211+
212+
// Create the actual script we're going to use.
213+
String scriptToUse =
214+
String.format(
215+
"window.seleniumPinnedScript%s = function(){%s}", Math.abs(script.hashCode()), script);
216+
217+
DevTools devTools = getDevTools();
218+
devTools.createSessionIfThereIsNotOne();
219+
devTools.send(new org.openqa.selenium.devtools.Command<>("Page.enable", Map.of()));
220+
devTools.send(
221+
new org.openqa.selenium.devtools.Command<>(
222+
"Runtime.evaluate", Map.of("expression", scriptToUse)));
223+
Map<String, Object> result =
224+
devTools.send(
225+
new org.openqa.selenium.devtools.Command<>(
226+
"Page.addScriptToEvaluateOnNewDocument",
227+
Map.of("source", scriptToUse),
228+
new TypeToken<Map<String, Object>>() {}.getType()));
229+
230+
ScriptKey key = new ScriptKey((String) result.get("identifier"));
231+
scriptKeys.put(script.hashCode(), key);
232+
return key;
233+
}
234+
235+
@Override
236+
public Set<ScriptKey> getPinnedScripts() {
237+
return Set.copyOf(scriptKeys.values());
238+
}
239+
240+
@Override
241+
public void unpin(ScriptKey key) {
242+
int hashCode = getScriptId(key);
243+
244+
executeScript(String.format("window.seleniumPinnedScript%s = undefined", Math.abs(hashCode)));
245+
scriptKeys.remove(hashCode);
246+
247+
DevTools devTools = getDevTools();
248+
devTools.send(
249+
new org.openqa.selenium.devtools.Command(
250+
"Page.removeScriptToEvaluateOnNewDocument", Map.of("identifier", key.getIdentifier())));
251+
}
252+
253+
@Override
254+
public Object executeScript(ScriptKey key, Object... args) {
255+
int hashCode = getScriptId(key);
256+
257+
String scriptToUse =
258+
String.format(
259+
"return window.seleniumPinnedScript%s.apply(window, arguments)", Math.abs(hashCode));
260+
261+
return this.executeScript(scriptToUse, args);
262+
}
263+
264+
private int getScriptId(ScriptKey key) {
265+
return scriptKeys.entrySet().stream()
266+
.filter(entry -> key.equals(entry.getValue()))
267+
.map(Map.Entry::getKey)
268+
.findAny()
269+
.orElseThrow(() -> new JavascriptException("Unable to find script key matching " + key));
270+
}
271+
197272
@Override
198273
public void setFileDetector(FileDetector detector) {
199274
throw new WebDriverException(

java/src/org/openqa/selenium/remote/RemoteWebDriver.java

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
import org.openqa.selenium.HasCapabilities;
5656
import org.openqa.selenium.HasDownloads;
5757
import org.openqa.selenium.ImmutableCapabilities;
58-
import org.openqa.selenium.JavascriptException;
5958
import org.openqa.selenium.JavascriptExecutor;
6059
import org.openqa.selenium.MutableCapabilities;
6160
import org.openqa.selenium.NoAlertPresentException;
@@ -66,11 +65,9 @@
6665
import org.openqa.selenium.Platform;
6766
import org.openqa.selenium.Point;
6867
import org.openqa.selenium.PrintsPage;
69-
import org.openqa.selenium.ScriptKey;
7068
import org.openqa.selenium.SearchContext;
7169
import org.openqa.selenium.SessionNotCreatedException;
7270
import org.openqa.selenium.TakesScreenshot;
73-
import org.openqa.selenium.UnpinnedScriptKey;
7471
import org.openqa.selenium.WebDriver;
7572
import org.openqa.selenium.WebDriverException;
7673
import org.openqa.selenium.WebElement;
@@ -86,7 +83,6 @@
8683
import org.openqa.selenium.internal.Debug;
8784
import org.openqa.selenium.internal.Require;
8885
import org.openqa.selenium.io.Zip;
89-
import org.openqa.selenium.json.TypeToken;
9086
import org.openqa.selenium.logging.LocalLogs;
9187
import org.openqa.selenium.logging.LoggingHandler;
9288
import org.openqa.selenium.logging.Logs;
@@ -480,86 +476,6 @@ public Object executeAsyncScript(String script, Object... args) {
480476
return execute(DriverCommand.EXECUTE_ASYNC_SCRIPT(script, convertedArgs)).getValue();
481477
}
482478

483-
@Override
484-
public ScriptKey pin(String script) {
485-
UnpinnedScriptKey key = (UnpinnedScriptKey) JavascriptExecutor.super.pin(script);
486-
String browserName = getCapabilities().getBrowserName().toLowerCase();
487-
if ((browserName.equals("chrome")
488-
|| browserName.equals("msedge")
489-
|| browserName.equals("microsoftedge"))
490-
&& this instanceof HasDevTools) {
491-
492-
((HasDevTools) this)
493-
.maybeGetDevTools()
494-
.ifPresent(
495-
devTools -> {
496-
devTools.createSessionIfThereIsNotOne();
497-
devTools.send(
498-
new org.openqa.selenium.devtools.Command<>("Page.enable", ImmutableMap.of()));
499-
devTools.send(
500-
new org.openqa.selenium.devtools.Command<>(
501-
"Runtime.evaluate", ImmutableMap.of("expression", key.creationScript())));
502-
Map<String, Object> result =
503-
devTools.send(
504-
new org.openqa.selenium.devtools.Command<>(
505-
"Page.addScriptToEvaluateOnNewDocument",
506-
ImmutableMap.of("source", key.creationScript()),
507-
new TypeToken<Map<String, Object>>() {}.getType()));
508-
key.setScriptId((String) result.get("identifier"));
509-
});
510-
}
511-
return key;
512-
}
513-
514-
@Override
515-
public void unpin(ScriptKey scriptKey) {
516-
UnpinnedScriptKey key = (UnpinnedScriptKey) scriptKey;
517-
518-
JavascriptExecutor.super.unpin(key);
519-
520-
String browserName = getCapabilities().getBrowserName().toLowerCase();
521-
if ((browserName.equals("chrome")
522-
|| browserName.equals("msedge")
523-
|| browserName.equals("microsoftedge"))
524-
&& this instanceof HasDevTools) {
525-
((HasDevTools) this)
526-
.maybeGetDevTools()
527-
.ifPresent(
528-
devTools -> {
529-
devTools.send(
530-
new org.openqa.selenium.devtools.Command<>("Page.enable", ImmutableMap.of()));
531-
devTools.send(
532-
new org.openqa.selenium.devtools.Command<>(
533-
"Runtime.evaluate", ImmutableMap.of("expression", key.removalScript())));
534-
devTools.send(
535-
new org.openqa.selenium.devtools.Command<>(
536-
"Page.removeScriptToEvaluateOnLoad",
537-
ImmutableMap.of("identifier", key.getScriptId())));
538-
});
539-
}
540-
}
541-
542-
@Override
543-
public Object executeScript(ScriptKey key, Object... args) {
544-
Require.stateCondition(
545-
key instanceof UnpinnedScriptKey, "Script key should have been generated by this driver");
546-
547-
if (!getPinnedScripts().contains(key)) {
548-
throw new JavascriptException("Script is unpinned");
549-
}
550-
551-
String browserName = getCapabilities().getBrowserName().toLowerCase();
552-
553-
if ((browserName.equals("chrome")
554-
|| browserName.equals("msedge")
555-
|| browserName.equals("microsoftedge"))
556-
&& this instanceof HasDevTools) {
557-
return executeScript(((UnpinnedScriptKey) key).executionScript(), args);
558-
}
559-
560-
return executeScript(((UnpinnedScriptKey) key).getScript(), args);
561-
}
562-
563479
@Override
564480
public TargetLocator switchTo() {
565481
return new RemoteTargetLocator();

java/test/org/openqa/selenium/ScriptPinningTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,13 @@ void callingAnUnpinnedScriptIsAnError() {
8787
assertThatExceptionOfType(JavascriptException.class)
8888
.isThrownBy(() -> executor.executeScript(cheese));
8989
}
90+
91+
@Test
92+
void afterPinningScriptShouldBeAvailableOnEveryPage() {
93+
ScriptKey cheese = executor.pin("return 'havarti'");
94+
95+
driver.get(pages.xhtmlTestPage);
96+
97+
assertThat(executor.executeScript(cheese)).isEqualTo("havarti");
98+
}
9099
}

0 commit comments

Comments
 (0)