1<?php
2
3namespace Facebook\WebDriver\Remote;
4
5use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
6use Facebook\WebDriver\WebDriverMouse;
7
8/**
9 * Execute mouse commands for RemoteWebDriver.
10 */
11class RemoteMouse implements WebDriverMouse
12{
13    /** @internal */
14    const BUTTON_LEFT = 0;
15    /** @internal */
16    const BUTTON_MIDDLE = 1;
17    /** @internal */
18    const BUTTON_RIGHT = 2;
19
20    /**
21     * @var RemoteExecuteMethod
22     */
23    private $executor;
24    /**
25     * @var bool
26     */
27    private $isW3cCompliant;
28
29    /**
30     * @param RemoteExecuteMethod $executor
31     * @param bool $isW3cCompliant
32     */
33    public function __construct(RemoteExecuteMethod $executor, $isW3cCompliant = false)
34    {
35        $this->executor = $executor;
36        $this->isW3cCompliant = $isW3cCompliant;
37    }
38
39    /**
40     * @param null|WebDriverCoordinates $where
41     *
42     * @return RemoteMouse
43     */
44    public function click(WebDriverCoordinates $where = null)
45    {
46        if ($this->isW3cCompliant) {
47            $moveAction = $where ? [$this->createMoveAction($where)] : [];
48            $this->executor->execute(DriverCommand::ACTIONS, [
49                'actions' => [
50                    [
51                        'type' => 'pointer',
52                        'id' => 'mouse',
53                        'parameters' => ['pointerType' => 'mouse'],
54                        'actions' => array_merge($moveAction, $this->createClickActions()),
55                    ],
56                ],
57            ]);
58
59            return $this;
60        }
61
62        $this->moveIfNeeded($where);
63        $this->executor->execute(DriverCommand::CLICK, [
64            'button' => self::BUTTON_LEFT,
65        ]);
66
67        return $this;
68    }
69
70    /**
71     * @param WebDriverCoordinates $where
72     *
73     * @return RemoteMouse
74     */
75    public function contextClick(WebDriverCoordinates $where = null)
76    {
77        if ($this->isW3cCompliant) {
78            $moveAction = $where ? [$this->createMoveAction($where)] : [];
79            $this->executor->execute(DriverCommand::ACTIONS, [
80                'actions' => [
81                    [
82                        'type' => 'pointer',
83                        'id' => 'mouse',
84                        'parameters' => ['pointerType' => 'mouse'],
85                        'actions' => array_merge($moveAction, [
86                            [
87                                'type' => 'pointerDown',
88                                'button' => self::BUTTON_RIGHT,
89                            ],
90                            [
91                                'type' => 'pointerUp',
92                                'button' => self::BUTTON_RIGHT,
93                            ],
94                        ]),
95                    ],
96                ],
97            ]);
98
99            return $this;
100        }
101
102        $this->moveIfNeeded($where);
103        $this->executor->execute(DriverCommand::CLICK, [
104            'button' => self::BUTTON_RIGHT,
105        ]);
106
107        return $this;
108    }
109
110    /**
111     * @param WebDriverCoordinates $where
112     *
113     * @return RemoteMouse
114     */
115    public function doubleClick(WebDriverCoordinates $where = null)
116    {
117        if ($this->isW3cCompliant) {
118            $clickActions = $this->createClickActions();
119            $moveAction = $where === null ? [] : [$this->createMoveAction($where)];
120            $this->executor->execute(DriverCommand::ACTIONS, [
121                'actions' => [
122                    [
123                        'type' => 'pointer',
124                        'id' => 'mouse',
125                        'parameters' => ['pointerType' => 'mouse'],
126                        'actions' => array_merge($moveAction, $clickActions, $clickActions),
127                    ],
128                ],
129            ]);
130
131            return $this;
132        }
133
134        $this->moveIfNeeded($where);
135        $this->executor->execute(DriverCommand::DOUBLE_CLICK);
136
137        return $this;
138    }
139
140    /**
141     * @param WebDriverCoordinates $where
142     *
143     * @return RemoteMouse
144     */
145    public function mouseDown(WebDriverCoordinates $where = null)
146    {
147        if ($this->isW3cCompliant) {
148            $this->executor->execute(DriverCommand::ACTIONS, [
149                'actions' => [
150                    [
151                        'type' => 'pointer',
152                        'id' => 'mouse',
153                        'parameters' => ['pointerType' => 'mouse'],
154                        'actions' => [
155                            $this->createMoveAction($where),
156                            [
157                                'type' => 'pointerDown',
158                                'button' => self::BUTTON_LEFT,
159                            ],
160                        ],
161                    ],
162                ],
163            ]);
164
165            return $this;
166        }
167
168        $this->moveIfNeeded($where);
169        $this->executor->execute(DriverCommand::MOUSE_DOWN);
170
171        return $this;
172    }
173
174    /**
175     * @param WebDriverCoordinates $where
176     * @param int|null $x_offset
177     * @param int|null $y_offset
178     *
179     * @return RemoteMouse
180     */
181    public function mouseMove(
182        WebDriverCoordinates $where = null,
183        $x_offset = null,
184        $y_offset = null
185    ) {
186        if ($this->isW3cCompliant) {
187            $this->executor->execute(DriverCommand::ACTIONS, [
188                'actions' => [
189                    [
190                        'type' => 'pointer',
191                        'id' => 'mouse',
192                        'parameters' => ['pointerType' => 'mouse'],
193                        'actions' => [$this->createMoveAction($where, $x_offset, $y_offset)],
194                    ],
195                ],
196            ]);
197
198            return $this;
199        }
200
201        $params = [];
202        if ($where !== null) {
203            $params['element'] = $where->getAuxiliary();
204        }
205        if ($x_offset !== null) {
206            $params['xoffset'] = $x_offset;
207        }
208        if ($y_offset !== null) {
209            $params['yoffset'] = $y_offset;
210        }
211
212        $this->executor->execute(DriverCommand::MOVE_TO, $params);
213
214        return $this;
215    }
216
217    /**
218     * @param WebDriverCoordinates $where
219     *
220     * @return RemoteMouse
221     */
222    public function mouseUp(WebDriverCoordinates $where = null)
223    {
224        if ($this->isW3cCompliant) {
225            $moveAction = $where ? [$this->createMoveAction($where)] : [];
226
227            $this->executor->execute(DriverCommand::ACTIONS, [
228                'actions' => [
229                    [
230                        'type' => 'pointer',
231                        'id' => 'mouse',
232                        'parameters' => ['pointerType' => 'mouse'],
233                        'actions' => array_merge($moveAction, [
234                            [
235                                'type' => 'pointerUp',
236                                'button' => self::BUTTON_LEFT,
237                            ],
238                        ]),
239                    ],
240                ],
241            ]);
242
243            return $this;
244        }
245
246        $this->moveIfNeeded($where);
247        $this->executor->execute(DriverCommand::MOUSE_UP);
248
249        return $this;
250    }
251
252    /**
253     * @param WebDriverCoordinates $where
254     */
255    protected function moveIfNeeded(WebDriverCoordinates $where = null)
256    {
257        if ($where) {
258            $this->mouseMove($where);
259        }
260    }
261
262    /**
263     * @param WebDriverCoordinates $where
264     * @param int|null $x_offset
265     * @param int|null $y_offset
266     *
267     * @return array
268     */
269    private function createMoveAction(
270        WebDriverCoordinates $where = null,
271        $x_offset = null,
272        $y_offset = null
273    ) {
274        $move_action = [
275            'type' => 'pointerMove',
276            'duration' => 100, // to simulate human delay
277            'x' => $x_offset === null ? 0 : $x_offset,
278            'y' => $y_offset === null ? 0 : $y_offset,
279        ];
280
281        if ($where !== null) {
282            $move_action['origin'] = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $where->getAuxiliary()];
283        } else {
284            $move_action['origin'] = 'pointer';
285        }
286
287        return $move_action;
288    }
289
290    /**
291     * @return array
292     */
293    private function createClickActions()
294    {
295        return [
296            [
297                'type' => 'pointerDown',
298                'button' => self::BUTTON_LEFT,
299            ],
300            [
301                'type' => 'pointerUp',
302                'button' => self::BUTTON_LEFT,
303            ],
304        ];
305    }
306}
307