Tuesday 31 August 2021

preventDefault, stopPropagation, and cancelBubble not enough to prevent contextMenu from trying to open in Firefox mobile test view

If you open the test URL in Firefox desktop version 91.0.2 (Latest at the time) Windows 10 64-bit (Probably other versions too) and open the F12 menu, then click the Responsive Design Mode button (ctrl+shift+m... Aka the mobile view button) and hold left click to the left of the image, you'll see the number go up to about 25 - then stop and reset to zero, and trigger onCancel. My theory is that's how long it takes until the context menu is normally triggered.

If you try it in regular desktop view or Chrome's regular desktop view - it works as expected - it counts up to 100 (And beyond) until you let go, then onFinish gets triggered.

The full code is here: Code sandbox

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
    <script>
      window.addEventListener(
        "contextmenu",
        function (e) {
          // do something here...
          e.preventDefault && e.preventDefault();
          e.stopPropagation && e.stopPropagation();
          e.cancelBubble = true;
          return false;
        },
        false
      );

      const noContext = document.getElementById("ItemImage");

      noContext.addEventListener("contextmenu", (e) => {
        e.preventDefault && e.preventDefault();
        e.stopPropagation && e.stopPropagation();
        e.cancelBubble = true;
        return false;
      });

      function absorbEvent_(event) {
        var e = event;
        e.preventDefault && e.preventDefault();
        e.stopPropagation && e.stopPropagation();
        e.cancelBubble = true;
        return false;
      }

      function preventLongPressMenu(node) {
        node.ontouchstart = absorbEvent_;
        node.ontouchmove = absorbEvent_;
        node.ontouchend = absorbEvent_;
        node.ontouchcancel = absorbEvent_;
      }

      function init() {
        preventLongPressMenu(document.getElementById("ItemImage"));
      }
    </script>
    <style>
      /* https://www.arungudelli.com/tutorial/css/disable-text-selection-in-html-using-user-select-css-property/ */
      .disable-select {
        user-select: none; /* supported by Chrome and Opera */
        -webkit-user-select: none; /* Safari */
        -khtml-user-select: none; /* Konqueror HTML */
        -moz-user-select: none; /* Firefox */
        -ms-user-select: none; /* Internet Explorer/Edge */
      }
    </style>
  </head>

  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

App.js

import React, { useCallback, useRef, useState } from "react";

import "./styles.css";
import { useLongPress } from "use-long-press";
import Item from "./Item";

function App() {
  const [progress, setProgress] = useState(0);
  const progressTimer = useRef();
  function handleTime() {
    setProgress((prevState) => (prevState += 5));
  }

  const callback = useCallback((event) => {
    event.preventDefault && event.preventDefault();
    event.stopPropagation && event.stopPropagation();
    event.cancelBubble = true;
    console.log("long pressed!");
  }, []);

  const longPressEvent = useLongPress(callback, {
    onStart: (event) => {
      console.log("On Start");
      event.preventDefault && event.preventDefault();
      event.stopPropagation && event.stopPropagation();
      event.cancelBubble = true;
      progressTimer.current = setInterval(handleTime, 100);
    },
    onFinish: (event) => {
      console.log("On finish");
      event.preventDefault && event.preventDefault();
      event.stopPropagation && event.stopPropagation();
      event.cancelBubble = true;
      setProgress(0);
      clearInterval(progressTimer.current);
    },
    onCancel: (event) => {
      console.log("On cancel");
      event.preventDefault && event.preventDefault();
      event.stopPropagation && event.stopPropagation();
      event.cancelBubble = true;
      setProgress(0);
      clearInterval(progressTimer.current);
    },
    threshold: 2000,
    captureEvent: true,
    cancelOnMovement: false,
    detect: "both"
  });

  let content = (
    <div className="content-center">
      {progress}
      <Item
        events={longPressEvent}
        name="name"
        image="file.png"
        progress={progress}
      />
    </div>
  );

  return <React.Fragment>{content}</React.Fragment>;
}

export default App;

Item.js

import React from "react";
import "./Item.css";
import VerticalProgress from "./VerticalProgress";
import faker from "faker";

export default function Item(props) {
  return (
    <div className="flex justify-center w-full disable-select">
      <div
        className="float-left absolute z-50 w-full disable-select"
        style=
        {...props.events}
      >
        <div></div>
      </div>
      <VerticalProgress className="z-0" progress={props.progress} />
      <img
        className="z-0 disable-select"
        src={faker.image.image()}
        alt={props.name}
        height="200"
        id="ItemImage"
      />
    </div>
  );
}

My question is: How can I prevent onCancel from being called early in Firefox desktop mobile test mode? If it's happening there, it's bound to be happening in other browsers like Safari or actual Firefox mobile. Even if it isn't, this is a very much unintended side effect and is making testing mobile difficult.

Second question: What's causing the

IndexSizeError: Selection.getRangeAt: 0 is out of range

Errors?



from preventDefault, stopPropagation, and cancelBubble not enough to prevent contextMenu from trying to open in Firefox mobile test view

No comments:

Post a Comment