Skip to content

[Problem/Bug]: Page cursor is replaced by the system arrow while any (hidden) iframe in the page is loading #5771

Open

Description

@zlzlelzl

What happened?

When any iframe in the page is navigating, WebView2 shows the system arrow over the whole WebView. This happens even when the iframe is hidden or off-screen. The page's CSS cursor (a custom url() cursor or a keyword such as crosshair) comes back only when that iframe has finished loading.

Our e-book reader preloads the next pages in hidden iframes. Because of this, the cursor flips to the system arrow for 0.1 to 0.5 seconds after every page turn, and back again when the preload finishes.

Importance

Moderate. My app's user experience is affected, but still usable.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

155.0.4283.62

SDK Version

1.0.3650.58

Framework

Win32

Operating System

Windows 11

OS Version

10.0.26300

Repro steps

  1. Load the page below in WebView2. The page sets cursor: crosshair on the whole document.
  2. Rest the mouse over the page. Do not move it.
  3. Click "start". Every second, the page points a hidden iframe at a URL that takes about one second to load.

Expected: the crosshair stays.
Actual: the cursor turns into the system arrow while the hidden iframe loads, then goes back to the crosshair.

<!doctype html>
<html style="cursor: crosshair; height: 100%">
<body style="height: 100%; margin: 0">
<button id="b">start</button>
<iframe id="f" style="position: absolute; left: -10000px; width: 10px; height: 10px"></iframe>
<script>
  b.onclick = () => setInterval(() => { f.src = "https://httpbin.org/delay/1?" + Date.now(); }, 2000);
</script>
</body>
</html>

The same happens with a custom cursor (cursor: url(cur.png) 0 0, auto). It also happens with a same-origin blob: iframe navigated through contentWindow.location.replace(). That case is shorter, but you still see it.

Where it comes from (Chromium):

  • FrameTree::NodeLoadingStateChanged calls root()->render_manager()->SetIsLoading(...) whenever any frame in the tree starts or stops loading. This includes subframe loads that GetLoadingState reports as LOADING_WITHOUT_UI.
  • RenderWidgetHostViewAura::SetIsLoading → UpdateCursorIfOverSelf:
    // Do not show loading cursor when the cursor is currently hidden.
    if (is_loading_ && cursor != ui::mojom::CursorType::kNone)
      cursor = ui::Cursor(ui::mojom::CursorType::kPointer);
    As a result, any page cursor except none becomes the pointer until every frame finishes loading.

Suggestion: do not override the page's cursor for loads that do not show loading UI. For example, apply the override only for LOADING_UI_REQUESTED (a main-frame navigation), or give hosts a setting to turn it off.

Workaround we use: while our own iframes navigate, we set cursor: none !important and draw the cursor image ourselves at the mouse position. We wait about 40 ms before navigating, so that Blink's 20 ms cursor update reaches the browser first.

Repros in Edge Browser

Yes, issue can be reproduced in the corresponding Edge version

Regression

No, this never worked

Last working version (if regression)

No response

AB#64807350

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtrackedWe are tracking this work internally.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions