/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../vs/base/browser/dom'; import { IframeUtils } from '../../../vs/base/browser/iframe'; import { StandardMouseEvent } from '../../../vs/base/browser/mouseEvent'; import { IDisposable, DisposableStore, } from '../../../vs/base/common/lifecycle'; export interface IStandardMouseMoveEventData { leftButton: boolean; buttons: number; posx: number; posy: number; } export interface IEventMerger { (lastEvent: R | null, currentEvent: MouseEvent): R; } export interface IMouseMoveCallback { (mouseMoveData: R): void; } export interface IOnStopCallback { (browserEvent?: MouseEvent | KeyboardEvent): void; } export function standardMouseMoveMerger( lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent ): IStandardMouseMoveEventData { let ev = new StandardMouseEvent(currentEvent); ev.preventDefault(); return { leftButton: ev.leftButton, buttons: ev.buttons, posx: ev.posx, posy: ev.posy, }; } export class GlobalMouseMoveMonitor implements IDisposable { private readonly _hooks = new DisposableStore(); private _mouseMoveEventMerger: IEventMerger | null = null; private _mouseMoveCallback: IMouseMoveCallback | null = null; private _onStopCallback: IOnStopCallback | null = null; public dispose(): void { this.stopMonitoring(false); this._hooks.dispose(); } public stopMonitoring( invokeStopCallback: boolean, browserEvent?: MouseEvent | KeyboardEvent ): void { if (!this.isMonitoring()) { // Not monitoring return; } // Unhook this._hooks.clear(); this._mouseMoveEventMerger = null; this._mouseMoveCallback = null; const onStopCallback = this._onStopCallback; this._onStopCallback = null; if (invokeStopCallback && onStopCallback) { onStopCallback(browserEvent); } } public isMonitoring(): boolean { return !!this._mouseMoveEventMerger; } public startMonitoring( initialElement: HTMLElement, initialButtons: number, mouseMoveEventMerger: IEventMerger, mouseMoveCallback: IMouseMoveCallback, onStopCallback: IOnStopCallback ): void { if (this.isMonitoring()) { // I am already hooked return; } this._mouseMoveEventMerger = mouseMoveEventMerger; this._mouseMoveCallback = mouseMoveCallback; this._onStopCallback = onStopCallback; const windowChain = IframeUtils.getSameOriginWindowChain(); const mouseMove = 'mousemove'; const mouseUp = 'mouseup'; const listenTo: (Document | ShadowRoot)[] = windowChain.map( (element) => element.window.document ); const shadowRoot = dom.getShadowRoot(initialElement); if (shadowRoot) { listenTo.unshift(shadowRoot); } for (const element of listenTo) { this._hooks.add( dom.addDisposableThrottledListener( element, mouseMove, (data: R) => { if (data.buttons !== initialButtons) { // Buttons state has changed in the meantime this.stopMonitoring(true); return; } this._mouseMoveCallback!(data); }, (lastEvent: R | null, currentEvent) => this._mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent) ) ); this._hooks.add( dom.addDisposableListener(element, mouseUp, (e: MouseEvent) => this.stopMonitoring(true) ) ); } if (IframeUtils.hasDifferentOriginAncestor()) { let lastSameOriginAncestor = windowChain[windowChain.length - 1]; // We might miss a mouse up if it happens outside the iframe // This one is for Chrome this._hooks.add( dom.addDisposableListener( lastSameOriginAncestor.window.document, 'mouseout', (browserEvent: MouseEvent) => { let e = new StandardMouseEvent(browserEvent); if (e.target.tagName.toLowerCase() === 'html') { this.stopMonitoring(true); } } ) ); // This one is for FF this._hooks.add( dom.addDisposableListener( lastSameOriginAncestor.window.document, 'mouseover', (browserEvent: MouseEvent) => { let e = new StandardMouseEvent(browserEvent); if (e.target.tagName.toLowerCase() === 'html') { this.stopMonitoring(true); } } ) ); // This one is for IE this._hooks.add( dom.addDisposableListener( lastSameOriginAncestor.window.document.body, 'mouseleave', (browserEvent: MouseEvent) => { this.stopMonitoring(true); } ) ); } } }