Skip to content

Commit

Permalink
Fix scrolling timeout issue
Browse files Browse the repository at this point in the history
This change addresses a potential issue where the maximum scrolling
distance may change between calculating it, sending the scroll message,
and verifying the new position. If verifying the position times out, the
code now checks if the scrolling distance has changed. If it has,
scrolling is retried once.

Additionally, the maximum wait time for verifying the new position has
been reduced from 30 seconds to 3 seconds.
  • Loading branch information
otsch committed Jan 12, 2025
1 parent 8a6052c commit 2ba53d1
Showing 1 changed file with 63 additions and 14 deletions.
77 changes: 63 additions & 14 deletions src/Input/Mouse.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public function click(?array $options = null)
*
* @throws \HeadlessChromium\Exception\CommunicationException
* @throws \HeadlessChromium\Exception\NoResponseAvailable
* @throws \HeadlessChromium\Exception\OperationTimedOut
*
* @return $this
*/
Expand All @@ -156,6 +157,7 @@ public function scrollUp(int $distance)
*
* @throws \HeadlessChromium\Exception\CommunicationException
* @throws \HeadlessChromium\Exception\NoResponseAvailable
* @throws \HeadlessChromium\Exception\OperationTimedOut
*
* @return $this
*/
Expand All @@ -180,6 +182,52 @@ private function scroll(int $distanceY, int $distanceX = 0): self
{
$this->page->assertNotClosed();

// make sure the mouse is on the screen
$this->move($this->x, $this->y);

[$distances, $targets] = $this->getScrollDistancesAndTargets($distanceY, $distanceX);

Check failure on line 188 in src/Input/Mouse.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 0 does not exist on array{distances: array{x: int, y: int}, targets: array{x: int, y: int}}.

Check failure on line 188 in src/Input/Mouse.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 1 does not exist on array{distances: array{x: int, y: int}, targets: array{x: int, y: int}}.

// scroll
$this->sendScrollMessage($distances);

try {
// wait until the scroll is done
Utils::tryWithTimeout(3_000_000, $this->waitForScroll($targets['x'], $targets['y']));
} catch (\HeadlessChromium\Exception\OperationTimedOut $exception) {
// Maybe the possible max scroll distances changed in the meantime.
$prevDistances = $distances;

[$distances, $targets] = $this->getScrollDistancesAndTargets($distanceY, $distanceX);

Check failure on line 200 in src/Input/Mouse.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 0 does not exist on array{distances: array{x: int, y: int}, targets: array{x: int, y: int}}.

Check failure on line 200 in src/Input/Mouse.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 1 does not exist on array{distances: array{x: int, y: int}, targets: array{x: int, y: int}}.

if ($prevDistances === $distances) {
throw $exception;
}

if ($distanceY !== 0 || $distanceX !== 0) { // Try with the new values.
$this->sendScrollMessage($distances);

// wait until the scroll is done
Utils::tryWithTimeout(3_000_000, $this->waitForScroll($targets['x'], $targets['y']));
}
}

// set new position after move
$this->x += $distances['x'];
$this->y += $distances['y'];

return $this;
}

/**
* @throws \HeadlessChromium\Exception\OperationTimedOut
* @throws \HeadlessChromium\Exception\CommunicationException
* @throws \HeadlessChromium\Exception\CommunicationException\ResponseHasError
* @throws \HeadlessChromium\Exception\NoResponseAvailable
*
* @return array{ distances: array{ x: int, y: int }, targets: array{ x: int, y: int } }
*/
private function getScrollDistancesAndTargets(int $distanceY, int $distanceX = 0): array
{
$scrollableArea = $this->page->getLayoutMetrics()->getCssContentSize();
$visibleArea = $this->page->getLayoutMetrics()->getCssVisualViewport();

Expand All @@ -192,26 +240,27 @@ private function scroll(int $distanceY, int $distanceX = 0): self
$targetX = $visibleArea['pageX'] + $distanceX;
$targetY = $visibleArea['pageY'] + $distanceY;

// make sure the mouse is on the screen
$this->move($this->x, $this->y);
return [

Check failure on line 243 in src/Input/Mouse.php

View workflow job for this annotation

GitHub Actions / PHPStan

Method HeadlessChromium\Input\Mouse::getScrollDistancesAndTargets() should return array{distances: array{x: int, y: int}, targets: array{x: int, y: int}} but returns array{array{x: int, y: int}, array{x: (float|int), y: (float|int)}}.
['x' => $distanceX, 'y' => $distanceY],
['x' => $targetX, 'y' => $targetY],
];
}

// scroll
/**
* @param array{ x: int, y: int } $distances
*
* @throws \HeadlessChromium\Exception\CommunicationException
* @throws \HeadlessChromium\Exception\NoResponseAvailable
*/
private function sendScrollMessage(array $distances): void
{
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchMouseEvent', [
'type' => 'mouseWheel',
'x' => $this->x,
'y' => $this->y,
'deltaX' => $distanceX,
'deltaY' => $distanceY,
'deltaX' => $distances['x'],
'deltaY' => $distances['y'],
]));

// wait until the scroll is done
Utils::tryWithTimeout(30000 * 1000, $this->waitForScroll($targetX, $targetY));

// set new position after move
$this->x += $distanceX;
$this->y += $distanceY;

return $this;
}

/**
Expand Down

0 comments on commit 2ba53d1

Please sign in to comment.