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

web: Do not scroll the page when mouse wheel is handled in AVM2 #19029

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 6 additions & 2 deletions core/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,8 +1374,12 @@ impl Player {
};
if let Some(target) = target {
let event = ClipEvent::MouseWheel { delta };
target.event_dispatch_to_avm2(context, event);
target.handle_clip_event(context, event);
if target.event_dispatch_to_avm2(context, event) == ClipEventResult::Handled {
player_event_handled = true;
}
if target.handle_clip_event(context, event) == ClipEventResult::Handled {
player_event_handled = true;
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;

[SWF(width="800", height="400")]
public class Test extends MovieClip {
private var text: TextField;

public function Test() {
var a = newSprite()
a.x = 0;
a.y = 0;
a.width = 200;
a.height = 400;
a.addEventListener(MouseEvent.MOUSE_WHEEL, consumeWheel1);
addChild(a);

var b = newSprite()
b.x = 400;
b.y = 0;
b.width = 200;
b.height = 400;
b.addEventListener(MouseEvent.MOUSE_WHEEL, consumeWheel2);
addChild(b);

var c = new TextField();
c.mouseWheelEnabled = true;
c.border = true;
c.x = 200;
c.y = 0;
c.width = 200;
c.height = 400;
c.multiline = true;
for (var i = 0; i < 100; ++ i) {
c.text += "line\n";
}
addChild(c);
text = c;

trace("Loaded!");
}

function consumeWheel1(event: MouseEvent) {
trace("Wheel consumed 1, vscroll: " + text.scrollV);
}

function consumeWheel2(event: MouseEvent) {
trace("Wheel consumed 2, vscroll: " + text.scrollV);
event.preventDefault();
}

function handleScroll(event: Event) {
trace("Scrolled");
}

private function newSprite(): Sprite {
var s:Sprite = new Sprite();
s.graphics.beginFill(0xFF00FF);
s.graphics.drawRect(0, 0, 200, 400);
s.graphics.endFill();
return s;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html>

<head>
<title>mouse_wheel_avm2</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>

<body>
<div>
<object type="application/x-shockwave-flash" data="test.swf" width="800" height="400" id="objectElement"></object>
</div>
</body>

</html>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
getTraceOutput,
hideHardwareAccelerationModal,
injectRuffleAndWait,
openTest,
playAndMonitor,
} from "../../utils.js";
import { expect, use } from "chai";
import chaiHtml from "chai-html";

use(chaiHtml);

async function scroll(
browser: WebdriverIO.Browser,
player: ChainablePromiseElement,
x: number,
y: number,
lines: number,
) {
const canvas = await player.shadow$("canvas");

return await browser.execute(
(element, x, y, lines) => {
const el = element as unknown as HTMLElement;
el.dispatchEvent(
new PointerEvent("pointermove", {
clientX: x,
clientY: y,
}),
);
return el.dispatchEvent(
new WheelEvent("wheel", {
deltaY: lines,
deltaMode: WheelEvent.DOM_DELTA_LINE,
cancelable: true,
}),
);
},
canvas,
x,
y,
lines,
);
}

describe("Mouse Wheel AVM2", () => {
it("load the test", async () => {
await openTest(browser, "integration_tests/mouse_wheel_avm2");
await injectRuffleAndWait(browser);
const player = await browser.$("<ruffle-object>");
await playAndMonitor(browser, player, "Loaded!\n");
await hideHardwareAccelerationModal(browser, player);
});

it("scroll the first clip", async () => {
const player = await browser.$("#objectElement");

expect(await scroll(browser, player, 100, 100, 1)).to.equal(false);

expect(await getTraceOutput(browser, player)).to.equal(
"Wheel consumed 1, vscroll: 1\n",
);
});

it("scroll the text field", async () => {
const player = await browser.$("#objectElement");

expect(await scroll(browser, player, 300, 100, 1)).to.equal(false);
});

it("scroll the second clip", async () => {
const player = await browser.$("#objectElement");

expect(await scroll(browser, player, 500, 100, 1)).to.equal(false);

expect(await getTraceOutput(browser, player)).to.equal(
"Wheel consumed 2, vscroll: 2\n",
);
});

it("scroll non-interactive content", async () => {
const player = await browser.$("#objectElement");

expect(await scroll(browser, player, 700, 100, 1)).to.equal(true);
});
});
25 changes: 25 additions & 0 deletions web/packages/selfhosted/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,28 @@ export function loadJsAPI(swf?: string) {
}
});
}

export async function closeAllModals(
browser: WebdriverIO.Browser,
player: ChainablePromiseElement,
) {
await browser.execute(
(modals) => {
for (const modal of modals) {
const m = modal as unknown as HTMLElement;
const cl = m.querySelector(".close-modal")! as HTMLElement;
cl.click();
}
},
await player.$$(".modal:not(.hidden)"),
);
}

export async function hideHardwareAccelerationModal(
browser: WebdriverIO.Browser,
player: ChainablePromiseElement,
) {
// Trigger it if not triggered yet
await player.moveTo();
await closeAllModals(browser, player);
}
4 changes: 3 additions & 1 deletion web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,9 @@ impl RuffleHandle {
_ => return,
};
let _ = instance.with_core_mut(|core| {
core.handle_event(PlayerEvent::MouseWheel { delta });
if core.handle_event(PlayerEvent::MouseWheel { delta }) {
js_event.prevent_default();
}
if core.should_prevent_scrolling() {
js_event.prevent_default();
}
Expand Down