"use strict"; const { MockRegistrar } = ChromeUtils.importESModule( "resource://testing-common/MockRegistrar.sys.mjs" ); const ATTACKER_URL = "https://example.com/browser/dom/events/test/file_empty.html"; let gMockDragService; // Synthesize mouse events from the content process to trigger a drag, // simulating an arbitrary drag invocation from the content process without // any valid drag state in the parent process. async function triggerInvalidDragFromContent(browser, marker) { await SpecialPowers.spawn(browser, [marker], async marker => { let source = content.document.createElement("div"); source.draggable = true; source.style.cssText = "width: 100px; height: 100px; background-color: green"; source.addEventListener("dragstart", event => { event.dataTransfer.setData("text/plain", marker); event.dataTransfer.effectAllowed = "copy"; }); content.document.body.append(source); let dragStartPromise = new Promise(resolve => { source.addEventListener("dragstart", resolve, { once: true }); }); let event = { isSynthesized: false, button: 0, buttons: 1 }; event.type = "mousedown"; EventUtils.synthesizeMouse(source, 1, 1, event, content); event.type = "mousemove"; EventUtils.synthesizeMouse(source, 3, 3, event, content); EventUtils.synthesizeMouse(source, 31, 31, event, content); // Intentionally not synthesizing a mouseup event to avoid drag session // being terminated from content process. await dragStartPromise; }); // Wait for the drag to be processed. await SpecialPowers.spawn(browser, [], async () => { return new Promise(resolve => { SpecialPowers.executeSoon(resolve); }); }); } async function triggerChromeDrag(marker) { let node = document.createXULElement("toolbarbutton"); node.setAttribute("label", "Chrome drag target"); node.style.cssText = "min-width: 180px; min-height: 40px; background: orange"; document.getElementById("navigator-toolbox").append(node); registerCleanupFunction(() => node.remove()); node.addEventListener( "dragstart", event => { event.dataTransfer.setData("text/plain", marker); event.dataTransfer.setData("text/html", `
${marker}
`); event.dataTransfer.effectAllowed = "copy"; }, { once: true } ); let event = { isSynthesized: false, button: 0, buttons: 1 }; event.type = "mousedown"; EventUtils.synthesizeMouse(node, 1, 1, event, window); event.type = "mousemove"; EventUtils.synthesizeMouse(node, 3, 3, event, window); EventUtils.synthesizeMouse(node, 31, 31, event, window); let session = gMockDragService.getCurrentSession(window); ok(session, "Should have a drag session"); let dataTransferSnapshot = { types: Array.from(session?.dataTransfer.types), plainText: session?.dataTransfer.getData("text/plain"), htmlText: session?.dataTransfer.getData("text/html"), }; session?.endDragSession(true, 0); event.type = "mouseup"; event.buttons = 0; // We intentionally turn off this a11y check, because the node is added for // testing purposes only, we don't need to do a11y checks on it. AccessibilityUtils.setEnv({ focusableRule: false }); EventUtils.synthesizeMouse(node, 31, 31, event, window); AccessibilityUtils.resetEnv(); return dataTransferSnapshot; } add_setup(function setupMockDragService() { let oldDragService = Cc["@mozilla.org/widget/dragservice;1"].getService( Ci.nsIDragService ); let controller = oldDragService.getMockDragController(); let cid = MockRegistrar.register( "@mozilla.org/widget/dragservice;1", controller.mockDragService ); gMockDragService = controller.mockDragService; gMockDragService.neverAllowSessionIsSynthesizedForTests = true; registerCleanupFunction(() => MockRegistrar.unregister(cid)); }); add_task(async function test_invalid_remote_drag() { let attacker = await BrowserTestUtils.openNewForegroundTab( gBrowser, ATTACKER_URL ); registerCleanupFunction(async () => { BrowserTestUtils.removeTab(attacker); }); let contentMarker = `Content-${crypto.randomUUID()}`; await triggerInvalidDragFromContent(attacker.linkedBrowser, contentMarker); let chromeMarker = `Chrome-${crypto.randomUUID()}`; let dataTransferSnapshot = await triggerChromeDrag(chromeMarker); info(`dataTransferSnapshot=${JSON.stringify(dataTransferSnapshot)}`); is( dataTransferSnapshot.plainText, chromeMarker, "Check plain text data in parent drag session" ); is( dataTransferSnapshot.htmlText, `
${chromeMarker}
`, "Check HTML text data in parent drag session" ); SimpleTest.isDeeply( dataTransferSnapshot.types, ["text/plain", "text/html"], "Check data types in parent drag session" ); });