jupyterlab/lumino

Drag & drop (tabbar, panels), data grid and others are slow when many DOM nodes are present

krassowski opened this issue · 3 comments

Description

Drag.overrideCursor sets a cursor and a class on the document.body:

/**
* Override the cursor icon for the entire document.
*
* @param cursor - The string representing the cursor style.
*
* @returns A disposable which will clear the override when disposed.
*
* #### Notes
* The most recent call to `overrideCursor` takes precedence.
* Disposing an old override has no effect on the current override.
*
* This utility function is used by the `Drag` class to override the
* mouse cursor during a drag-drop operation, but it can also be used
* by other classes to fix the cursor icon during normal mouse drags.
*
* #### Example
* ```typescript
* import { Drag } from '@lumino/dragdrop';
*
* // Force the cursor to be 'wait' for the entire document.
* let override = Drag.overrideCursor('wait');
*
* // Clear the override by disposing the return value.
* override.dispose();
* ```
*/
export function overrideCursor(
cursor: string,
doc: Document | ShadowRoot = document
): IDisposable {
let id = ++overrideCursorID;
const body =
doc instanceof Document
? doc.body
: (doc.firstElementChild as HTMLElement);
body.style.cursor = cursor;
body.classList.add('lm-mod-override-cursor');
return new DisposableDelegate(() => {
if (id === overrideCursorID) {
body.style.cursor = '';
body.classList.remove('lm-mod-override-cursor');
}
});
}
/**
* The internal id for the active cursor override.
*/
let overrideCursorID = 0;
}

It is used by a number of places:

In Chromium, there is a bug: Issue 664066: Change global cursor without recalculating style which causes exactly this action to recalculate styles of every single element in the document.

A freeze is also observable in Firefox, however there the Style Recalculation is 4 times faster (~250ms vs ~1.7 seconds).

Reproduce (in JupyterLab)

  1. Download gh-9757-reproducer.ipynb
  2. Run all cells
  3. Either:
    • a) Open a CSV file with DataGrid, try clicking on it the body or the header
    • b) Move a tab to another/new docking area
  4. See the UI freeze for 1.5 seconds on mouse down and then freeze again on mouse up

a) DataGrid click

Clicking on the data grid froze the menu for 3 seconds, with both actions attributable to Drag.overrideCursor call.

Screenshot from 2022-10-30 20-49-14

b) Moving tabs

Moving a tab to a new dock panel took me 10 seconds. 7 seconds were 4 Recalculate Style actions, one of them (1.7 seconds) can certainly be attributed to this problem and is highlighted on the screenshot below. The remaining issues are layout trashing when creating the blue overlay, possibly avoidable but a separate issue.

Screenshot from 2022-10-30 20-41-39

Expected behavior

No UI freezes.

Context

  • Browser and version: Chrome 107
  • JupyterLab version: 3.5.0

Thanks for tracking this down. What solution do we have for this? Shadow nodes?

Shadow DOM should help but we need to test it to be sure (its on my agenda). Since quite a few of the problematic Style Recalculation paths are also issues in Chromium (I hesitate to call them bugs, maybe buglets - they are non optimized code paths in algorithm which should be, and for the most part is, heavily optimized), there is no guarantee theses issues would go away if we just add a shadow DOM boundary.

As for this specific issue I have some things to try. The rough idea is to set the cursor on the closest element which needs to be modified. If all fails we could just add a cursor-like div serving as a drag reminder (kind of like we have the semi-transparent node already when moving cells/file browser items).

We could also start using native https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable which lets browser change the cursor as it wishes, but this may be a larger refactor.