Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Commit

Permalink
Take over enhancements to render Draft.js in an iframe (#1938)
Browse files Browse the repository at this point in the history
Summary:
**Summary**

Takes over and applies requested PR feedback to #1877 by haikyuu.

Refer to original PR for context and discussion.

**Test Plan**

Run some manual smoke tests on the editor running in an iframe as follows:

```
yarn
python3 -m http.server 8080 .
open http://localhost:8080/examples/draft-0-10-0/iframe/iframe.html
```
Pull Request resolved: #1938

Reviewed By: claudiopro

Differential Revision: D13137413

Pulled By: jack-arms

fbshipit-source-id: efcdbabc7d8d2aff4fbebc8b06c22d57756ebc12
  • Loading branch information
Claudio Procida authored and facebook-github-bot committed Nov 19, 2019
1 parent 8f77aa3 commit dceddf5
Show file tree
Hide file tree
Showing 35 changed files with 358 additions and 107 deletions.
2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.js.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.min.js.json

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const getDefaultKeyBinding = require('getDefaultKeyBinding');
const getScrollPosition = require('getScrollPosition');
const gkx = require('gkx');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');
const nullthrows = require('nullthrows');

const isIE = UserAgent.isBrowser('IE');
Expand Down Expand Up @@ -457,7 +458,13 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
* ie9-beta-minor-change-list.aspx
*/
if (isIE) {
document.execCommand('AutoUrlDetect', false, false);
// editor can be null after mounting
// https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback
if (!this.editor) {
global.execCommand('AutoUrlDetect', false, false);
} else {
this.editor.ownerDocument.execCommand('AutoUrlDetect', false, false);
}
}
}

Expand Down Expand Up @@ -492,10 +499,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
const scrollParent = Style.getScrollParent(editorNode);
const {x, y} = scrollPosition || getScrollPosition(scrollParent);

invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');

editorNode.focus();

Expand All @@ -519,10 +523,10 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {

blur: () => void = (): void => {
const editorNode = this.editor;
invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
if (!editorNode) {
return;
}
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');
editorNode.blur();
};

Expand Down
6 changes: 2 additions & 4 deletions src/component/contents/DraftEditorBlock.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');
const nullthrows = require('nullthrows');

const SCROLL_BUFFER = 10;
Expand Down Expand Up @@ -121,10 +122,7 @@ class DraftEditorBlock extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const pOffset = scrollParent.offsetTop + scrollParent.offsetHeight;
const scrollBottom = pOffset + scrollPosition.y;
Expand Down
11 changes: 7 additions & 4 deletions src/component/contents/DraftEditorTextNode.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
* @flow
* @emails oncall+draft_js
*/

Expand All @@ -15,6 +15,7 @@ const React = require('React');
const UserAgent = require('UserAgent');

const invariant = require('invariant');
const isElement = require('isElement');

// In IE, spans with <br> tags render as two newlines. By rendering a span
// with only a newline character, we can be sure to render a single line.
Expand Down Expand Up @@ -81,11 +82,13 @@ class DraftEditorTextNode extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
const node = this._node;
const shouldBeNewline = nextProps.children === '';
invariant(node instanceof Element, 'node is not an Element');

invariant(isElement(node), 'node is not an Element');
const elementNode: Element = (node: any);
if (shouldBeNewline) {
return !isNewline(node);
return !isNewline(elementNode);
}
return node.textContent !== nextProps.children;
return elementNode.textContent !== nextProps.children;
}

