Skip to content

Commit

Permalink
EranMes: Implement storing of modifier keys between calls. This enabl…
Browse files Browse the repository at this point in the history
…es combining keyboard and mouse actions with the advanced user interactions API.

r14127
  • Loading branch information
eranmes committed Oct 12, 2011
1 parent 64fef00 commit a690a68
Show file tree
Hide file tree
Showing 20 changed files with 102 additions and 29 deletions.
Binary file modified cpp/prebuilt/amd64/libwebdriver_firefox_ff4_64.so
Binary file not shown.
Binary file modified cpp/prebuilt/amd64/libwebdriver_firefox_ff5_64.so
Binary file not shown.
Binary file modified cpp/prebuilt/amd64/libwebdriver_firefox_ff6_64.so
Binary file not shown.
Binary file modified cpp/prebuilt/amd64/libwebdriver_firefox_ff7_64.so
Binary file not shown.
Binary file modified cpp/prebuilt/i386/libwebdriver_firefox_ff3.so
Binary file not shown.
Binary file modified cpp/prebuilt/i386/libwebdriver_firefox_ff4.so
Binary file not shown.
Binary file modified cpp/prebuilt/i386/libwebdriver_firefox_ff5.so
Binary file not shown.
Binary file modified cpp/prebuilt/i386/libwebdriver_firefox_ff6.so
Binary file not shown.
Binary file modified cpp/prebuilt/i386/libwebdriver_firefox_ff7.so
Binary file not shown.
3 changes: 1 addition & 2 deletions cpp/webdriver-interactions/interactions_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,7 @@ list<GdkEvent*> KeypressEventsHandler::CreateEventsForKey(
// First case - is it the NULL symbol? If so, reset modifiers and exit.
if (key_to_emulate == gNullKey) {
LOG(DEBUG) << "Null key - clearing modifiers.";
ClearModifiers();
return ret_list;
return CreateModifierReleaseEvents();
}

// Now: The key is either a modifier key or character key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public interface DriverCommand {
String CLICK_ELEMENT = "clickElement";
String HOVER_OVER_ELEMENT = "hoverOverElement";
String SEND_KEYS_TO_ELEMENT = "sendKeysToElement";
String SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT = "sendModifierKeyToActiveElement";
String SEND_KEYS_TO_ACTIVE_ELEMENT = "sendKeysToActiveElement";
String SUBMIT_ELEMENT = "submitElement";
String UPLOAD_FILE = "uploadFile";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
import static org.openqa.selenium.remote.DriverCommand.REMOVE_SESSION_STORAGE_ITEM;
import static org.openqa.selenium.remote.DriverCommand.SCREENSHOT;
import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.SET_ALERT_VALUE;
import static org.openqa.selenium.remote.DriverCommand.SET_BROWSER_ONLINE;
import static org.openqa.selenium.remote.DriverCommand.SET_BROWSER_VISIBLE;
Expand Down Expand Up @@ -317,7 +317,7 @@ public HttpCommandExecutor(URL addressOfRemoteServer) {
.put(MOUSE_DOWN, post("/session/:sessionId/buttondown"))
.put(MOUSE_UP, post("/session/:sessionId/buttonup"))
.put(MOVE_TO, post("/session/:sessionId/moveto"))
.put(SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT, post("/session/:sessionId/modifier"))
.put(SEND_KEYS_TO_ACTIVE_ELEMENT, post("/session/:sessionId/keys"))

// IME related commands.
.put(IME_GET_AVAILABLE_ENGINES, get("/session/:sessionId/ime/available_engines"))
Expand Down
11 changes: 6 additions & 5 deletions java/client/src/org/openqa/selenium/remote/RemoteWebDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -647,17 +647,18 @@ public void sendKeys(String keysToSend) {

private class RemoteKeyboard implements Keyboard {
public void sendKeys(CharSequence... keysToSend) {
switchTo().activeElement().sendKeys(keysToSend);
execute(DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT,
ImmutableMap.of("value", keysToSend));
}

public void pressKey(Keys keyToPress) {
execute(DriverCommand.SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT,
ImmutableMap.of("value", keyToPress, "isdown", true));
execute(DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT,
ImmutableMap.of("value", keyToPress));
}

public void releaseKey(Keys keyToRelease) {
execute(DriverCommand.SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT,
ImmutableMap.of("value", keyToRelease, "isdown", false));
execute(DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT,
ImmutableMap.of("value", keyToRelease));

}
}
Expand Down
4 changes: 4 additions & 0 deletions java/client/test/org/openqa/selenium/TestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public static boolean isFirefox(WebDriver driver) {
return getUserAgent(driver).contains("Firefox");
}

public static boolean isInternetExplorer(WebDriver driver) {
return getUserAgent(driver).contains("MSIE");
}

public static boolean isFirefox30(WebDriver driver) {
return getUserAgent(driver).contains("Firefox/3.0.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import static org.openqa.selenium.Ignore.Driver.OPERA;
import static org.openqa.selenium.Ignore.Driver.REMOTE;
import static org.openqa.selenium.Ignore.Driver.SELENESE;
import static org.openqa.selenium.TestUtilities.isFirefox;
import static org.openqa.selenium.TestUtilities.isInternetExplorer;
import static org.openqa.selenium.TestWaiter.waitFor;
import static org.openqa.selenium.WaitingConditions.elementToExist;

Expand Down Expand Up @@ -160,6 +162,13 @@ public void testChordControlCutAndPaste() {
return;
}

if (Platform.getCurrent().is(Platform.WINDOWS) &&
(isInternetExplorer(driver) || isFirefox(driver))) {
System.out.println("Skipping testChordControlCutAndPaste on Windows: native events library" +
" does not support storing modifiers state yet.");
return;
}

driver.get(pages.javascriptPage);

WebElement element = driver.findElement(By.id("keyReporter"));
Expand All @@ -170,18 +179,24 @@ public void testChordControlCutAndPaste() {

assertEquals("abc def", element.getAttribute("value"));

//TODO: Figure out why calling sendKey(Key.CONTROL + "a") and then
//sendKeys("x") does not work on Linux.
new Actions(driver)
.sendKeys(Keys.CONTROL + "a")
.sendKeys(Keys.CONTROL + "x")
.sendKeys(Keys.CONTROL + "a" + "x")
.perform();

// Release keys before next step.
new Actions(driver).sendKeys(Keys.NULL).perform();

assertEquals("", element.getAttribute("value"));

new Actions(driver)
.sendKeys(Keys.CONTROL + "v")
.sendKeys(Keys.CONTROL + "v")
.sendKeys("v")
.perform();

new Actions(driver).sendKeys(Keys.NULL).perform();

assertEquals("abc defabc def", element.getAttribute("value"));
}
}
4 changes: 4 additions & 0 deletions javascript/firefox-driver/extension/components/dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ Dispatcher.prototype.init_ = function() {
on(Request.Method.POST, Dispatcher.executeAs('mouseClick'));
this.bind_('/session/:sessionId/doubleclick').
on(Request.Method.POST, Dispatcher.executeAs('mouseDoubleClick'));
// Keyboard emulation
this.bind_('/session/:sessionId/keys').
on(Request.Method.POST, Dispatcher.executeAs('sendKeysToActiveElement'));



// --------------------------------------------------------------------------
Expand Down
32 changes: 32 additions & 0 deletions javascript/firefox-driver/extension/components/firefoxDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function FirefoxDriver(server, enableNativeEvents, win) {

this.jsTimer = new fxdriver.Timer();
this.mouse = Utils.newInstance("@googlecode.com/webdriver/syntheticmouse;1", "wdIMouse");
// Current state of modifier keys (for synthenized events).
this.modifierKeysState = undefined;
}


Expand Down Expand Up @@ -1255,3 +1257,33 @@ FirefoxDriver.prototype.mouseDoubleClick = function(respond, parameters) {
throw generateErrorForNativeEvents(this.enableNativeEvents, events, node);
}
};

FirefoxDriver.prototype.sendKeysToActiveElement = function(respond, parameters) {
Utils.installWindowCloseListener(respond);

var currentlyActiveElement = Utils.getActiveElement(respond.session.getDocument());

if(bot.dom.isEditable(currentlyActiveElement)) {
goog.dom.selection.setCursorPosition(
currentlyActiveElement, currentlyActiveElement.value.length);
}

var useElement = currentlyActiveElement;
var tagName = useElement.tagName.toLowerCase();
if (tagName == "body" && useElement.ownerDocument.defaultView.frameElement) {
useElement.ownerDocument.defaultView.focus();

// Turns out, this is what we should be using as the target
// to send events to
useElement = useElement.ownerDocument.getElementsByTagName("html")[0];
}

// In case Utils.type performs non-native typing, it will return the state of the
// modifier keys to be used in subsequent typing. This is a stop-gap solution until
// a syntheticKeyboard class will be extracted.
var newState = Utils.type(respond.session.getDocument(), useElement, parameters.value.join(''),
this.enableNativeEvents, this.jsTimer, false /*release modifiers*/, this.modifierKeysState);
this.modifierKeysState = newState;

respond.send();
};
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ FirefoxDriver.prototype.sendKeysToElement = function(respond, parameters) {
}

Utils.type(respond.session.getDocument(), use, parameters.value.join(''),
originalDriver.enableNativeEvents, originalDriver.jsTimer);
originalDriver.enableNativeEvents, originalDriver.jsTimer, true /*release modifiers*/);

respond.send();
}, 0);
Expand Down
26 changes: 20 additions & 6 deletions javascript/firefox-driver/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ Utils.useNativeEvents = function() {
return false;
}

Utils.type = function(doc, element, text, opt_useNativeEvents, jsTimer) {
Utils.type = function(doc, element, text, opt_useNativeEvents, jsTimer, releaseModifiers,
opt_keysState) {

// For consistency between native and synthesized events, convert common
// escape sequences to their Key enum aliases.
Expand All @@ -381,7 +382,7 @@ Utils.type = function(doc, element, text, opt_useNativeEvents, jsTimer) {
var pageUnloadedIndicator = Utils.getPageUnloadedIndicator(element);

// Now do the native thing.
obj.sendKeys(node, text, true);
obj.sendKeys(node, text, releaseModifiers);

Utils.waitForNativeEventsProcessing(element, obj, pageUnloadedIndicator, jsTimer);

Expand All @@ -393,6 +394,12 @@ Utils.type = function(doc, element, text, opt_useNativeEvents, jsTimer) {
var shiftKey = false;
var altKey = false;
var metaKey = false;
if (opt_keysState) {
controlKey = opt_keysState.control;
shiftKey = opt_keysState.shiftKey;
altKey = opt_keysState.alt;
metaKey = opt_keysState.meta;
}

Utils.shiftCount = 0;

Expand Down Expand Up @@ -669,29 +676,36 @@ Utils.type = function(doc, element, text, opt_useNativeEvents, jsTimer) {

// exit cleanup: keyup active modifier keys

if (controlKey) {
if (controlKey && releaseModifiers) {
var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
Utils.keyEvent(doc, element, "keyup", kCode, 0,
controlKey = false, shiftKey, altKey, metaKey);
}

if (shiftKey) {
if (shiftKey && releaseModifiers) {
var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
Utils.keyEvent(doc, element, "keyup", kCode, 0,
controlKey, shiftKey = false, altKey, metaKey);
}

if (altKey) {
if (altKey && releaseModifiers) {
var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
Utils.keyEvent(doc, element, "keyup", kCode, 0,
controlKey, shiftKey, altKey = false, metaKey);
}

if (metaKey) {
if (metaKey && releaseModifiers) {
var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
Utils.keyEvent(doc, element, "keyup", kCode, 0,
controlKey, shiftKey, altKey, metaKey = false);
}

return {
shiftKey: shiftKey,
alt: altKey,
meta: metaKey,
control: controlKey
};
};


Expand Down
22 changes: 13 additions & 9 deletions wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,16 +934,20 @@ def main():
'string to be typed.'))

resources.append(
SessionResource('/session/:sessionId/modifier').
Post('Send an event to the active element to depress or release a '
'modifier key.').
AddJsonParameter('value', '{string}',
'The modifier key event to be sent. This key must be one'
' Ctrl, Shift, Alt, or Command/Meta, as defined by the '
SessionResource('/session/:sessionId/keys').
Post('Send a sequence of key strokes to the active element. This '
'command is similar to the '
'[JsonWireProtocol#/session/:sessionId/element/:id/value'
' send keys] command in every aspect except the implicit '
'termination: The modifiers are *not* released at the end of the '
'call. Rather, the state of the modifier keys is kept between '
'calls, so mouse interactions can be performed while modifier '
'keys are depressed.').
AddJsonParameter('value', '{Array.<string>}',
'The keys sequence to be sent. The sequence is defined '
'in the'
'[JsonWireProtocol#/session/:sessionId/element/:id/value'
' send keys] command.').
AddJsonParameter('isdown', '{boolean}',
'Whether to generate a key down or key up.'))
' send keys] command.'))

resources.append(
ElementResource('/session/:sessionId/element/:id/name').
Expand Down

0 comments on commit a690a68

Please sign in to comment.