Skip to content

Commit c488e10

Browse files
committed
EranMes: Make mouse hovering in Windows persistent by firing WM_MOUSEMOVE from a background thread. Resolves issue 2067.
r17545
1 parent da1bffd commit c488e10

18 files changed

Lines changed: 278 additions & 37 deletions

cpp/IEDriver/IESession.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "IESession.h"
1515
#include "IECommandExecutor.h"
1616
#include "logging.h"
17+
#include "interactions.h"
1718

1819
namespace webdriver {
1920

@@ -62,6 +63,9 @@ void IESession::Initialize(void* init_params) {
6263
void IESession::ShutDown(void) {
6364
LOG(TRACE) << "Entering IESession::ShutDown";
6465

66+
// Kill the background thread first - otherwise the IE process crashes.
67+
stopPersistentEventFiring();
68+
6569
DWORD process_id;
6670
DWORD thread_id = ::GetWindowThreadProcessId(this->executor_window_handle_,
6771
&process_id);
512 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
-3.5 KB
Binary file not shown.
1 KB
Binary file not shown.
-3.5 KB
Binary file not shown.
-4 KB
Binary file not shown.
1 KB
Binary file not shown.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
Copyright 2007-2012 WebDriver committers
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include "stdafx.h"
18+
#include "event_firing_thread.h"
19+
20+
// Thread for firing event
21+
HANDLE hConstantEventsThread = NULL;
22+
class EventFiringData
23+
{
24+
public:
25+
EventFiringData(HWND directInputTo, long onX, long onY, WPARAM buttonValue) :
26+
m_shouldFire(true), m_keepRunning(true),
27+
m_to(directInputTo), m_x(onX), m_y(onY), m_buttonState(buttonValue) { }
28+
// Used to control temporary firing of events.
29+
void pauseFiring() { m_shouldFire = false; }
30+
void resumeFiring() { m_shouldFire = true; }
31+
bool shouldFire() { return m_shouldFire; }
32+
// Used to control the existance of the background thread:
33+
// when shouldRun returns false the thread will exit.
34+
void stopRunning() { m_keepRunning = false; }
35+
bool shouldRun() { return m_keepRunning; }
36+
37+
// Information on where to send the event to.
38+
HWND getTarget() { return m_to; }
39+
long getXLocation() { return m_x; }
40+
long getYLocation() { return m_y; }
41+
WPARAM getInputDevicesState() { return m_buttonState; }
42+
43+
// Update the keyboard state.
44+
void setInputDevicesState(WPARAM buttonValue) { m_buttonState = buttonValue; }
45+
46+
// Fire events to a new window / coordinates.
47+
void setNewTarget(HWND directInputTo, long onX, long onY, WPARAM buttonValue)
48+
{
49+
m_to = directInputTo;
50+
m_x = onX;
51+
m_y = onY;
52+
m_buttonState = buttonValue;
53+
}
54+
55+
private:
56+
bool m_shouldFire;
57+
bool m_keepRunning;
58+
HWND m_to;
59+
long m_x, m_y;
60+
WPARAM m_buttonState;
61+
};
62+
63+
// Function passed to the thread.
64+
DWORD WINAPI MouseEventFiringFunction(LPVOID lpParam)
65+
{
66+
EventFiringData* firingData;
67+
68+
firingData = (EventFiringData*) lpParam;
69+
// busy-wait loop, waiting for 10 milliseconds between
70+
// dispatching events. Since the thread is usually
71+
// paused for short periods of time (tens of milliseconds),
72+
// a more modern signalling method was not used.
73+
while (firingData->shouldRun()) {
74+
if (firingData->shouldFire()) {
75+
SendMessage(firingData->getTarget(),
76+
WM_MOUSEMOVE, firingData->getInputDevicesState(),
77+
MAKELPARAM(firingData->getXLocation(),
78+
firingData->getYLocation()));
79+
}
80+
Sleep(10 /* ms */);
81+
}
82+
83+
return 0;
84+
}
85+
86+
EventFiringData* EVENT_FIRING_DATA;
87+
88+
void pausePersistentEventsFiring()
89+
{
90+
if ((hConstantEventsThread != NULL) && (EVENT_FIRING_DATA != NULL)) {
91+
EVENT_FIRING_DATA->pauseFiring();
92+
Sleep(10 /* ms */);
93+
}
94+
}
95+
96+
// Helper method to update the state of a given flag according to a toggle.
97+
static void setStateByFlag(bool shouldSetFlag, UINT flagValue)
98+
{
99+
if ((hConstantEventsThread == NULL) || (EVENT_FIRING_DATA == NULL)) {
100+
return;
101+
}
102+
103+
WPARAM currentInputState = EVENT_FIRING_DATA->getInputDevicesState();
104+
if (shouldSetFlag) {
105+
currentInputState |= flagValue;
106+
} else {
107+
currentInputState = currentInputState & (~flagValue);
108+
}
109+
EVENT_FIRING_DATA->setInputDevicesState(currentInputState);
110+
}
111+
112+
void updateShiftKeyState(bool isShiftPressed)
113+
{
114+
setStateByFlag(isShiftPressed, MK_SHIFT);
115+
}
116+
117+
void updateLeftMouseButtonState(bool isButtonPressed)
118+
{
119+
setStateByFlag(isButtonPressed, MK_LBUTTON);
120+
}
121+
122+
// Creates a new thread if there isn't one up and running.
123+
void resumePersistentEventsFiring(
124+
HWND inputTo, long toX, long toY, WPARAM buttonValue)
125+
{
126+
if (hConstantEventsThread == NULL) {
127+
EVENT_FIRING_DATA = new EventFiringData(inputTo, toX, toY, buttonValue);
128+
hConstantEventsThread = CreateThread(
129+
NULL, // Security permissions.
130+
0, // default stack size.
131+
MouseEventFiringFunction,
132+
EVENT_FIRING_DATA,
133+
0, // default creation flags
134+
NULL);
135+
} else {
136+
EVENT_FIRING_DATA->setNewTarget(inputTo, toX, toY, buttonValue);
137+
EVENT_FIRING_DATA->resumeFiring();
138+
}
139+
}
140+
141+
void resumePersistentEventsFiring()
142+
{
143+
if ((hConstantEventsThread == NULL) || (EVENT_FIRING_DATA == NULL)) {
144+
return;
145+
}
146+
EVENT_FIRING_DATA->resumeFiring();
147+
}
148+
149+
extern "C" {
150+
// Terminates the background thread.
151+
void stopPersistentEventFiring()
152+
{
153+
if ((hConstantEventsThread != NULL) && (EVENT_FIRING_DATA != NULL)) {
154+
EVENT_FIRING_DATA->stopRunning();
155+
WaitForSingleObject(hConstantEventsThread, 250 /* ms */);
156+
CloseHandle(hConstantEventsThread);
157+
hConstantEventsThread = NULL;
158+
delete EVENT_FIRING_DATA;
159+
EVENT_FIRING_DATA = NULL;
160+
}
161+
}
162+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Copyright 2007-2012 WebDriver committers
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#ifndef event_firing_thread_h_
18+
#define event_firing_thread_h_
19+
20+
/**
21+
* The purpose of background, persistent event firing is to make mouse hovering
22+
* on Windows work. IE, specifically, would consider the mouse to have moved
23+
* outside the window if WM_MOUSEMOVE messages are not constantly sent to
24+
* the window.
25+
*
26+
* This is achieved by starting a background thread that keeps firing those
27+
* messages every 10 ms. Event firing is paused when other mouse actions occur.
28+
* Additionally, the state of the input devices must be updated so that
29+
* WM_MOUSEMOVE events sent are consistent with previous actions. This
30+
* boils down to whether the Shift key is pressed and whether the left
31+
* mouse button is pressed (for drag-and-drop to work).
32+
**/
33+
// Start or resume persistent "mouse over" event firing by a
34+
// background thread.
35+
extern void resumePersistentEventsFiring(
36+
HWND inputTo, long toX, long toY, WPARAM buttonValue);
37+
// Resume without changing the target. Used after pausing evennt
38+
// firing for mouse actions.
39+
extern void resumePersistentEventsFiring();
40+
// Pauses persistent event firing by the background thread.
41+
extern void pausePersistentEventsFiring();
42+
// When the state of the shift key changes, update the background thread
43+
// so that subsequent mouse over events will have the right keyboard state.
44+
extern void updateShiftKeyState(bool isShiftPressed);
45+
// When the left mouse button is pressed, update the background thread.
46+
// Otherwise IE gets confused.
47+
extern void updateLeftMouseButtonState(bool isButtonPressed);
48+
#endif // event_firing_thread_h_

0 commit comments

Comments
 (0)