atlassian / react-beautiful-dnd

Beautiful and accessible drag and drop for lists with React
https://react-beautiful-dnd.netlify.app
Other
33.25k stars 2.53k forks source link

CSP workaround assumes a server is generating HTML on request #1768

Open jquense opened 4 years ago

jquense commented 4 years ago

We are running into an issue using RBD, because of the style inlining. The nonce solution is only feasible if you actually have a web server generating HTML. We don't, assets are all built and then pushed to S3 and served statically.

This seems like a really onerous requirement for users of RBD, any alternatives?

jquense commented 4 years ago

We are looking into patching this locally but would ideally like to upstream our work if possible.

Can someone fill me in on why the current approach was taken? It seems like new style elements are created for every DragDropContext presumably so that active styles only apply to the drag/dropables of the active context? This could still be accomplished via static css though. It'd be nice to have the option to disable the style marshaling and manually include a stylesheet, Would all be open to that?

keshikashviligio commented 4 years ago

Some here, Any news?

fbrooks commented 4 years ago

We are having the same issue

jquense commented 4 years ago

We are using patch-package at the moment to solve this. Below is our patch. NOTE this isn't a complete solution which is why i haven't PR'd the change here. It basically assumes you only have a single DND context on any given page, something that is true for our app but may not be true for yours

diff --git a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js
index 20c6acf..0a7e4f9 100644
--- a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js
+++ b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js
@@ -4892,152 +4892,41 @@ var scrollContainer = {
   contextId: prefix$1 + "-scroll-container-context-id"
 };

-var makeGetSelector = function makeGetSelector(context) {
-  return function (attribute) {
-    return "[" + attribute + "=\"" + context + "\"]";
-  };
-};
-
-var getStyles = function getStyles(rules, property) {
-  return rules.map(function (rule) {
-    var value = rule.styles[property];
-
-    if (!value) {
-      return '';
-    }
-
-    return rule.selector + " { " + value + " }";
-  }).join(' ');
-};
-
-var noPointerEvents = 'pointer-events: none;';
-var getStyles$1 = (function (contextId) {
-  var getSelector = makeGetSelector(contextId);
+var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React.useLayoutEffect : React.useEffect;

-  var dragHandle$1 = function () {
-    var grabCursor = "\n      cursor: -webkit-grab;\n      cursor: grab;\n    ";
-    return {
-      selector: getSelector(dragHandle.contextId),
-      styles: {
-        always: "\n          -webkit-touch-callout: none;\n          -webkit-tap-highlight-color: rgba(0,0,0,0);\n          touch-action: manipulation;\n        ",
-        resting: grabCursor,
-        dragging: noPointerEvents,
-        dropAnimating: grabCursor
-      }
-    };
-  }();
-
-  var draggable$1 = function () {
-    var transition = "\n      transition: " + transitions.outOfTheWay + ";\n    ";
-    return {
-      selector: getSelector(draggable.contextId),
-      styles: {
-        dragging: transition,
-        dropAnimating: transition,
-        userCancel: transition
-      }
-    };
-  }();
-
-  var droppable$1 = {
-    selector: getSelector(droppable.contextId),
-    styles: {
-      always: "overflow-anchor: none;"
-    }
-  };
-  var body = {
-    selector: 'body',
-    styles: {
-      dragging: "\n        cursor: grabbing;\n        cursor: -webkit-grabbing;\n        user-select: none;\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        overflow-anchor: none;\n      "
-    }
-  };
-  var rules = [draggable$1, dragHandle$1, droppable$1, body];
-  return {
-    always: getStyles(rules, 'always'),
-    resting: getStyles(rules, 'resting'),
-    dragging: getStyles(rules, 'dragging'),
-    dropAnimating: getStyles(rules, 'dropAnimating'),
-    userCancel: getStyles(rules, 'userCancel')
-  };
-});
-
-var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect : useEffect;
-
-var getHead = function getHead() {
-  var head = document.querySelector('head');
-  !head ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot find the head to append a style to') : invariant(false) : void 0;
-  return head;
-};
-
-var createStyleEl = function createStyleEl(nonce) {
-  var el = document.createElement('style');
-
-  if (nonce) {
-    el.setAttribute('nonce', nonce);
-  }
+function useStyleMarshal(contextId, nonce) {

-  el.type = 'text/css';
-  return el;
-};
+  const setState = useCallback(
+    memoizeOne(function(state) {
+      document.body.dataset.rbdActionState = state
+    }),
+    [],
+  );

-function useStyleMarshal(contextId, nonce) {
-  var styles = useMemo(function () {
-    return getStyles$1(contextId);
-  }, [contextId]);
-  var alwaysRef = useRef(null);
-  var dynamicRef = useRef(null);
-  var setDynamicStyle = useCallback(memoizeOne(function (proposed) {
-    var el = dynamicRef.current;
-    !el ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot set dynamic style element if it is not set') : invariant(false) : void 0;
-    el.textContent = proposed;
-  }), []);
-  var setAlwaysStyle = useCallback(function (proposed) {
-    var el = alwaysRef.current;
-    !el ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot set dynamic style element if it is not set') : invariant(false) : void 0;
-    el.textContent = proposed;
-  }, []);
   useIsomorphicLayoutEffect(function () {
-    !(!alwaysRef.current && !dynamicRef.current) ? process.env.NODE_ENV !== "production" ? invariant(false, 'style elements already mounted') : invariant(false) : void 0;
-    var always = createStyleEl(nonce);
-    var dynamic = createStyleEl(nonce);
-    alwaysRef.current = always;
-    dynamicRef.current = dynamic;
-    always.setAttribute(prefix$1 + "-always", contextId);
-    dynamic.setAttribute(prefix$1 + "-dynamic", contextId);
-    getHead().appendChild(always);
-    getHead().appendChild(dynamic);
-    setAlwaysStyle(styles.always);
-    setDynamicStyle(styles.resting);
+    setState('resting')
     return function () {
-      var remove = function remove(ref) {
-        var current = ref.current;
-        !current ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot unmount ref as it is not set') : invariant(false) : void 0;
-        getHead().removeChild(current);
-        ref.current = null;
-      };
-
-      remove(alwaysRef);
-      remove(dynamicRef);
+      setState('')
     };
-  }, [nonce, setAlwaysStyle, setDynamicStyle, styles.always, styles.resting, contextId]);
+  }, [setState]);
+
   var dragging = useCallback(function () {
-    return setDynamicStyle(styles.dragging);
-  }, [setDynamicStyle, styles.dragging]);
+    setState('dragging');
+  }, [setState]);
+
   var dropping = useCallback(function (reason) {
     if (reason === 'DROP') {
-      setDynamicStyle(styles.dropAnimating);
+      setState('dropAnimating');
       return;
     }

-    setDynamicStyle(styles.userCancel);
-  }, [setDynamicStyle, styles.dropAnimating, styles.userCancel]);
+    setState('userCancel');
+  }, [setState]);
+
   var resting = useCallback(function () {
-    if (!dynamicRef.current) {
-      return;
-    }
+    setState('resting');
+  }, [setState]);

-    setDynamicStyle(styles.resting);
-  }, [setDynamicStyle, styles.resting]);
   var marshal = useMemo(function () {
     return {
       dragging: dragging,