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 mitmweb splitter becoming drag and drop #6492

Merged
merged 4 commits into from Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## Unreleased: mitmproxy next

* Fix `mitmweb` splitter becoming drag and drop.
([#6492](https://github.com/mitmproxy/mitmproxy/pull/6492), @xBZZZZ)


## 14 November 2023: mitmproxy 10.1.5
Expand Down
183 changes: 125 additions & 58 deletions web/src/js/__tests__/components/common/SplitterSpec.tsx
Expand Up @@ -4,87 +4,155 @@ import renderer from "react-test-renderer";
import Splitter from "../../../components/common/Splitter";
import TestUtils from "react-dom/test-utils";

describe("Splitter Component", () => {
it("should render correctly", () => {
let splitter = renderer.create(<Splitter />),
describe.each([
["", ""],
["x", "X"],
["y", "Y"],
])("Splitter Component", (axisLower, axisUpper) => {
if (axisLower === "") {
it("should render correctly with default (x) axis", () => {
const splitter = renderer.create(<Splitter />),
tree = splitter.toJSON();
expect(tree).toMatchInlineSnapshot(`
<div
className="splitter splitter-x"
>
<div
onLostPointerCapture={[Function]}
onPointerDown={[Function]}
onPointerMove={[Function]}
/>
</div>
`);
});
return;
}

it("should render correctly with specified axis", () => {
const splitter = renderer.create(<Splitter axis={axisLower} />),
tree = splitter.toJSON();
expect(tree).toMatchSnapshot();
expect(tree).toMatchInlineSnapshot(`
<div
className="splitter splitter-${axisLower}"
>
<div
onLostPointerCapture={[Function]}
onPointerDown={[Function]}
onPointerMove={[Function]}
/>
</div>
`);
});

let splitter = TestUtils.renderIntoDocument(<Splitter />),
const splitter = TestUtils.renderIntoDocument(
<Splitter axis={axisLower} />
),
dom = ReactDOM.findDOMNode(splitter),
previousElementSibling = {
offsetHeight: 0,
offsetWidth: 0,
offsetWidth: 300,
offsetHeight: 500,
style: { flex: "" },
},
nextElementSibling = {
style: { flex: "" },
};

it("should handle mouseDown ", () => {
window.addEventListener = jest.fn();
splitter.onMouseDown({ pageX: 1, pageY: 2 });
expect(splitter.state.startX).toEqual(1);
expect(splitter.state.startY).toEqual(2);
expect(window.addEventListener).toBeCalledWith(
"mousemove",
splitter.onMouseMove
);
expect(window.addEventListener).toBeCalledWith(
"mouseup",
splitter.onMouseUp
);
expect(window.addEventListener).toBeCalledWith(
"dragend",
splitter.onDragEnd
);
Object.defineProperties(dom, {
previousElementSibling: { value: previousElementSibling },
nextElementSibling: { value: nextElementSibling },
});
dom.firstElementChild.setPointerCapture = jest.fn();

it("should handle dragEnd", () => {
window.removeEventListener = jest.fn();
splitter.onDragEnd();
expect(dom.style.transform).toEqual("");
expect(window.removeEventListener).toBeCalledWith(
"dragend",
splitter.onDragEnd
);
expect(window.removeEventListener).toBeCalledWith(
"mouseup",
splitter.onMouseUp
);
expect(window.removeEventListener).toBeCalledWith(
"mousemove",
splitter.onMouseMove
it("should handle pointerdown", () => {
const e = {
pageX: 13,
pageY: 22,
pointerId: -4618,
target: dom.firstElementChild,
};
expect(splitter.state.dragPointer).toEqual(0.1);
splitter.onPointerDown(e);
expect(e.target.setPointerCapture).toBeCalledWith(-4618);
expect(splitter.state.dragPointer).toEqual(-4618);
expect(splitter.state.startPos).toEqual(e[`page${axisUpper}`]);
});

it("should handle pointermove", () => {
const e = {
pageX: 62,
pageY: 21,
pointerId: -4618,
target: dom.firstElementChild,
};
splitter.onPointerMove(e);
expect(dom.style.transform).toEqual(
axisLower === "x"
? `translateX(${62 - 13}px)`
: `translateY(${21 - 22}px)`
);
});

it("should handle mouseUp", () => {
Object.defineProperty(dom, "previousElementSibling", {
value: previousElementSibling,
});
Object.defineProperty(dom, "nextElementSibling", {
value: nextElementSibling,
});
splitter.onMouseUp({ pageX: 3, pageY: 4 });
expect(splitter.state.applied).toBeTruthy();
it("should handle lostpointercapture", () => {
const e = {
pageX: 56,
pageY: 82,
pointerId: -4618,
target: dom.firstElementChild,
};
splitter.onLostPointerCapture(e);
expect(splitter.state.dragPointer).toEqual(0.1);
expect(dom.style.transform).toEqual("");
expect(previousElementSibling.style.flex).toEqual(
`0 0 ${axisLower === "x" ? 300 + 56 - 13 : 500 + 82 - 22}px`
);
expect(nextElementSibling.style.flex).toEqual("1 1 auto");
expect(previousElementSibling.style.flex).toEqual("0 0 2px");
});

it("should handle mouseMove", () => {
splitter.onMouseMove({ pageX: 10, pageY: 10 });
expect(dom.style.transform).toEqual("translate(9px, 0px)");
it("should not resize previousElementSibling negative", () => {
const e = {
pageX: 56,
pageY: 82,
pointerId: 47,
target: dom.firstElementChild,
};
splitter.onPointerDown(e);
e[`page${axisUpper}`] = -1234;
splitter.onLostPointerCapture(e);
expect(previousElementSibling.style.flex).toEqual("0 0 0px");
});

let splitterY = TestUtils.renderIntoDocument(<Splitter axis="y" />);
splitterY.onMouseMove({ pageX: 10, pageY: 10 });
expect(ReactDOM.findDOMNode(splitterY).style.transform).toEqual(
"translate(0px, 10px)"
it("should ignore other pointers", () => {
splitter.onPointerDown({
pageX: 70,
pageY: 60,
pointerId: 47,
target: dom.firstElementChild,
});
splitter.onPointerDown({
pageX: 70,
pageY: 60,
pointerId: 46,
target: dom.firstElementChild,
});
expect(splitter.state.dragPointer).toEqual(47);
splitter.onPointerMove({ pageX: 75, pageY: 55, pointerId: 46 });
expect(dom.style.transform).toEqual("");
splitter.onPointerMove({
pageX: 74,
pageY: 54,
pointerId: 47,
target: dom.firstElementChild,
});
splitter.onLostPointerCapture({ pageX: 76, pageY: 56, pointerId: 46 });
expect(dom.style.transform).toEqual(
axisLower === "x"
? `translateX(${74 - 70}px)`
: `translateY(${54 - 60}px)`
);
});

it("should handle resize", () => {
let x = jest.spyOn(window, "setTimeout");
const x = jest.spyOn(window, "setTimeout");
splitter.onResize();
expect(x).toHaveBeenCalled();
});
Expand All @@ -99,7 +167,6 @@ describe("Splitter Component", () => {
it("should handle reset", () => {
splitter.reset(false);
expect(splitter.state.applied).toBeFalsy();

expect(splitter.reset(true)).toEqual(undefined);
});
});

This file was deleted.

88 changes: 41 additions & 47 deletions web/src/js/components/common/Splitter.tsx
Expand Up @@ -4,8 +4,9 @@ import classnames from "classnames";

type SplitterState = {
applied: boolean;
startX: number;
startY: number;
startPos: number;
// .dragPointer === 0.1 means not dragging
dragPointer: number;
};

type SplitterProps = {
Expand All @@ -17,62 +18,51 @@ export default class Splitter extends Component<SplitterProps, SplitterState> {

constructor(props, context) {
super(props, context);

this.state = { applied: false, startX: 0, startY: 0 };

this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
}

onMouseDown(e) {
this.setState({ startX: e.pageX, startY: e.pageY });

window.addEventListener("mousemove", this.onMouseMove);
window.addEventListener("mouseup", this.onMouseUp);
// Occasionally, only a dragEnd event is triggered, but no mouseUp.
window.addEventListener("dragend", this.onDragEnd);
this.state = { applied: false, startPos: 0, dragPointer: 0.1 };
this.onLostPointerCapture = this.onLostPointerCapture.bind(this);
this.onPointerDown = this.onPointerDown.bind(this);
this.onPointerMove = this.onPointerMove.bind(this);
}

onDragEnd() {
ReactDOM.findDOMNode(this).style.transform = "";

window.removeEventListener("dragend", this.onDragEnd);
window.removeEventListener("mouseup", this.onMouseUp);
window.removeEventListener("mousemove", this.onMouseMove);
onPointerDown(e) {
if (this.state.dragPointer !== 0.1) {
return;
}
e.target.setPointerCapture(e.pointerId);
this.setState({
startPos: this.props.axis === "x" ? e.pageX : e.pageY,
dragPointer: e.pointerId,
});
}

onMouseUp(e) {
this.onDragEnd();

const node = ReactDOM.findDOMNode(this);
const prev = node.previousElementSibling;

let flexBasis = prev.offsetHeight + e.pageY - this.state.startY;

if (this.props.axis === "x") {
flexBasis = prev.offsetWidth + e.pageX - this.state.startX;
onLostPointerCapture(e) {
if (this.state.dragPointer !== e.pointerId) {
return;
}
const node = e.target.parentNode;
const prev = node.previousElementSibling;

prev.style.flex = `0 0 ${Math.max(0, flexBasis)}px`;
node.style.transform = "";
prev.style.flex = `0 0 ${Math.max(
0,
(this.props.axis === "x"
? prev.offsetWidth + e.pageX
: prev.offsetHeight + e.pageY) - this.state.startPos
)}px`;
node.nextElementSibling.style.flex = "1 1 auto";

this.setState({ applied: true });
this.setState({ applied: true, dragPointer: 0.1 });
this.onResize();
}

onMouseMove(e) {
let dX = 0;
let dY = 0;
if (this.props.axis === "x") {
dX = e.pageX - this.state.startX;
} else {
dY = e.pageY - this.state.startY;
onPointerMove(e) {
if (this.state.dragPointer !== e.pointerId) {
return;
}
ReactDOM.findDOMNode(
this
).style.transform = `translate(${dX}px, ${dY}px)`;
e.target.parentNode.style.transform =
this.props.axis === "x"
? `translateX(${e.pageX - this.state.startPos}px)`
: `translateY(${e.pageY - this.state.startPos}px)`;
}

onResize() {
Expand Down Expand Up @@ -116,7 +106,11 @@ export default class Splitter extends Component<SplitterProps, SplitterState> {
this.props.axis === "x" ? "splitter-x" : "splitter-y"
)}
>
<div onMouseDown={this.onMouseDown} draggable="true" />
<div
onLostPointerCapture={this.onLostPointerCapture}
onPointerDown={this.onPointerDown}
onPointerMove={this.onPointerMove}
/>
</div>
);
}
Expand Down