Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix wheel/touch browser locking in IE and Safari #9333

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixtures/dom/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Header extends React.Component {
<option value="/pointer-events">Pointer Events</option>
<option value="/mouse-events">Mouse Events</option>
<option value="/selection-events">Selection Events</option>
<option value="/scroll">Scroll Events</option>
</select>
</label>
<label htmlFor="react_version">
Expand Down
72 changes: 72 additions & 0 deletions fixtures/dom/src/components/fixtures/scroll/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const React = global.React;

function wait(time) {
console.log('Blocking!');
var startTime = new Date().getTime();
var endTime = startTime + time;
while (new Date().getTime() < endTime) {
// wait for it...
}
console.log('Not blocking!');
}

var scrollable = {
width: 300,
height: 200,
overflowY: 'auto',
margin: '0 auto',
background: '#ededed',
};

class ScrollFixture extends React.Component {
componentDidMount() {
// jank up the main thread!
this.jank = setInterval(() => wait(3000), 4000);
}

componentWillUnmount() {
clearInterval(this.jank);
}

onWheel() {
console.log('wheel');
}

onTouchStart() {
console.log('touch start');
}

onTouchMove() {
console.log('touch move');
}

render() {
let listItems = [];

// This is to produce a long enough page to allow for scrolling
for (var i = 0; i < 50; i++) {
listItems.push(<li key={i}>List item #{i + 1}</li>);
}

return (
<section>
<h2>Scroll Testing</h2>
<p>
Mouse wheel, track pad scroll, and touch events should not be blocked
by JS execution in IE Edge, Safari, and Firefox.
</p>
<div
style={scrollable}
onTouchStart={this.onTouchStart}
onTouchMove={this.onTouchMove}
onWheel={this.onWheel}>
<h2>I am scrollable!</h2>
<ul>{listItems}</ul>
</div>
<ul>{listItems}</ul>
</section>
);
}
}

export default ScrollFixture;
124 changes: 124 additions & 0 deletions packages/react-dom/src/__tests__/EventDispatchOrder-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict';

const React = require('react');
const ReactDOM = require('react-dom');

describe('EventDispatchOrder', () => {
let container;

// The order we receive here is not ideal since it is expected that the
// capture listener fire before all bubble listeners. Other React apps
// might depend on this.
//
// @see https://github.com/facebook/react/pull/12919#issuecomment-395224674
const expectedOrder = [
'document capture',
'inner capture',
'inner bubble',
'outer capture',
'outer bubble',
'document bubble',
];

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
container.remove();
});

function produceDispatchOrder(event, reactEvent) {
const eventOrder = [];
const track = tag => () => eventOrder.push(tag);
const outerRef = React.createRef();
const innerRef = React.createRef();

function OuterReactApp() {
return React.createElement('div', {
ref: outerRef,
[reactEvent]: track('outer bubble'),
[reactEvent + 'Capture']: track('outer capture'),
});
}

function InnerReactApp() {
return React.createElement('div', {
ref: innerRef,
[reactEvent]: track('inner bubble'),
[reactEvent + 'Capture']: track('inner capture'),
});
}

ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(<OuterReactApp />, container);

ReactDOM.unmountComponentAtNode(outerRef.current);
ReactDOM.render(<InnerReactApp />, outerRef.current);

const trackDocBubble = track('document bubble');
const trackDocCapture = track('document capture');

document.addEventListener(event, trackDocBubble);
document.addEventListener(event, trackDocCapture, true);

innerRef.current.dispatchEvent(new Event(event, {bubbles: true}));

document.removeEventListener(event, trackDocBubble);
document.removeEventListener(event, trackDocCapture, true);

return eventOrder;
}

it('dispatches standard events in the correct order', () => {
expect(produceDispatchOrder('click', 'onClick')).toEqual(expectedOrder);
});

// Scroll and Wheel are attached as captured events directly to the element
describe('scrolling events', () => {
it('dispatches scroll events in the correct order', () => {
expect(produceDispatchOrder('scroll', 'onScroll')).toEqual(expectedOrder);
});

it('dispatches scroll events in the correct order', () => {
expect(produceDispatchOrder('wheel', 'onWheel')).toEqual(expectedOrder);
});
});

// Touch events are attached as bubbled events directly to the element
describe('touch events', () => {
it('dispatches touchstart in the correct order', () => {
expect(produceDispatchOrder('touchstart', 'onTouchStart')).toEqual(
expectedOrder,
);
});

it('dispatches touchend in the correct order', () => {
expect(produceDispatchOrder('touchend', 'onTouchEnd')).toEqual(
expectedOrder,
);
});

it('dispatches touchmove in the correct order', () => {
expect(produceDispatchOrder('touchmove', 'onTouchMove')).toEqual(
expectedOrder,
);
});

it('dispatches touchmove in the correct order', () => {
expect(produceDispatchOrder('touchcancel', 'onTouchCancel')).toEqual(
expectedOrder,
);
});
});
});
Loading