componentDidMount(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const Immutable = require('immutable');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

const SCROLL_BUFFER = 10;

Expand Down Expand Up @@ -251,11 +252,9 @@ class DraftEditorBlockNode extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const htmlBlockNode: HTMLElement = (blockNode: any);
const blockBottom = htmlBlockNode.offsetHeight + htmlBlockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
if (scrollDelta > 0) {
Expand Down
6 changes: 4 additions & 2 deletions src/component/handlers/composition/DOMObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
const UserAgent = require('UserAgent');

const findAncestorOffsetKey = require('findAncestorOffsetKey');
const getWindowForNode = require('getWindowForNode');
const Immutable = require('immutable');
const invariant = require('invariant');
const nullthrows = require('nullthrows');
Expand Down Expand Up @@ -45,8 +46,9 @@ class DOMObserver {
constructor(container: HTMLElement) {
this.container = container;
this.mutations = Map();
if (window.MutationObserver && !USE_CHAR_DATA) {
this.observer = new window.MutationObserver(mutations =>
const containerWindow = getWindowForNode(container);
if (containerWindow.MutationObserver && !USE_CHAR_DATA) {
this.observer = new containerWindow.MutationObserver(mutations =>
this.registerMutations(mutations),
);
} else {
Expand Down
9 changes: 6 additions & 3 deletions src/component/handlers/drag/DraftEditorDragHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ReactDOM = require('ReactDOM');
const findAncestorOffsetKey = require('findAncestorOffsetKey');
const getTextContentFromFiles = require('getTextContentFromFiles');
const getUpdatedSelectionState = require('getUpdatedSelectionState');
const getWindowForNode = require('getWindowForNode');
const isEventHandled = require('isEventHandled');
const nullthrows = require('nullthrows');

Expand All @@ -38,8 +39,10 @@ function getSelectionForEvent(
/* $FlowFixMe(>=0.68.0 site=www,mobile) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
if (typeof document.caretRangeFromPoint === 'function') {
const dropRange = document.caretRangeFromPoint(event.x, event.y);
const {ownerDocument} = event.currentTarget;

if (typeof ownerDocument.caretRangeFromPoint === 'function') {
const dropRange = ownerDocument.caretRangeFromPoint(event.x, event.y);
node = dropRange.startContainer;
offset = dropRange.startOffset;
} else if (event.rangeParent) {
Expand Down Expand Up @@ -142,7 +145,7 @@ function endDrag(editor) {
const editorNode = ReactDOM.findDOMNode(editor);
if (editorNode) {
const mouseUpEvent = new MouseEvent('mouseup', {
view: window,
view: getWindowForNode(editorNode),
bubbles: true,
cancelable: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const getEditorState = (text: string = 'Arsenal') => {

const getDraftEditor = (obj): DraftEditor => (obj: any);

const getInputEvent = (data): SyntheticInputEvent<> =>
const getInputEvent = (data): SyntheticInputEvent<HTMLElement> =>
({
data,
preventDefault: jest.fn(),
Expand Down
11 changes: 7 additions & 4 deletions src/component/handlers/edit/__tests__/editOnBlur-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+draft_js
* @flow strict-local
* @format
*/

Expand All @@ -29,8 +30,8 @@ const getEditorState = (text: string = 'Arsenal') => {
);
};

const getBlurEvent = data => ({
data,
const getBlurEvent = currentTarget => ({
currentTarget,
});

function withGlobalGetSelectionAs(getSelectionValue = {}, callback) {
Expand Down Expand Up @@ -68,7 +69,8 @@ test('editor removes selection on blur (default behaviour)', () => {
editor: editorNode,
};

onBlur(editor, getBlurEvent());
// $FlowExpectedError
onBlur(editor, getBlurEvent(editorNode));

expect(globalSelection.removeAllRanges).toHaveBeenCalledTimes(1);
});
Expand Down Expand Up @@ -97,7 +99,8 @@ test('editor preserves selection on blur', () => {
editor: editorNode,
};

onBlur(editor, getBlurEvent());
// $FlowExpectedError
onBlur(editor, getBlurEvent(editorNode));

expect(globalSelection.removeAllRanges).toHaveBeenCalledTimes(0);
});
Expand Down
26 changes: 16 additions & 10 deletions src/component/handlers/edit/__tests__/editOnInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,24 @@ function withGlobalGetSelectionAs(getSelectionValue = {}, callback) {
}

test('restoreEditorDOM and keyCommandPlainBackspace are NOT called when the `inputType` is not from a backspace press', () => {
const inputEvent = {
nativeEvent: {inputType: 'insetText'},
};
const anchorNodeText = 'react draftjs';
const globalSelection = {
anchorNode: document.createTextNode(anchorNodeText),
};
withGlobalGetSelectionAs(globalSelection, () => {
const editorState = getEditorState(anchorNodeText);

const editorNode = document.createElement('div');
const editor = {
_latestEditorState: editorState,
props: {},
update: jest.fn(),
restoreEditorDOM: jest.fn(),
editor: editorNode,
};

const inputEvent = {
nativeEvent: {inputType: 'insetText'},
currentTarget: editorNode,
};

// $FlowExpectedError
Expand All @@ -71,23 +74,26 @@ test('restoreEditorDOM and keyCommandPlainBackspace are NOT called when the `inp
});

test('restoreEditorDOM and keyCommandPlainBackspace are called when backspace is pressed', () => {
const inputEvent = {
// When Backspace is pressed and input-type is supported, an event with
// inputType === 'deleteContentBackward' is triggered by the browser.
nativeEvent: {inputType: 'deleteContentBackward'},
};
const anchorNodeText = 'react draftjs';
const globalSelection = {
anchorNode: document.createTextNode(anchorNodeText),
};
withGlobalGetSelectionAs(globalSelection, () => {
const editorState = getEditorState(anchorNodeText);

const editorNode = document.createElement('div');
const editor = {
_latestEditorState: editorState,
props: {},
update: jest.fn(),
restoreEditorDOM: jest.fn(),
editor: editorNode,
};

const inputEvent = {
// When Backspace is pressed and input-type is supported, an event with
// inputType === 'deleteContentBackward' is triggered by the browser.
nativeEvent: {inputType: 'deleteContentBackward'},
currentTarget: editorNode,
};

// $FlowExpectedError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const removeTextWithStrategy = require('removeTextWithStrategy');

function keyCommandBackspaceToStartOfLine(
editorState: EditorState,
e: SyntheticKeyboardEvent<HTMLElement>,
): EditorState {
const afterRemoval = removeTextWithStrategy(
editorState,
Expand All @@ -28,8 +29,8 @@ function keyCommandBackspaceToStartOfLine(
if (selection.isCollapsed() && selection.getAnchorOffset() === 0) {
return moveSelectionBackward(strategyState, 1);
}

const domSelection = global.getSelection();
const {ownerDocument} = e.currentTarget;
const domSelection = ownerDocument.defaultView.getSelection();
let range = domSelection.getRangeAt(0);
range = expandRangeToStartOfLine(range);

Expand Down
9 changes: 7 additions & 2 deletions src/component/handlers/edit/editOnBeforeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function replaceText(
*/
function editOnBeforeInput(
editor: DraftEditor,
e: SyntheticInputEvent<>,
e: SyntheticInputEvent<HTMLElement>,
): void {
if (editor._pendingStateFromBeforeInput !== undefined) {
editor.update(editor._pendingStateFromBeforeInput);
Expand Down Expand Up @@ -159,7 +159,12 @@ function editOnBeforeInput(
// Chrome will also split up a node into two pieces if it contains a Tab
// char, for no explicable reason. Seemingly caused by this commit:
// https://chromium.googlesource.com/chromium/src/+/013ac5eaf3%5E%21/
const nativeSelection = global.getSelection();

// in test environment, e.target is not available
const nativeSelection = (e.currentTarget
? e.currentTarget.ownerDocument.defaultView
: global
).getSelection();
// Selection is necessarily collapsed at this point due to earlier check.
if (
nativeSelection.anchorNode &&
Expand Down
9 changes: 4 additions & 5 deletions src/component/handlers/edit/editOnBlur.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const EditorState = require('EditorState');
const containsNode = require('containsNode');
const getActiveElement = require('getActiveElement');

function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
function editOnBlur(editor: DraftEditor, e: SyntheticEvent<HTMLElement>): void {
// In a contentEditable element, when you select a range and then click
// another active element, this does trigger a `blur` event but will not
// remove the DOM selection from the contenteditable.
Expand All @@ -27,13 +27,12 @@ function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
// We therefore force the issue to be certain, checking whether the active
// element is `body` to force it when blurring occurs within the window (as
// opposed to clicking to another tab or window).
// However if users wish to override this behaviour they can provide
// a prop preserveSelectionOnBlur of `true`.
const {ownerDocument} = e.currentTarget;
if (
!Boolean(editor.props.preserveSelectionOnBlur) &&
getActiveElement() === document.body
getActiveElement(ownerDocument) === ownerDocument.body
) {
const selection = global.getSelection();
const selection = ownerDocument.defaultView.getSelection();
const editorNode = editor.editor;
if (
selection.rangeCount === 1 &&
Expand Down
8 changes: 5 additions & 3 deletions src/component/handlers/edit/editOnCut.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
* @flow
* @emails oncall+draft_js
*/

Expand All @@ -19,6 +19,7 @@ const Style = require('Style');

const getFragmentFromSelection = require('getFragmentFromSelection');
const getScrollPosition = require('getScrollPosition');
const isNode = require('isInstanceOfNode');

/**
* On `cut` events, native behavior is allowed to occur so that the system
Expand All @@ -43,8 +44,9 @@ function editOnCut(editor: DraftEditor, e: SyntheticClipboardEvent<>): void {

// Track the current scroll position so that it can be forced back in place
// after the editor regains control of the DOM.
if (element instanceof Node) {
scrollPosition = getScrollPosition(Style.getScrollParent(element));
if (isNode(element)) {
const node: Node = (element: any);
scrollPosition = getScrollPosition(Style.getScrollParent(node));
}

const fragment = getFragmentFromSelection(editorState);
Expand Down

0 comments on commit dceddf5

Please sign in to comment.